JavaScript вимкнено. Код нижче — повний приклад.
App.jsximport { useRef, useState } from 'react';
import {
Animated,
dragInput,
input,
useAnimated,
useAnimatedListener,
} from '@kkwaenggwari/animated';
import './styles.css';
const cards = [
{ id: 1, title: 'Kyoto', color: '#2854ff' },
{ id: 2, title: 'Lisbon', color: '#8fb0ff' },
{ id: 3, title: 'Oaxaca', color: '#5c7cff' },
{ id: 4, title: 'Tromsø', color: '#16181d' },
{ id: 5, title: 'Busan', color: '#4a6bff' },
];
function TopCard({ card, threshold, rotation, onGone }) {
const node = useRef(null);
const gone = useRef(false);
const drag = useAnimated(
{ x: 0, y: 0 },
(point) => {
input(
node,
dragInput({
x: point.x,
y: point.y,
settle: ({ projected }) =>
Math.abs(projected.x) > threshold
? { x: Math.sign(projected.x) * 560, y: projected.y }
: { x: 0, y: 0 },
}),
);
},
[threshold],
);
useAnimatedListener(drag.x, ({ value }) => {
if (!gone.current && Math.abs(value) > 540) {
gone.current = true;
onGone(value > 0 ? 'Keep' : 'Skip');
}
});
const rotate = drag.x.interpolate({
inputRange: [-300, 300],
outputRange: [`${-rotation}deg`, `${rotation}deg`],
});
const keep = drag.x.interpolate({
inputRange: [0, threshold],
outputRange: [0, 1],
extrapolate: 'clamp',
});
const skip = drag.x.interpolate({
inputRange: [-threshold, 0],
outputRange: [1, 0],
extrapolate: 'clamp',
});
return (
<div
ref={node}
className="card top"
style={{
background: card.color,
transform: `translate(calc(${drag.x} * 1px), calc(${drag.y} * 1px)) rotate(${rotate})`,
}}
>
<strong>{card.title}</strong>
<span className="stamp keep" style={{ opacity: keep }}>
Keep
</span>
<span className="stamp skip" style={{ opacity: skip }}>
Skip
</span>
</div>
);
}
export default function App({ threshold = 120, rotation = 16 }) {
const [deck, setDeck] = useState(cards);
const [last, setLast] = useState('Swipe the top card');
const gone = (verdict) => {
setLast(`${deck[0].title}: ${verdict}`);
setDeck((list) => list.slice(1));
};
return (
<div className="stage">
<div className="deck">
{deck.map((card, depth) => (
<Animated.div
key={card.id}
className="slot"
style={{ zIndex: deck.length - depth }}
pose={{
opacity: depth < 3 ? 1 : 0,
transform: [{ translateY: depth * 16 }, { scale: 1 - depth * 0.06 }],
}}
tempo={{ spring: { stiffness: 260, damping: 22 } }}
>
{depth === 0 ? (
<TopCard card={card} threshold={threshold} rotation={rotation} onGone={gone} />
) : (
<div className="card" style={{ background: card.color }}>
<strong>{card.title}</strong>
</div>
)}
</Animated.div>
))}
{deck.length === 0 && (
<button type="button" className="reset" onClick={() => setDeck(cards)}>
Deal again
</button>
)}
</div>
<output>{last}</output>
</div>
);
}