react列表手势触摸拖拽元素和滚动条冲突问题

1.场景:列表支持手势touch触屏,列表支持手指上下(或左右)滑动进行滚动,手指点击listItem元素可拖拽元素。

2.问题:滚动条事件和手势touch事件冲突。

3.解决方案:300毫秒以内,判断用户手势滑动角度判断用户手势滑动是横向滑动操作类型:拖拽事件、滚动条事件两种类型。

3.1 y轴有滚动条例子:

垂直方向:在不可取的方向上进行滑动,执行y轴方向滚动事件;

水平方向:在可取范围手指拖动,执行拖拽事件。

这样就能避手势触摸滑动和滚动条事件冲突问题。

3.2 x轴有滚动条例子:和y轴方向处理方案相反。

4.核心代码:

滑动组件代码:

import './index.less';

import React, { useEffect, useRef } from 'react';
interface Props {
  /** 手势水平滑动事件 */
  onVertical?: () => void;
  /** 手势垂直滑动事件 */
  onHorizontal?: () => void;
  /** 手势滑动的组件 */
  children?: React.ReactNode;
  /** 可用水平角度值 */
  degreeValue?: number;
}
/** 手势触屏滑动位置信息 */
type PositionT = {
  // 滑动开始x位置
  startX: number,
  // 滑动开始x位置
  startY: number,
  // 滑动结束x位置
  endX: number,
  // 滑动结束x位置
  endY: number,
  // 时间差
  timeStamp: number,
  // 当前手势状态: 水平方向或垂直方向
  direction: string,
};

const defaultPosition = {
  startX: 0,
  startY: 0,
  endX: 0,
  endY: 0,
  timeStamp: 0,
  direction: '',
};

/** 设置时间阈值为300毫秒 */
const TIME = 300;

const SwipeDetector: React.FC<Props> = ({
  children,
  onHorizontal,
  onVertical,
  degreeValue = 15,
}) => {
  // Dom ref属性
  const domRef = useRef<HTMLDivElement>(null);
  useEffect(() => {
    // 手势触屏滑动位置信息
    let position: PositionT = { ...defaultPosition }

    // 开始触屏
    const onTouchStart = (e: TouchEvent) => {
      position.startX = e.touches[0].clientX;
      position.startY = e.touches[0].clientY;

      position.timeStamp = new Date().getTime();
    };
    // 滑动中
    const onTouchMove = (e: TouchEvent) => {
      position.endX = e.touches[0].clientX;
      position.endY = e.touches[0].clientY;

      const currTime = new Date().getTime();
      // 300ms生效计算事件类型
      if (Math.floor(currTime - position.timeStamp) < TIME && !position.direction) {
        const angx = position.endX - position.startX;
        const angy = position.endY - position.startY;
        // 拖拽的角度
        const angle = getAngle(angx, angy);
        // 是否在有效区域内拖拽
        if (isAvailableAreas(angle)) {
          // 水平滑动事件
          position.direction = 'horizontal';
          onHorizontal?.();
        } else {
          // 垂直滑动事件
          position.direction = 'vertical';
          onVertical?.();
        }
      } else {
        // 垂直移动进行中
        if (position.direction === 'vertical') {
          onVertical?.();
        } else if (position.direction === 'horizontal') {
          // 水平移动进行中
          onHorizontal?.();
        }
      }
    };

    // 滑动结束
    const onTourchEnd = () => {
      position = { ...defaultPosition }
    };
    /** 计算滑动角度 */
    const getAngle = (angx: number, angy: number) => {
      return Math.atan2(angy, angx) * 180 / Math.PI;
    };
    /**
     * 300毫秒以内滑动区域是可拖拽区域
     */
    const isAvailableAreas = (angle: number) => {
      // 右侧有效区域拖拽
      if (angle >= -degreeValue && angle <= degreeValue) {
        // 水平滑动
        return true;
      } else if ((angle >= 180 - degreeValue && angle <= 180) || (angle >= -180 && angle < degreeValue - 180)) {
        // 水平滑动
        return true;
      } else {
        // 垂直滑动
        return false;
      }
    }

    if (domRef?.current) {
      // 滑动事件绑定
      if (domRef?.current) {
        domRef.current.addEventListener('touchstart', onTouchStart);
        domRef.current.addEventListener('touchmove', onTouchMove, false);
        domRef.current.addEventListener('touchend', onTourchEnd);
      }

      // 卸载事件
      return () => {
        if (domRef?.current) {
          domRef.current.removeEventListener('touchstart', onTouchStart);
          domRef.current.removeEventListener('touchmove', onTouchMove, false);
          domRef.current.removeEventListener('touchend', onTourchEnd);
        }
      };
    }
  }, []);

  return (
    <div
      ref={domRef}
      className="swiper-layer"
      style={{ touchAction: 'pan-y' }}
    >
      {children}
    </div>
  );
};

export default SwipeDetector;

列表组件使用代码示例:

  // 是否水平拖拽状态
  const dragging = useRef(false);

  const onVertical = () => {
    //垂直滑动
    dragging.current = false;
  };
  // 水平滑动
  const handleSwiper = () => {
    // 水平滑动
  };
  <SwipeDetector
    onVertical={onVertical}
    onHorizontal={onHorizontal}
    degreeValue={75}
  >
    <my-list>
      <my-list-item>
        onTouchMove={(e) => {
          if (水平拖拽) {
            // 阻止事件冒泡,滚动条无响应
            //执行拖拽逻辑
          } else if (垂直滑动) {
            //执行拖拽逻辑
          }
        }}
        onTouchEnd={(e) => {
          dragging.current = false;
        }}
      />
    </my-list>
  </SwipeDetector>

: 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值