幻灯片卡住成帧器运动

slideshow stuck framer motion

我想在 framer motion 中制作幻灯片,我发现在 framer motion 文档中有这样的幻灯片示例 https://codesandbox.io/s/framer-motion-image-gallery-pqvx3?from-embed=&file=/src/Example.tsx, but I found a bug when we drag and double click it, it will be stuck like this picture

import * as React from "react";
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { wrap } from "popmotion";
import { images } from "./image-data";

const variants = {
  enter: (direction: number) => {
    return {
      x: direction > 0 ? 1000 : -1000,
      opacity: 0
    };
  },
  center: {
    zIndex: 1,
    x: 0,
    opacity: 1
  },
  exit: (direction: number) => {
    return {
      zIndex: 0,
      x: direction < 0 ? 1000 : -1000,
      opacity: 0
    };
  }
};
const swipeConfidenceThreshold = 10000;
const swipePower = (offset: number, velocity: number) => {
  return Math.abs(offset) * velocity;
};

export const Example = () => {
  const [[page, direction], setPage] = useState([0, 0]);images.
  const imageIndex = wrap(0, images.length, page);

  const paginate = (newDirection: number) => {
    setPage([page + newDirection, newDirection]);
  };

  return (
    <>
      <AnimatePresence initial={false} custom={direction}>
        <motion.img
          key={page}
          src={images[imageIndex]}
          custom={direction}
          variants={variants}
          initial="enter"
          animate="center"
          exit="exit"
          transition={{
            x: { type: "spring", stiffness: 300, damping: 30 },
            opacity: { duration: 0.2 }
          }}
          drag="x"
          dragConstraints={{ left: 0, right: 0 }}
          dragElastic={1}
          onDragEnd={(e, { offset, velocity }) => {
            const swipe = swipePower(offset.x, velocity.x);

            if (swipe < -swipeConfidenceThreshold) {
              paginate(1);
            } else if (swipe > swipeConfidenceThreshold) {
              paginate(-1);
            }
          }}
        />
      </AnimatePresence>
    </>
  );
};

我尝试解决这个问题,但仍然无法解决,有人可以帮助我吗?

这看起来像是 framer-motion 的错误。

直到 v1.6.2,everything works fine。该错误似乎出现在所有更高版本中。

还有一个有趣的更新日志:

[1.6.3] 2019-08-19 Fixed Ensuring onDragEnd always fires after if onDragStart fired.

Here is a link to the related issue on GitHub, opened by the author of this question.

在修复该错误之前,这里有一个使用 Pan 事件的解决方法

export default function Carousel() {
  const animationConfidenceThreshold = 200; // you have to move the element 200px in order to perform an animation

  const [displayed, setDisplayed] = useState(0); // the index of the displayed element
  const xOffset = useMotionValue(0); // this is the motion value that drives the translation

  const lastOffset = useRef(0); // this is the lastValue of the xOffset after the Pan ended
  const elementAnimatingIn = useRef(false); // this will be set to true whilst a new element is performing its animation to the center

  useEffect(() => {
    // this happens after we have dragged the element out and triggered a rerender
    if (elementAnimatingIn.current) {
      const rightPan = xOffset.get() > 0; // check if the user drags it to the right

      // if the element has animated out to the right it animates in from the left
      xOffset.set(
        rightPan ? -1 * window.innerWidth - 200 : window.innerWidth + 200
      );
      // perform the animation to the center
      animate(xOffset, 0, {
        duration: 0.5,
        onComplete: () => {
          xOffset.stop();
        },
        onStop: () => {
          elementAnimatingIn.current = false;
          lastOffset.current = xOffset.get();
        }
      });
    }
  });

  return (
    <div className="container">
      <motion.div
        className="carouselElement"
        onPan={(e, info) => {
          xOffset.set(lastOffset.current + info.offset.x); // set the xOffset to the current offset of the pan + the prev offset
        }}
        style={{ x: xOffset }}
        onPanStart={() => {
          // check if xOffset is animating, if true stop animation and set lastOffset to current xOffset
          if (xOffset.isAnimating()) {
            xOffset.stop();
            lastOffset.current = xOffset.get();
          }
        }}
        onPanEnd={(e, info) => {
          // there can be a difference between the info.offset.x in onPan and onPanEnd
          // so we will set the xOffset to the info.offset.x when the pan ends
          xOffset.set(lastOffset.current + info.offset.x);
          lastOffset.current = xOffset.get(); // set the lastOffset to the current xOffset
          if (Math.abs(lastOffset.current) < animationConfidenceThreshold) {
            // if its only a small movement, animate back to the initial position
            animate(xOffset, 0, {
              onComplete: () => {
                lastOffset.current = 0;
              }
            });
          } else {
            // perform the animation to the next element
            const rightPan = xOffset.get() > 0; // check if the user drags it to the right
            animate(
              xOffset,
              rightPan ? window.innerWidth + 200 : -1 * window.innerWidth - 200, // animate out of view
              {
                duration: 0.5,
                onComplete: () => {
                  // after the element has animated out
                  // stop animation (it does not do this on its own, only one animation can happen at a time)
                  xOffset.stop();
                  elementAnimatingIn.current = true;
                  // trigger a rerender with the new content - now the useEffect runs
                  setDisplayed(rightPan ? displayed - 1 : displayed + 1);
                }
              }
            );
          }
        }}
      >
        <span style={{ userSelect: "none" }}>
          {"I am element #" + displayed}
        </span>
      </motion.div>
    </div>
  );
}

Check this codesandbox out!