React 中touch和click事件冲突

在浏览器实现可拖拽的元素(以img为)同时添加了onTouchStart、onTouchMove、onTouchEnd和onClick事件。
实现如下:

const FloatingButton = ({
  src,
  alt,
  onClick,
  draggable = false
}) => {
  const [position, setPosition] = useState({});
  const ref = useRef();
  const startPoint = useRef();
  const touchStartPoint = useRef();
  const touchMovePoint = useRef();
  const { width: screenWidth, height: screenHeight } = window.screen;

  const calculatePosition = (pointOffset, currentSize, fullSize) => {
    if (pointOffset < 0) {
      return 0;
    } else if (pointOffset + currentSize > fullSize) {
      return fullSize - currentSize;
    }
    return pointOffset;
  };

  const left = () => {
    const pointOffsetX = startPoint.current.x + (touchMovePoint.current.clientX - touchStartPoint.current.clientX);
    return calculatePosition(pointOffsetX, startPoint.current.width, screenWidth);
  };

  const top = () => {
    const pointOffsetY = startPoint.current.y + (touchMovePoint.current.clientY - touchStartPoint.current.clientY);
    return calculatePosition(pointOffsetY, startPoint.current.height, screenHeight);
  };

  const handleClick = () => {
    onClick();
  };

  const handleTouchStart = (e) => {
    e.stopPropagation();
    if (draggable) {
      touchStartPoint.current = e.targetTouches[0];
      startPoint.current = {
        x: ref.current.getBoundingClientRect().left,
        y: ref.current.getBoundingClientRect().top,
        width: ref.current.width,
        height: ref.current.height
      };
    }
  };

  const handleTouchMove = (e) => {
    e.stopPropagation();
    if (draggable) {
      touchMovePoint.current = e.targetTouches[0];
      setPosition({
        left: left(),
        top: top()
      });
    }
  };

  const handleTouchEnd = (e) => {
    e.stopPropagation();
    if (draggable) {
      setPosition({
        left: screenWidth / 2 > left() ? 0 : screenWidth - startPoint.current.width,
        top: top()
      });
    }
  };

  return (
    {alt}
  );
};

当在未拖动过的情况下,进行点击,会报错:

TypeError: Cannot read property 'clientX' of undefined

原因:
当同时监听touch和click事件时,触发顺序是 onTouchStart => onTouchEnd => onClick
所以touchMovePoint并未被赋值,导致报错。
虽然报错并不影响使用,但是还是想把它解决掉。

解决方案:
想要同时保留可以点击和可拖拽的功能,但是又要解决冲突,可以有2种解决方案。

//方案一
const FloatingButton = ({
  src,
  alt,
  onClick,
  draggable = false
}) => {
  const [position, setPosition] = useState({});
  const ref = useRef();
  const startPoint = useRef();
  const touchStartPoint = useRef();
  const touchMovePoint = useRef();
  const { width: screenWidth, height: screenHeight } = window.screen;

  const calculatePosition = (pointOffset, currentSize, fullSize) => {
    if (pointOffset < 0) {
      return 0;
    } else if (pointOffset + currentSize > fullSize) {
      return fullSize - currentSize;
    }
    return pointOffset;
  };

  const left = () => {
    const pointOffsetX = startPoint.current.x + (touchMovePoint.current.clientX - touchStartPoint.current.clientX);
    return calculatePosition(pointOffsetX, startPoint.current.width, screenWidth);
  };

  const top = () => {
    const pointOffsetY = startPoint.current.y + (touchMovePoint.current.clientY - touchStartPoint.current.clientY);
    return calculatePosition(pointOffsetY, startPoint.current.height, screenHeight);
  };

  const handleClick = () => {
    onClick();
  };

  const handleTouchStart = (e) => {
    e.stopPropagation();
    if (draggable) {
      touchStartPoint.current = e.targetTouches[0];
      startPoint.current = {
        x: ref.current.getBoundingClientRect().left,
        y: ref.current.getBoundingClientRect().top,
        width: ref.current.width,
        height: ref.current.height
      };
    }
  };

  const handleTouchMove = (e) => {
    e.stopPropagation();
    if (draggable) {
      touchMovePoint.current = e.targetTouches[0];
      setPosition({
        left: left(),
        top: top()
      });
    }
  };

  const handleTouchEnd = (e) => {
    e.stopPropagation();
    if (touchMovePoint && draggable) {
      setPosition({
        left: screenWidth / 2 > left() ? 0 : screenWidth - startPoint.current.width,
        top: top()
      });
      touchMovePoint.current = null;
    }
  };

  return (
    {alt}
  );
};
//方案二
const FloatingButton = ({
  src,
  alt,
  onClick,
  draggable = false
}) => {
  const [position, setPosition] = useState({});
  const ref = useRef();
  const startPoint = useRef();
  const touchStartPoint = useRef();
  const touchMovePoint = useRef();
  const { width: screenWidth, height: screenHeight } = window.screen;

  const calculatePosition = (pointOffset, currentSize, fullSize) => {
    if (pointOffset < 0) {
      return 0;
    } else if (pointOffset + currentSize > fullSize) {
      return fullSize - currentSize;
    }
    return pointOffset;
  };

  const left = () => {
    const pointOffsetX = startPoint.current.x + (touchMovePoint.current.clientX - touchStartPoint.current.clientX);
    return calculatePosition(pointOffsetX, startPoint.current.width, screenWidth);
  };

  const top = () => {
    const pointOffsetY = startPoint.current.y + (touchMovePoint.current.clientY - touchStartPoint.current.clientY);
    return calculatePosition(pointOffsetY, startPoint.current.height, screenHeight);
  };

  const handleClick = () => {
    onClick();
  };

  const handleTouchStart = (e) => {
    e.stopPropagation();
    if (draggable) {
      touchStartPoint.current = e.targetTouches[0];
      startPoint.current = {
        x: ref.current.getBoundingClientRect().left,
        y: ref.current.getBoundingClientRect().top,
        width: ref.current.width,
        height: ref.current.height
      };
    }
  };

  const handleTouchMove = (e) => {
    e.stopPropagation();
    if (draggable) {
      touchMovePoint.current = e.targetTouches[0];
      setPosition({
        left: left(),
        top: top()
      });
    }
  };

  const handleTouchEnd = (e) => {
    e.stopPropagation();
    if (!touchMovePoint) {
       handleClick();
    } else if (draggable) {
      setPosition({
        left: screenWidth / 2 > left() ? 0 : screenWidth - startPoint.current.width,
        top: top()
      });
      touchMovePoint.current = null;
    }
  };

  return (
    {alt}
  );
};

你可能感兴趣的:(React 中touch和click事件冲突)