LoginSignup
urakata_engineer
@urakata_engineer

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

react-springでスライドアニメーションを作りたい

解決したいこと

表題の件、react-springでスライドアニメーションを実現したいです。

カードコンポーネントを初期表示時、useSpringを初期化していて、
ボタン押下時にスライドアニメーションを適用したいのですが、
最初にボタンを押下した時だけ、スライドが適用されずに位置だけが移動してしまいます。

2回目以降はしっかりとスライドされるので、hookや初期化が間違えている気がしているのですが、
ハマってしまっているので、何かご助言いただければ幸いです。

よろしくお願いします。

再現動画貼りたかったですが、Qiitaでは投稿できず、ソースのみ提供いたします。

ざっくりとした質疑で申し訳ないですが、ここ違うのでは?という箇所あれば
ご指摘いただきたいです。

該当するソースコード

function MatchCard({ user }) {
  const [loading, setLoading] = useState(true);
  const [currentIndex, setCurrentIndex] = useState(0); // カード枚数
  const [liked, setLiked] = useState(null);
  const [skipped, setSkipped] = useState(null);

  // カードのアニメーション
  const [cardStyles, setCardStyles] = useSpring(() => ({
    transform: liked ? 'translateX(-100%)' : skipped ? 'translateX(100%)' : 'translateX(0)',
    config: { tension: 80, friction: 20 },
  }));

  const handleLike = () => {
    setLiked(true);
    setSkipped(false);
    // いいねの処理を追加する
    nextCard();
  };

  const handleSkip = () => {
    setSkipped(true);
    setLiked(false);
    // スキップの処理を追加する
    nextCard();
  };

  const nextCard = () => {
    setCurrentIndex(prevIndex => prevIndex + 1);
  };

  useEffect(() => {
    setCardStyles({
      transform: liked ? 'translateX(-100%)' : skipped ? 'translateX(100%)' : 'translateX(0)',
    });
  }, [liked, skipped, setCardStyles]);

  useEffect(() => {
    // データのロードが完了したらloadingをfalseに設定する
    setLoading(false);
  }, []);

  useEffect(() => {
    console.log(`いま表示しているカードの順番${currentIndex}`);
  }, [currentIndex]);

  if (loading) {
    // データがロード中の場合、くるくるアニメーションを表示する
    return (
      <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '300px' }}>
        <CircularProgress />
      </div>
    );
  }

  return (
    <animated.div style={{...cardStyles, display: 'inline-block', width: '345px', height: '100%'}}>
    {/* <div style={{ display: 'inline-block', width: '345px', height: '100%' }}> */}
      <Card sx={{ maxWidth: 345 }}>
        <CardMedia
          component="img"
          height="140"
          image={user.imageUrl}
          alt={user.name}
        />
        <CardContent>
          <Typography gutterBottom variant="h5" component="div" noWrap>
            {user.name}
          </Typography>
          <Typography variant="body2" color="text.secondary" noWrap>
            {user.description}
          </Typography>
        </CardContent>
        <ButtonGroup fullWidth variant="contained">
          <Button sx={{ flexGrow: 1 }} color="primary" onClick={handleLike} disabled={liked}>Like</Button>
          <Button sx={{ flexGrow: 1 }} color="secondary" onClick={handleSkip} disabled={skipped}>Skip</Button>
        </ButtonGroup>
      </Card>
    {/* </div> */}
    </animated.div>
  );
}

export default MatchCard;

0

1Answer

自己解決しました。

useSpringのそもそもの使い方が間違っていたようです。

useSpringにCSSスタイルを当てるのは問題ないですが、
カスタムイベントでそのuseSpringを更新するような実装では、
新しいスタイルがuseSpringフックに検知されず、アニメーションが実行されない
という最大の落とし穴があるようでした。

正解はこう↓

// useSpringでスタイルを設定し、返り値を変数xとして定義
const { x } = useSpring({ x: 0 });

// startプロパティでtoの値を設定することで、移動できる。
useEffect(() => {
    if (liked) {
      // カードがいいねまたはスキップされたらアニメーションを開始する
      x.start({ to: -100 });
    } else {
      x.start({ to: 100 });
    }
  }, [currentIndex, liked, skipped, x]);

<animated.div style={
      {
        display: 'inline-block',
        width: '345px',
        height: '100%',
        transform: x.to((val) => `translateX(${val}px)`) // transform属性でxの値を変数化
      }}> {children} </animated.div>

0

Your answer might help someone💌