百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

前端里的拖拖拽拽

myzbx 2025-01-02 17:55 21 浏览

最近在项目中使用了 react-dnd,一个基于 HTML5 的拖拽库,“拖拽能力”丰富了前端的交互方式,基于拖拽能力,会扩展各种各样的拖拽反馈效果,因此有必要学习了解,最好的学习方式就是实操!

拖拽交互常见于各种前端编辑器里,而“编辑器”是一个集成前端技术能力的综合性工程,其中就会涉及到各种形式的拖拽交互,因为“拖拽”是提升用户体验的重要交互方式,所以需要对拖拽的交互效果做各种定制化,作为开发者理应熟练掌握“拖拽”的应用!

最近在开发一款低代码平台,所以借此机会分享一下关于“拖拽”这一交互的基础知识和实践经验,希望可以给有需要的同学提供一点参考。

一、HTML5 中的拖放

拖(Drag)和放(Drop)是 HTML5 标准的组成部分,了解掌握之后,举一反三,有助于提升我们在拖拽场景下技术方案的设计能力。

1.1 draggable 属性

现代浏览器中,不难发现,图片标签(<img />)是可以被长按拖拽,但如果需要自定义的 DOM 节点可以被拖拽需要配置以告诉浏览器提供对元素(Element / Tag)支持拖拽的能力。

而元素是否允许被拖放且可响应 API 操作依赖于 draggable 全局标签属性

draggable 是一个布尔值类型的标签属性:

  • true:元素可被拖拽
  • false:元素不可拖拽

当元素设置了 draggable 属性,此时长按就可以自由拖拽了:

1.2 Drag & Drop 事件

HTML 的 drag & drop 使用了“DOM Event”和从“Mouse Event”继承而来的“drag event” 。

一个典型的拖拽操作: 用户选中一个可拖拽的(draggable)元素,并将其拖拽(鼠标按住不放)至一个可放置的(droppable)元素上,然后松开鼠标。

在拖动元素期间,一些与拖放相关的事件会被触发,像 dragdragover 类型的事件会被频繁触发。

除了定义拖拽事件类型,每个事件类型还赋予了对应的事件处理器

事件类型

事件处理器

触发时机

绑定元素

dragstart

ondragstart

当开始拖动一个元素时

拖拽

drag

ondrag

当元素被拖动期间按一定频率触发

拖拽

dragend

ondragend

当拖动的元素被释放(?松开、按键盘 ESC)时

拖拽

dragenter

ondragenter

当拖动元素到一个可释放目标元素时

放置

dragexit

ondragexit

当元素变得不再是拖动操作的选中目标时

放置

dragleave

ondragleave

当拖动元素离开一个可释放目标元素

放置

dragover

ondragover

当元素被拖到一个可释放目标元素上时(100 ms/次)

放置

drop

ondrop

当拖动元素在可释放目标元素上释放时

放置

各个事件的时机可以用下面这个图简单表示:

??注意: dragOver 事件的默认行为是:“Reset the current drag operation to "none"”。也就是说,如果不阻止放置元素的 dragOver 事件,则放置元素不会响应“拖动元素”的“放置行为”

// 让绑定该事件的元素支持放置
function handleDragOver(e) {
  // 阻止默认的重置行为
  // 即可成为拖拽元素的放置区
  e.preventDefault();
}

从设计事件标准来看,如果我们需要自行实现拖拽的效果,就需要从这关键的几个事件去思考设计。

1.3 DataTransfer

在上述的事件类型中,不难发现,放置元素和拖动元素分别绑定了自己的事件,可如何将拖拽元素和放置元素建立联系以及传递数据

这就涉及到 DataTransfer 对象:

DataTransfer 对象用于保存拖动并放下(drag and drop)过程中的数据。它可以保存一项或多项数据,这些数据项可以是一种或者多种数据类型。 —— DataTransfer - MDN

DataTransfer 对象在不同浏览器上因为标准可能不一样使得 API 有差异,但有几个“标准(常用)”属性和方法需要熟悉

在 Chrome 浏览器上的 DataTransfer 实例如下:

(1) 属性

属性

说明

dropEffect

获取当前选定的拖放操作类型或者设置的为一个新的类型。值为:none、copy、link、move

effectAllowed

提供所有可用的操作类型。值是:none、copy、copyLink、copyMove、link、linkMove、move、all、uninitialized

files

包含数据传输中可用的所有本地文件的列表。如果拖动操作不涉及拖动文件,则此属性为空列表

items (只读)

提供一个包含所有拖动数据列表的 DataTransferItemList 对象

types (只读)

提供一个 dragstart 事件中设置的格式的 strings 数组。

(2) 方法

属性

说明

setData(format, value)

设置给定类型的数据。如果该类型的数据不存在,则将其添加到末尾,以便类型列表中的最后一项将是新的格式。如果该类型的数据已经存在,则在相同位置替换现有数据。

getData(format)

检索给定类型的数据,如果该类型的数据不存在或 data transfer 不包含数据,则返回空字符串

clearData([format])

删除与给定类型关联的数据。类型参数是可选的。如果类型为空或未指定,则删除与所有类型关联的数据。如果指定类型的数据不存在,或者 data transfer 中不包含任何数据,则该方法不会产生任何效果。

`setDragImage(img

element, xOffset, yOffset)`

在简单的拖拽场景中,其实可以类比 window.localStorage 对象的 setItem()getItem() 方法来理解记忆.

getData() 在测试中发现只能在 ondrop 事件中获取到值:

1.4 一个案例掌握拖放 API

<div>
  <div class="drag" draggable="true" id="dragger" ondragstart="handleDragStart(event)">拖动元素</div>
  <div class="drop" ondrop="handleDrop(event)" ondragover="allowDrop(event)">放置区域</div>
</div>

<script>
  function handleDragStart(e) {
    e.dataTransfer.setData('DRAG_NODE_ID', e.target.id)
  }
  function handleDragOver(e) {
    e.preventDefault();
  }
  function handleDrop(e) {
    e.preventDefault();
    var data = e.dataTransfer.getData('DRAG_NODE_ID');
    e.target.appendChild(document.getElementById(data));
  }
</script>

演示案例: codepen.io/DYBOY/pen/e…

效果:

1.6 兼容性

是 HTML5 标准提出的能力,因此各大浏览器厂商对于标准的支持有差异,其兼容性参考如下:

相较于传统的通过鼠标事件:mousedownmousemovemouseup 组合实现的拖拽要简单很多,少了放入目标边界的判断,也少了对位置的实时获取操作。

另外目前的 API 不算多,例如我们想要定制化拖拽的图片大小、鼠标样式等,目前暂时没发现比较方便的解决方式,但是从另一个角度来说,让我们对于拖拽能力的设计和标准有了一个更深切的认识,对于设计实现拖拽交互有了一个“理论”基础!

二、手搓一个

有了上面的基础知识,那么实现一个列表拖拽排序并不是什么难事。

2.1 设计实现

结合上述的 Drag & Drop 的事件类型,那么拖拽排序主要是针对“拖动对象”之间相互作用关系的逻辑梳理,此处我们暂且区分为:

  • 源对象: 拖拽列表中被拖动的单个列表项
  • 目标对象: 拖拽列表中和“源对象”产生“相互作用”的列表项

整体的交互事件的设计思路如下:

(1) ondragstart

此时开始拖拽“源对象”的时机,在此事件回调函数中改变“源对象”的样式,设置拖拽的一些传递参数等初始值。

// 源对象开始拖拽
const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
  e.dataTransfer.effectAllowed = "move";
  setDragId(e.currentTarget.dataset.index); // 从 dataset 获取拖拽项的 id
};

(2) ondragover

正与拖拽中的“源对象”产生相互影响的目标对象,此时“源对象”处于“目标对象”的正上方,目标对象 100ms/次的频率调用“目标对象”的 ondragover 中声明的回调事件。

此时,我们会计算改变“源对象”和“目标对象”的位置。

// 源对象在目标对象上方时
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
  e.preventDefault(); // 允许放置,阻止默认事件
  const dropId = e.currentTarget.dataset.index;
  move(dragId, dropId); // 改变原列表数据
};

(3) ondrag

该事件作用于“源对象”,此时正处于拖拽过程中,此时可以改变源对象的 opacitydisplay(none)visiblity 样式属性,如果在 dragstart 事件改变,则会导致拖拽拷贝对象丢失。

// 源对象被拖拽过程中
const handleDrag = (e: React.DragEvent<HTMLDivElement>) => {
  e.currentTarget.style.opacity = "0";
};

(4) ondragend

在松手完成“源对象”的放置时,主动调用绑定在“源对象”身上的事件,此时恢复更改的样式。

// 源对象被放置完成时
const handleDragEnd = (e: React.DragEvent<HTMLDivElement>) => {
  e.currentTarget.style.opacity = "1";
};

2.2 实现效果

2.3 加点动画

上面的实现中效果还算可以,但是少了拖拽项的切换过程动画,直接在 dragover 事件中通过 move(dragId, dropId) 方法直接修改了原列表数据的排序,导致切换突变。

借助 animation 新增 CSS 帧动画:

@keyframes dropUp {
  100% {
    transform: translateY(5px);
  }
}

@keyframes dropDown {
  100% {
    transform: translateY(-5px);
  }
}

.drop-up{
  animation: dropUp 0.3s ease-in-out forwards;
}
.drop-down{
  animation: dropDown 0.3s ease-in-out forwards;
}

同样的在 dragOver 事件中处理,新增逻辑代码:

// 源对象在目标对象上方时
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
  ...
  // 设置动画
  const dropId = e.currentTarget.dataset.index;
  const dragIndex = findIndex(listData, (i) => i.id === dragId);
  const dropIndex = findIndex(listData, (i) => i.id === dropId);
  // 通过增加对应的 CSS class,实现视觉上的动画过渡
  e.currentTarget.classList.remove("drop-up", "drop-down");
  if (dragIndex < dropIndex) {
    e.currentTarget.classList.add("drop-down");
  } else if (dragIndex > dropIndex) {
    e.currentTarget.classList.add("drop-up");
  } 
  ...
};

增加了动画的效果:

看起来似乎好一点了,当然大家可以去扩充动画的效果,亦或者借助三方动画库。

三、已有拖拽库

目前主流的拖拽库有:

  • react-dnd: github.com/react-dnd/r…
  • react-beautiful-dnd: github.com/atlassian/r…
  • sortablejs: sortablejs.github.io/Sortable/
  • react-sortable-hoc: github.com/clauderic/r…

关于几者的差异,可以参阅:《关于react中使用拖拽插件的评测》

四、总结

由于低代码平台其实会有丰富的拖拽场景,从可扩展和兼容性上考虑,最终选择了 react-dnd 作为基础拖拽库,当然,在复杂的拖拽场景下,是需要自行扩展该拖拽库,上手难度相对会高一点,不过有了这些“拖拽知识”作为前置基础,那么扩展功能也就不是什么难事了。

相关推荐

首次被击毁!低调但先进的S-350,为何活得比韩国仿版差这么多?

【军武次位面】作者:乐乐2月18日,乌克兰军方网站发布了其前线炮兵侦察旅,在顿涅茨克地区攻击俄军S-350防空系统的现场视频。这也是这款地位独特的先进防空系统,第一次确认在战场上被摧毁——考虑到近三年...

Windows 10 LTSC 2021 vs 2019:哪个更适合你?

本内容来源于@什么值得买APP,观点仅代表作者本人|作者:闪电神龙微软近日发布了2024年11月份ISO镜像,包括Windows1124H2、Windows1022H2以及Server2025...

叛变投敌?俄军最先进隐身无人机S-70,在乌东上空被苏-57击落!

【军武次位面】作者:天狼2024年10月5日,乌克兰东部战区传来一条令人震惊的消息:一架俄罗斯最先进的隐身无人机S-70“猎人-B”在乌东上空被击落,令人意外的是,击落它的竟然是俄罗斯自己的战斗机。这...

自动驾驶车祸致1死1伤!特斯拉被判赔偿2.43亿美元

当地时间8月1日,美国佛罗里达州一个陪审团裁定,美国电动汽车制造商特斯拉应为2019年一辆配备自动驾驶系统的ModelS所致的致命车祸承担部分责任,并判令该公司向一名遇难女性的家属及一名伤者支付约2...

HP488DZ 无绳电锤钻(18V)牧田DTD156SFJ

HP488DZ无绳电锤钻(18V)牧田DTD156SFJHP488DZ无绳电锤钻(18V)HP488DZ特征HP488D是一款基于HP457D开发的无绳电锤,采用18V锂离子电池供电。其...

FJK-SJRFPZS防爆阀位行程开关级

解答常见误区在工业自动化和安全控制领域,FJK-SJRFPZS防爆阀位行程开关等级是一个关乎设备安全与运行效率的重要参数。许多用户在选择和应用这类开关时,可能对其等级分类存在一些误解。本文将通过通俗易...

China&#39;s PLA aerobatic team to perform in Thailand for 50th anniversary of bilateral diplomatic ties

TIANJIN,March2(Xinhua)--TheBayiAerobaticTeamoftheChinesePeople'sLiberationArmy(PLA)A...

JD.com Enters Travel and Hospitality With Supply Chain-Focused Strategy

TMTPOST--JD.comhasofficiallythrownitshatintoChina’sfiercelycompetitivetravelandhospita...

JD.com Drives Robotics Funding Frenzy With Investments in LimX Dynamics, Spirit AI, and EngineAI

TMTPOST--JD.comisdoublingdownonembodiedintelligence,catalyzinganewwaveoffundinginChi...

JD.com opens first JD Mall in Beijing, steps up offline retail push

bySongJiananJD.comhaslauncheditsfirstJDMALLinBeijing,expandingitsofflineretailfootpr...

JD.com&#39;s food delivery fleet tops 120,000 full-time riders

JD.com'sfull-timefooddeliveryfleethassurpassed120,000ridersandisexpectedtoexceed150,00...

China willing to share military equipment achievements with friendly countries: defense ministry

BEIJING,July8(Xinhua)--Chinahasalwaystakenaprudent,responsibleapproachtomilitaryexpor...

FJK-SJRFPZS防爆阀位行程开关等级

解答常见误区在工业自动化和安全控制领域,FJK-SJRFPZS防爆阀位行程开关等级是一个关乎设备安全与运行效率的重要参数。许多用户在选择和应用这类开关时,可能对其等级分类存在一些误解。本文将通过通俗易...

JD&#39;s 618 Festival Smashes Records as AI Powers Next-Gen Retail Engine

AsianFin–JD.com’s2025“618ShoppingFestival”wrappedupwithrecord-breakingmomentum,drivenby...

JD’s Food Delivery Blitz Shakes Meituan as Founder Wang Xing Vows to Win at All Costs

Credit:CFPAsianFin--JD.comInc.isturninguptheheatinChina'sfooddeliverywars,andfounder...