一些与拖拽相关的技术点
采用 HTML5 dnd 拖拽事件是容易的,但有很多限制。
拖拽事件的选择
- HTML5 Drag and Drop API
- 利用 Mouse 事件来实现 Drag And Drop
采用 HTML5 dnd 拖拽事件是容易的,但有很多限制,例如触摸屏的支持、仅 XY 轴拖拽的交互处理、跨窗口的拖拽,而利用 Mouse 事件我们能够做的上限更高。
利用 Mouse 事件做 dnd 的一般实现:
- Draggable,在 mousedown 将元素设置为 position: absolute, 隐藏当前节点并生成一个 ghost 节点来辅助, 而在 mousemove 的时候将 ghost 节点的位置更新,可以使用 position 的位置计算也可以使用 transform 来变换位置。
- Droppable,标记可以放置元素的容器节点,在 mousemove 的时候根据当前的位置需要探测可以 droppable 的节点,可以采取 document.elementFromPoint(x, y) API 来找到当前位置的节点,而当前位置最上层的节点是 draggable 的节点,所以我们需要将 draggable 中的节点先 hidden 然后在探测 droppable 相关的元素再恢复 visible。
- Snappable, mousemove 的时候,计算容器内所有需被计算的节点(可以外部传入,默认为容器内所有可以拖拽的元素)的 offset, 然后根据节点的位置进行辅助线的渲染(分为水平和垂直的两个方向)
代码详情可以看 利用 mouse 实现 dnd 例子
Vue Component DSL
以 Vue-smooth-dnd 的组件为例, 分为 Container 组件, Container 组件包含 Draggable 组件,而 Draggable 包含的即是需要被拖拽的 DOM 元素。
<!-- 单个容器 -->
<Container
orientation="horizontal"
@drag-start="e => log('drag start', e)"
@drag-end="e => log('drag end', e)"
>
<Draggable v-for="card in cards" :key="card.id">
<div :class="card.props.className" :style="card.props.style">
<p>{{ card.data }}</p>
</div>
</Draggable>
</Container>
<!-- 容器嵌套 -->
<Container>
<Draggable v-for="parent in parents" :key="parent.id">
<div class="parent">
<p>{{ parent.name }}</p>
<Container>
<Draggable v-for="child in parent.children" :key="child.id">
<div class="child">
<p>{{ child.name }}</p>
</div>
</Draggable>
</Container>
</div>
</Draggable>
</Container>