那些被忽略但很好用的 Web API / Drag & Drop

就喜欢拖拖拉拉

现在使用手机、平板来浏览网站的机会比起电脑来说,实在是多太多了,所以其实 Web API 也不断针对这样的趋势在靠拢,像是我们前期介绍的 Battery API 就是一个案例。

使用者在长期使用行动装置的情况下,已经习惯了系统 OS 或 APP 的操作,所以对於「拖曳物件」这个手势动作已经是直觉反应了,而在网站开发中,Drag & Drop API 就是协助我们完成「拖曳」功能的好帮手。


Drag & Drop

Drag & Drop 其实是「拖」和「放」两个动作,也就是除了「拖曳」元素外,也可以将其「放置」在特定位置。这也代表说整个过程中会与使用者交互的元素不只有一个,会发生的事件也不只有一个,在拖放的过程中至少会有两个角色,各自也都有对应的事件:

  • Drag Source: 被使用者点击不放并「拖曳」的目标元素。
事件 说明
dragstart 当使用者开始拖曳时触发(滑鼠开始移动时)
drag 开始拖曳到结束拖曳前都会不断触发(约几百毫秒触发一次)
dragend 当使用者结束拖曳时触发(滑鼠按键放开时)
  • Drop Location: 一个可以「放置」 drag source 的元素。
事件 说明
dragenter 当使用者拖曳期间进入元素时触发
dragover 当使用者拖曳期间经过元素时触发(约几百毫秒触发一次)
dragleave 当使用者拖曳期间离开元素时触发
drop 当使用者将拖曳的目标放置在元素时触发

 

# Attribute:draggable

通常来说,按住元素不放也是没办法拖曳的,若要使其可以拖曳必须要在 HTML 标签上加入 draggable 属性:

<div class="box" draggable="true"></div>

Drop Location 则不用额外添加属性,任何元素都可以是 Drop Location。

 

# 交互事件

由於前面的表格已经大致说明了各个事件的触发时机了,应该也都不难离解,所以我们就直接试试看能够利用事件做些什麽效果,以及 Drag Source 和 Drop Location 之间该怎麽互动。

- 样式改变

我们可以利用 dragstartdragend 来改变 Drag Source 的样式,让使用者能更清楚目前被拖曳的元素是哪一个,以及利用 dragenterdragleave 来改变 Drop Location 的样式:

<div class="container">
  <div class="box" draggable="true"></div>
  <div class="box" draggable="true"></div>
  <div class="box" draggable="true"></div>
</div>
<div class="container"></div>
.container {
  background: white;
}
.container.hover {
  background: aliceBlue;
}
.box {
  background: lightblue;
  cursor: grab;
}
.box.dragging {
  background: lightgreen;
  cursor: grabbing;
}
const boxes = document.querySelectorAll(".box");
boxes.forEach((box) => {
  box.addEventListener("dragstart", (e) => {
    e.target.classList.add("dragging");
  });
  box.addEventListener("dragend", (e) => {
    e.target.classList.remove("dragging");
  });
});

const containers = document.querySelectorAll(".container");
containers.forEach((container) => {
  container.addEventListener("dragenter", (e) => {
    e.target.classList.add("hover");
  });
  container.addEventListener("dragleave", (e) => {
    e.target.classList.remove("hover");
  });
});

- 鼠标位置

由於 dragdragover 都是不断触发的事件,所以我们可以用来追踪使用者游标的位置:

const boxes = document.querySelectorAll(".box");
boxes.forEach((box) => {
  box.addEventListener("drag", (e) => {
    console.log(`滑鼠在视窗中的座标: ${e.clientX} / ${e.clientY}`);
  });
});

const containers = document.querySelectorAll(".container");
containers.forEach((container) => {
  container.addEventListener("dragover", (e) => {
    console.log(`滑鼠在 Drop Location 中的座标: ${e.offsetX} / ${e.offsetY}`);
  });
});

- 移动元素

最後只要搭配上 drop 事件,我们就可以达成移动元素的效果,不过有个要注意的事情是 dragoverdrop 会有执行上的冲突,所以如果要让 drop 的 Callback 能够顺利触发,必须要在 dragover 中将预设行为给取消掉:

let source = null;

const boxes = document.querySelectorAll(".box");
boxes.forEach((box) => {
  box.addEventListener("dragstart", (e) => {
    source = e.target;
  box.addEventListener("dragend", (e) => {
    source = null;
  });
});

const containers = document.querySelectorAll(".container");
containers.forEach((container) => {
  container.addEventListener("dragover", (e) => {
    e.preventDefault();
  });
  container.addEventListener("drop", (e) => {
    e.target.appendChild(source)
    e.target.classList.remove("hover");
  });
});

 

看过以上示范後是不是觉得 Drag & Drop API 还蛮简单的呢?只要先设定好需要拖曳的元素後,透过几个事件的交互就可以达到这样的效果。不过其实很多 API 都是这样的,使用起来都很容易,但实际上要应用时就容易逻辑卡住,所以为了让各位能对 Drag & Drop 有更具体的印象,明天就来实际写一个拖拉的 ToDo List 吧!


<<:  Day 26 - [Android APP] 04-MVVM - Repository与API串接

>>:  [第二十六天]从0开始的UnityAR手机游戏开发-输出64位元的APP

[Day18] 箭头函式

Arrow Function 这个从 ES6 开始新增的一种写法,叫做 Arrow Function...

第36天~就是自己KEY

这篇的上一篇:https://ithelp.ithome.com.tw/articles/10283...

虹语岚访仲夏夜-23(专业的小四篇)

万里无云时 总觉得喘不过气 要问为什麽 一定是 无云天空下 只有我问我    该往那去 寂静夜深时...

Day13 Sass篇-什麽是变数?

大家好,我是乌木白,今天要介绍的是 Sass 里的变数! 变数是什麽? 变数按照字面上来看,就是一...

DAY22-导览设计之Sidebar

前言: 今天我们要来完成前面提到的Sidebar,我会从Navbar接着开始接着讲,那就让我们开始...