可视化拖拽交互详解

915 阅读7分钟

简介

拖拽在可视化中是非常常见的交互,但是想把拖拽交互做好并不容易,既需要对 DOM 的拖拽机制有充分的了解,也需要可视化的绘图引擎提供良好的拖拽支持。我们常见的拖拽交互可以大体上分为三个场景:

  • 画布内部的拖拽
  • 画布外部元素拖拽到画布内
  • 画布内的图形拖拽到画布外

接下来我们会详细的讨论这三个场景,同时讲解绘图引擎(G 4.0)如何实现一套完备的拖拽模型,我们先从 DOM 的拖拽事件开始。

G 是 AntV 几个产品共同的底层 2D 渲染引擎,高效易用,专注于图形的渲染、拾取、事件以及动画机制,给上层 G2、F2、G6 提供统一的渲染机制。

DOM 的拖拽模型

DOM 的拖拽主要包含 7 个事件,参考 MDN drag event

  • dragstart
  • drag
  • dragend
  • dragenter
  • dragover
  • dragleave
  • drop

这 7 个事件可以分为两组,前 3 个事件 dragstart, drag, dragend 用于触发被的元素,后 4 个用于响应拖拽事件(在上面移动、释放),看下面的示例:

drag.gif

浏览器目前内置了拖拽的效果,首先要在 DOM 上声明 draggable="true" ,通过监听事件设置样式和最终改变 DOM 的容器。

<div class="dropzone">
    <div id="draggable" draggable="true" ondragstart="event.dataTransfer.setData('text/plain',null)">
        This div is draggable
    </div>
</div>
<div class="dropzone"></div>
<div class="dropzone"></div>
<div class="dropzone"></div>

注意:

  • draggable 的元素可以触发 dragstart, drag 和 dragend 事件,此时 mousemove, mouseenter,mouseleave 等事件不再触发。
  • dropzone 的元素可以响应 dragenter, dragleave, dragover 和 drop 事件
  • drop 事件在 dragend 之前触发,必须在 dragover 中 preventDefault 否则无法触发 drop 事件
  • 拖拽过程中离开浏览器会触发 dragleave,继续移动不会再触发 drag,释放时触发 dragend ,但是不触发 drop

画布内部的拖拽

在画布内部的拖拽操作是可视化场景中最主要的拖拽操作,大体上分为两大类:

  • 拖拽图形(改变位置、大小、容器等)
  • 拖拽画布

drag-shape.gif
drag-canvas.gif

highlight.gif
icon.gif

为了实现这些拖拽功能,绘图引擎不但要在图形上支持 DOM 的 7 种拖拽事件,还需要在画布上实现一些事件。图形对 7 种基础事件的支持:

  • dragstart: 在图形上触发拖拽
  • drag: 拖拽持续
  • dragend: 结束图形的拖拽
  • dragenter: 当拖拽图形进入另外一个图形时,在后者上触发
  • dragover: 当拖拽图形持续在另一个图形上移动时触发,在后者上触发
  • dragleave: 拖拽图形离开了另一个图形,在后者上触发
  • drop: 拖拽图形在另一个图形上释放,在后者上触发

drag-shape1.gif

注意:

  • 上面所有的事件都支持冒泡,会冒泡到画布(dragenter 和 dragleave 的触发类似于 mouseenter mouseleave)上。
  • 图形上只有设置了 draggable = true 时才允许拖拽。

如果想支持在空白画布处拖拽,画布本身也要对拖拽进行支持,主要是下面 4 个事件:

  • dragstart:拖拽画布空白处触发拖拽
  • drag: 拖拽画布
  • dragdend: 拖拽画布结束
  • drop: 拖拽图形时在画布空白处释放

drag-canvas.gif

注意:

  • 拖拽过程中需要处理拖拽到画布外面继续拖拽和释放鼠标的场景

G 4.0 的实现

G 4.0 为了实现上述效果在支持了上面图形的 7 种事件、画布的 4种事件外,还还增加了一些事件的触发时机:

  • 拖拽图形离开画布时,触发画布的 dragleave, 再次进入触发 dragenter
  • 拖拽图形离开画布时,继续拖拽会触发 drag 事件,释放时触发 drop 和 dragend 事件
  • 拖拽画布(不是图形),离开画布时不触发 dragleave,再次进入也不触发 dragenter,持续拖拽会触发 drag,释放会触发 dragend ,但是不会触发 drop 事件。

画布外部的元素拖拽到画布内

将画布外的元素拖拽到画布内,这种交互形式在编辑器场景下非常常见,不但要处理画布外元素的拖拽,也要在画布上监听各种事件,当拖拽的元素同时会同画布内的图形进行互动。

drag-dom.gif

如果绘图引擎的画布未考虑同画布外元素拖拽时的兼容,理想中我们要实现上面这个交互的步骤是:

  • 设置外部元素 draggable = true
  • 监听外部元素的 dragstart, drag 和 dragend 事件
  • 监听画布的 mouseenter, mouseleave 事件
  • 监听画布内图形的 mouseenter, mouseleave 事件,设置图形的 hove 样式
  • 当 dragend(或者 drop) 时添加图形

但是不幸的是上述步骤无法完成这个交互,原因在于:元素触发 drag 事件时无法再触发 mousemove, mouseenter 等鼠标移动相关的事件,这时候只能选择 dragenter, dragleave, dragover 事件,我们需要在 dragover 事件中查找对应的图形,模拟实现图形本身的 dragenter, dragleave。

G 4.0 的实现

我们在 G 4.0  重构时充分考虑画布内外元素的拖拽,对 DOM 的 drag 事件做了充分的兼容:

  • 拖拽元素进行画布时会触发画布的 dragenter, 继续续拖拽会触发画布的 dragover
  • 拖拽元素进入图形,会触发图形的 dragenter,继续拖拽触发图形的 dragover
  • 拖拽元素离开图形,会触发图形的 dragleave
  • 拖拽元素在图形上释放,会触发图形的 drop 事件,在画布上释放会触发画布的 drop 事件
  • 拖拽元素离开画布时会触发画布的 dragleave

画布内的图形拖拽到画布外

将画布中的图形可以拖拽到外部 DOM 元素上,可以实现很多精彩交互,例如:

  • 从画布中将图形移入垃圾筐
  • 将画布中的元素放入外部列表(作为筛选条件)
  • 将一个画布中的元素移入到另一个画布中

drag-out.gif

这个交互看起来简单,但是实现起来非常复杂:

  • 画布内部的图形拖拽到画布外时,需要显示对应的 HTML,回到画布时需要回复成图形
  • 画布外的容器需要能够监听拖拽图形的事件,监听 drop 时将 HTML 添加到容器内
  • 在容器之外的地方释放,需要清理显示的 HTML,回复拖拽的图形

G 4.0 中的实现

G 4.0 中已经实现了拖拽图形到画布外面的几个事件,可以完美的实现这个交互:

  • dragleave:拖拽离开画布时,图形隐藏,对应的 HTML 显示,同时定位到鼠标的位置
  • dragenter:再次拖拽进入画布时,图形显示,对应的 HTML 隐藏
  • drag:持续拖拽时,移动图形/HTML
  • dragend: 结束时,需要判定是否需要回滚
  • drop:释放时,如果在容器上方,则添加到容器内

总结

可视化中的拖拽交互不仅仅上面的三种(例如:框选、滑动等),每种交互在不同的场景下也有新的需求(拖拽节点到画布外部时,画布跟随移动),但是所有的拖拽交互都可以通过上面的三种交互衍生出来。
G 4.0 除了拖拽之外,其他的基础事件也进行了很多的重构,结合上层的 G2、G6 的交互重新设计了事件模型。
希望 AntV 能够给您带来更高质量、更容易实现的各种交互,拖拽的细化仅仅是一个开始!

AntV 官网antv.vision
Ggithub.com/antvis/g
G 是 AntV 几个产品共同的底层 2D 渲染引擎,高效易用,专注于图形的渲染、拾取、事件以及动画机制,给上层 G2、F2、G6 提供统一的渲染机制。