资讯专栏INFORMATION COLUMN

拖拽参考线及吸附工具react-dragline

asce1885 / 3301人阅读

摘要:剩下的,就是把精力集中于实现核心功能参考线和吸附。以下根据拖拽的事件周期,,分别阐述。但是考虑到吸附功能是需要对元素的位置具备完全地控制能力,因为初步决定只提供的使用方式。

大概在2017年7月,我司计划开发一款可视化建站的项目。由于团队初建人手短缺,当时只有一年工作经验的我被“赶鸭子上架”,开始了为期一年半的折腾之旅。在众多复杂的交互中,有一项需求是“拖拽对齐吸附及显示参考线”,当时也希望在社区寻找解决方案。很可惜,除了一些简单的DEMO外,并没有可用于生产环境的实践。一年半过去了,项目接近尾声不那么忙。我逐步整理出自己在工作中的解决方案,于是就有了这个开源项目react-dragline。

示例

开门见山,首先上一个简单的例子。

import { DraggableContainer, DraggableChild } from "react-dragline"

const children = [
  { id: 1, position: { x: 100, y: 10 } },
  { id: 2, position: { x: 400, y: 200 } },
]

const containerStyle = {
  height: 600,
  position: "relative",
}

const childStyle = {
  width: 100,
  height: 100,
  cursor: "move",
  background: "#8ce8df",
}

export default function Example() {
  return (
    
      {
        children.map(({ id, position }) => (
          
            
)) } ) }

然后你就可以动手拖一拖体验一下啦~ 在线DEMO戳我

关于调用方式,起初参考了react-sortable-hoc,计划使用HOC的写法,但感觉使用HOC会把代码从JSX中分离,复杂度不高的情况下有些过度设计,所以这选择了这更传统的写法。位置属性是通过绝对定位实现的,因此需要使用者自行为DraggableContainer加上定位属性relative/absolute/fixed,本意是检测到没有定位属性时自动加上relative,但是这种方式在服务端渲染的场景下会有丑陋的“跳动”(因为只有在客户端才能检测DOM嘛),因此就把这项功能给去了。更多的options都写在README里了,出自我的“中式英语”大家阅读起来也没什么难度。

实现原理

关于原理,拖拽功能是基于react-draggable的Uncontrolled组件DraggableCore,统一使用left和top作为x,y坐标的映射。DraggableContainerDraggableChild之间的通信是通过React.cloneElement实现的。剩下的,就是把精力集中于实现核心功能参考线和吸附。以下根据拖拽的事件周期onstartondragonstop分别阐述。

onstart

在拖拽初始中获取每个DraggableChild的坐标、宽高、索引等信息。可以将所有的DraggableChild分为两类,target为当前拖拽的元素,compares为其余的元素,可以称为对照组(为了方便行为,下文中target即代表拖拽目标元素,compare即代表当前与target比较的元素)。为什么不在componentDidMount中就获取好呢?因为这些元素的信息可能会变得,比如说增删。相比起这细微的性能损失,维护信息变化的成本显然要高得多。

ondrag

核心代码主要在ondrag的过程中,我们需要不断的去比较targetcompares之间的距离是否小于阈值threshold(默认5px)。考虑过是否需要加上debounce,但是似乎对灵敏度还是有些影响,不是一个太好的选择。
吸附功能的实现相对简单,坐标和对照组的某元素的距离小于阈值threshold时,让其等于对照组的坐标即可:

  // a 为对照组某元素的坐标
  if (Math.abs(a - x) < threshold + 1) {
    x = a
  }

参考线的实现略微复杂一些,以Y轴方向为例,最初的实现是分别取target元素和compare元素上下位置(Element.getBoundingClientRect),组成一个包含四个值的数组[t, b, T, B]target用小写字母表示,compare用大写字母表示),最大差值(排序取首尾值相减)即为参考线的长度,取最小值作为参考线的起点。
这么做似乎也没有什么问题,实际上是有一些细微的误差的。使用DOM元素的位置信息计算具有一定的滞后性,DOM表示的是当前的位置,而计算的是拖拽下一帧的位置,这样“细微的误差”也就可以解释了。解决方式也很简单,将数组中的tb替换为yy + height即可。
结束了吗? 并没有。最初的设计是将计算xy是否需要吸附和参考线在统一流程里,因为有吸附才有会出现参考线,避免了重复的计算。然而,当target元素同时和X轴和Y轴两个compare元素吸附时,Y轴的参考线是会受到Y轴吸附的影响(X轴同理)。见下图:

当水平方向上两个绿色的元素吸附时,Y轴的参考线也必须“突然地增加了一段”。因此后续又做了一次代码封装粒度更小的重构,以在计算完成xy吸附之后再对参考线作出一次修正。

onstop

拖拽结束就比较简单了,将参考线的和一些其它状态清除就好了。

收获与总结

在整理这些项目的过程中,除了核心代码本身,还有一些我觉得更为宝贵的收获。

在构建方式上,我们日常的项目(Application)开发都是把源码和第三方依赖等打包成“可执行文件”,即可以直接扔到浏览器上跑JavaScript代码。但是在打造一款第三方项目(Library)时,这样做是显然不可行的。试想一下,如果一个项目有10个第三方依赖,而每个依赖都引入classnames,如果这些第三方依赖包都把classnames打包到源码中,那对于使用者来说,岂不是有10份重复的classnames代码?实际上我们需要做的是“只编译,不打包”。可否记得你在使用npm install的时候安装数量都是远远大于写在package.json内依赖的数量?没错,所有依赖及依赖的依赖...都是由用户统一安装,这样就可以避免了上述“10份重复的代码”的问题。另外,一般考虑到浏览器用户,确实会提供一份把依赖也打包进源码的UMD文件。

关于前端测试,这也是我之前了解较少的领域。一般前端业务变化频繁,生命周期相对较短,不太具备持续迭代的可能,因此写测试倒说不上是一个性价比高的选择。但是对于需要持续迭代的底层UI(组件库),单元测试的必要性还是很高的。因此我也假模假样地基于jest和enzyme写了一些测试用例,以保证后续迭代的不会因为粗心大意而对之前功能有所影响。

细心的朋友可能会发现,我在DraggableChild中使用的defaultPosition而不是position,这里就涉及到一些uncontrolled components的知识。一般来说,合格的React组件是需要提供positiondefaultPosition两种使用方式的。但是考虑到吸附功能是需要对元素的位置具备完全地控制能力,因为初步决定只提供defaultPosition的使用方式。

react-dragline算是我的第一个不那么玩具的开源项目了,欢迎大家交流拍砖~

原文首发于我的博客https://www.vq0599.com/p/44,转载请注明。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109173.html

相关文章

  • react下移动端可吸附悬浮窗,悬浮球,悬浮按钮,支持拖动拖拽功能

    摘要:基于实现的移动端的可吸附悬浮按钮预览地址移动端源码地址安装使用 基于react实现的移动端的可吸附悬浮按钮 预览地址(移动端): https://kkfor.github.io/suspe... 源码地址: https://github.com/kkfor/susp... 安装 npm install suspend-button -S 使用 import React, { Compo...

    kun_jian 评论0 收藏0
  • react下移动端可吸附悬浮窗,悬浮球,悬浮按钮,支持拖动拖拽功能

    摘要:基于实现的移动端的可吸附悬浮按钮预览地址移动端源码地址安装使用 基于react实现的移动端的可吸附悬浮按钮 预览地址(移动端): https://kkfor.github.io/suspe... 源码地址: https://github.com/kkfor/susp... 安装 npm install suspend-button -S 使用 import React, { Compo...

    张红新 评论0 收藏0
  • vue-draggable-resizable 可拖拽缩放的组件

    摘要:类似于吸附效果代码修改组件之间的冲突检测首先是组件之间的冲突检测,组件与组件的边界检测需要一个标记进行判断。是否开启元素对齐当调用对齐时,用来设置组件与组件之间的对齐距离,以像素为单位。如果发现什么或者可以将代码优化的地方请劳烦告知我。 Vue 用于可调整大小和可拖动元素的组件并支持组件之间的冲突检测与组件对齐 更新2.0版本 说明:组件基于vue-draggable-resizabl...

    springDevBird 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<