react按钮拖拽_组件封装@非常有用的拖拽视图组件

前言

嗨喽,各位小伙伴,今天是圣诞节,祝大家节日快乐。之前有给大家分享过vue实现拖拽视图,目前我正在开发的项目使用的是React Hooks + TS技术栈,前段时间呢基于React + TS 重构了拖拽视图组件并且做了优化,今天刚好有时间写文章分享给大家。我们知道,在开发移动端H5页面时,部分页面可能存在悬浮按,而悬浮按钮往往会出现遮挡正文视图的情况,此时产品经理就会要求你这个悬浮按钮得支持拖拽,不过不用担心,耀哥今天就带着大家去封装一个拖拽视图组件,以便不时之需。

1a3b2458b81c7f70d3c6aeb66f298a24.gif

思考

要想实现拖拽视图组件,我们可将拖拽元素固定定位并且监听拖拽元素的 touchmove 事件实时计算更新 topleft 值即可。在封装拖拽视图组件之前,首先要思考以下几个问题:

「1. 调用方式」

调用者应该如何调用我们封装的拖拽视图组件?你要暴露哪些属性供调用者使用?为了让调用者能够自定义拖拽视图,这里我选择使用插槽自定义视图内容,其次暴露供调用者设置初始位置的属性 positon 及监听用户点击拖拽元素的 onTap 属性。所以调用方式如下:

 {}}>
"box">拖拽式图内容

「2. 处理初始值」

通常悬浮按钮放置于屏幕右下角位置,所以我默认将拖拽视图放置在距离屏幕右侧15像素,底部80像素的位置,并且将拖拽视图放置的位置以屏幕右侧和底部为基准,即:

  • 如果只设置top或者bottom值,则水平方向默认居右;
  • 如果只设置left或者right值,则垂直方向默认居下;
  • 如果同时设置top、right、bottom、left值,则right/bottom值有效;
  • 同一方向,如果同时设置top、bottom值,则bottom值有效;
  • 同一方向,如果同时设置left、right值,则right值有效;

「3. 处理边界」

拖拽元素理论上只支持在屏幕内拖拽,不可超出屏幕边界,所以我们只需计算出拖拽元素在屏幕中可拖拽的范围即可。由于拖拽元素使用固定定位并且我们通过改变 topleft 属性达到拖拽效果,因此:

  • top  取值范围:0 ~ 屏幕高度 - 拖拽元素的高度
  • left 取值范围:0 ~ 屏幕宽度 - 拖拽元素的宽度

「4. 核心技术」

  • fixed
  • getBoundingClientRect()
  • touchmove 事件

实现

提示:代码中均附有详细注释,这里直接贴出代码,有不明白的朋友欢迎留言。

首先在components目录下新建拖拽视图文件:

.
├── DragView
    ├── index.tsx
    └── index.less

index.tsx 文件代码:

import React, { FC, memo, useEffect, useRef, useState } from 'react';
import './index.less';

/** 组件属性接口 */
interface IProps {
  /** 拖拽元素 */
  children: JSX.Element | JSX.Element[];
  /** 拖拽元素初始位置 */
  position?: {
    top?: number;
    right?: number;
    bottom?: number;
    left?: number;
  };
  /** 点击事件 */
  onTap?: () => void;
}

const DragView: FC = props => {// 设置默认初始位置const { position = { right: 15, bottom: 80 } } = props;// refsconst lgWrapper = useRefnull>(null); /** 存储容器对象 */// statesconst [rect, setRect] = useState(() => ({ width: 0, height: 0 })); /** 屏幕尺寸 */const [bounding, setBounding] = useState(() => ({x: 0,y: 0,})); /** 拖拽边界值 */const [pos, setPos] = useState(() => ({ x: 0, y: 0 })); /** 拖拽元素坐标 */// methodsconst calc = () => {// 获取屏幕的尺寸信息const clientWidth = window.innerWidth;const clientHeight = window.innerHeight;if (lgWrapper.current) {// 获取容器元素的尺寸信息const _rect = lgWrapper.current.getBoundingClientRect();// 获取用户设置的位置信息const { top, right, bottom, left } = position;// 定义_pos记录临时坐标,默认在右下侧const _pos = {
        x: clientWidth - _rect.width,
        y: clientHeight - _rect.height,
      };// 单独判断并设置各方向的值if (top !== undefined) {
        _pos.y = top;
      }if (right !== undefined) {
        _pos.x = clientWidth - right - _rect.width;
      }if (bottom !== undefined) {
        _pos.y = clientHeight - bottom - _rect.height;
      }if (left !== undefined) {
        _pos.x = left;
      }// 同一方向,如果同时设置top、bottom值,则bottom值有效;if (top !== undefined && bottom !== undefined) {
        _pos.y = clientHeight - bottom - _rect.height;
      }// 同一方向,如果同时设置left、right值,则right值有效;if (left !== undefined && right !== undefined) {
        _pos.x = clientWidth - right - _rect.width;
      }// 更新拖拽元素位置
      setPos({ ..._pos });// 记录容器尺寸信息
      setRect(_rect);// 获取拖拽元素在屏幕内可拖拽的边界值
      setBounding({
        x: clientWidth - _rect.width,
        y: clientHeight - _rect.height,
      });
    }
  };// effects
  useEffect(() => {if (lgWrapper.current) {// 当组件一加载就计算初始位置信息
      calc();
    }
  }, [lgWrapper]);
  useEffect(() => {// 拖拽事件处理函数const onMove = (event: TouchEvent) => {// 获取触点const touch = event.touches[0];// 定位滑块的位置
      let x = touch.clientX - rect.width / 2;
      let y = touch.clientY - rect.height / 2;// 处理边界if (x 0) {
        x = 0;
      } else if (x > bounding.x) {
        x = bounding.x;
      }if (y 0) {
        y = 0;
      } else if (y > bounding.y) {
        y = bounding.y;
      }// 更新拖拽视图位置
      setPos({ x, y });// 阻止默认行为
      event.preventDefault();// 阻止事件冒泡
      event.stopPropagation();
    };// 监听拖拽事件if (lgWrapper.current) {
      lgWrapper.current.addEventListener('touchmove', onMove, {
        passive: false,
      });
    }// 移除拖拽事件return () => {if (lgWrapper.current)
        lgWrapper.current.removeEventListener('touchmove', onMove);
    };
  }, [lgWrapper, bounding, rect]);// renderreturn (
      className="lg-drag-view"
      style={{
        left: `${pos.x}px`,
        top: `${pos.y}px`,
      }}
      onClick={() => {if (props.onTap) props.onTap();
      }}
    >
      {props.children}

  );
}; export  default memo(DragView);

index.less 文件:

.lg-drag-view {
  position: fixed;
  z-index: 999;
}

大家可直接复制代码去验证组件是否可用。

尾言

好啦,各位小伙伴,拖拽视图是不是特别简单呢?大家赶快去尝试吧。今天的分享就到这里啦,喜欢这篇文章的朋友可以点赞,也可以直接关注我的公众号,您的关注与支持,是我唯一继续下去的动力!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值