キーボードでトリガーされるイベントの機能を実装しようとしています (このカードスワイプの例に基づいています)、再レンダリングがないため (私は想定しています)、状態フックが認識されない/down/trigger
内部で変更が発生するため、実装できないようですuseGesture
、 何か案が?
私は試した
- useState と useEffect を使用してキーダウン状態を設定し、それらを比較します (認識されません)
- based on 状態にクラスを追加する
div
(Spring で補間しない)
// These two are just helpers, they curate spring data, values that are later being interpolated into css
const to = i => ({
x: 0,
y: i * -10,
scale: 1,
angle: 25,
rot: -10 + Math.random() * 20,
delay: i * 100,
});
const from = i => ({ rot: 0, scale: 1.5, y: -1000 });
// This is being used down there in the view, it interpolates rotation and scale into a css transform
const trans = (r, s, a) =>
`perspective(1500px) rotateX(${a}deg) rotateY(${r /
10}deg) rotateZ(${r}deg) scale(${s})`;
const Deck = ({ cards, handleAllSwiped, handleListCard }) => {
const [listed, setListed] = useState(false);
const [collected, setCollected] = useState(false);
const [keypressed, setKeypressed] = useState(() => null);
// The set flags all the cards that are flicked out
const [gone] = useState(() => new Set());
useEffect(
() => {
console.log(`_gone_ has changed`);
console.log({gone})
setKeypressed(null);
},
[gone.size],
);
useEffect(
() => {
console.log(`_keypressed_ has changed`, { keypressed });
keypressed !== null && gone.add[cards.length - gone.size - 1];
},
[keypressed],
);
const handleKeydown = useCallback(e => {
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
return setKeypressed(e.key === 'ArrowLeft' ? 'left' : 'right');
} else {
return null;
}
});
useEffect(() => {
window.addEventListener('keydown', handleKeydown);
return () => window.removeEventListener('keydown', handleKeydown);
}, []);
// Create a bunch of springs that contain x/y-position, rotation and scale - using the helpers above
const [props, set] = useSprings(cards.length, i => ({
...to(i),
from: from(i),
}));
// Create a gesture, we're interested in down-state, delta (current-pos - click-pos), direction and velocity
const bind = useGesture(
({
args: [index],
down,
delta: [xDelta],
distance,
direction: [xDir],
velocity,
}) => {
// If you flick hard enough it should trigger the card to fly out
const trigger = velocity > 0.3 || distance > 200;
// Direction should either point left or right
const dir = xDelta < 0 ? -1 : 1;
const direction = dir > 0 ? `right` : `left`;
// If button/finger's up and trigger velocity is reached, we flag the card ready to fly out
if (!down && trigger) {
gone.add(index);
direction === 'left' ? handleListCard(index) : null;
}
// When all cards are removed, make a reset
if (!down && gone.size === cards.length) {
handleAllSwiped();
}
if (!down || down == false) {
setCollected(false);
setListed(false);
}
// useSprings.set reconfigures the springs
set(i => {
// We're only interested in changing spring-data for the current spring
if (index !== i) return;
const isGone = gone.has(index);
// When a card is gone it flys out left or right, otherwise it's either dragged to delta, or goes back to zero
const x = isGone ? (200 + window.innerWidth) * dir : down ? xDelta : 0;
// How much the card tilts, flicking it harder makes it rotate faster
const rot = xDelta / 100 + (isGone ? dir * 10 * velocity : 0);
// Active cards lift up a bit
const scale = down ? 1.3 : 1;
const angle = down ? 0 : 25;
return {
x,
rot,
scale,
angle,
delay: undefined,
config: { friction: 50, tension: down ? 800 : isGone ? 200 : 500 },
};
});
},
);
// Now we're just mapping the animated values to our view, that's it. Btw, this component only renders once. :-)
return (
<div className={styles.deckRoot}>
<span
className={cn(
styles.dropMarket,
listed && styles['dropMarket--active'],
)}>
<ShoppingCartIcon />
</span>
<span
className={cn(
styles.dropCollection,
collected && styles['dropCollection--active'],
)}>
<LibraryIcon />
</span>
{props.map(({ x, y, rot, scale, angle }, i) => (
<animated.div
key={i}
style={{
transform: interpolate(
[x, y],
(x, y) => `translate3d(${x}px,${y}px,0)`,
),
}}
>
{/* This is the card itself, we're binding our gesture to it (and inject its index so we know which is which) */}
<animated.div
{...bind(i)}
style={{
transform: interpolate([rot, scale, angle], trans),
// backgroundImage: `url(${cards[i]})`,
}}>
{cards[i]}
</animated.div>
</animated.div>
))}
</div>
);
};