我们一直使用并且理所当然的常见手势是能够在屏幕上拖动元素。尽管这种拖拽手势有多么常见,但是没有很好的内置支持来使网络上的元素可拖动。如果我们希望超越鼠标并支持触摸之类的东西,那就更是如此!这就是本教程的用武之地。在接下来的几节中,我们将介绍一个纯粹的基于JavaScript的解决方案(也就是没有jQuery),它允许您将任何无聊的元素转换为可以在页面上拖动的元素。
继续~
1.例子
2.Drag Me Up,Scotty!
现在我们已经很好地了解了我们将要创建的内容,现在是时候实际创建它了。正如您将在稍后看到的那样,使任何元素可拖动的代码并不是火箭科学。困难的部分只是理解典型拖动操作中涉及的各个阶段。
3.拖动操作解剖
假设我们有一个想要拖动或可拖动的元素,
要启动拖动,我们首先按下元素,
按下这一操作通过鼠标或者我们的手指。为了简化术语,我将鼠标光标或手指(或手写笔)更一般地称为指针。
当我们用指针按下元素,我们移动指针到新的位置。
移动过程中经过的每个点,可动元素跟踪指针的位置并且移动。
一旦移动指针到最后的目的地,我们释放指针:
当释放按压时,我们的可拖动元素停止跟踪指针并且保持在最后的位置上。拖曳过程结束。
3.敲代码了!
我们有理由非常详细地研究拖动手势的每个微小步骤。您即将看到的代码将所有这些视觉效果和文本转换为我们的浏览器实际理解的内容。
我们的拖动示例的完整HTML,CSS和JavaScript如下所示:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport"
content="width=device-width,
initial-scale=1.0,
user-scalable=no" />
<title>Drag/Drop/Bounce</title>
<style>
#container {
width: 100%;
height: 400px;
background-color: #333;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
border-radius: 7px;
touch-action: none;
}
#item {
width: 100px;
height: 100px;
background-color: rgb(245, 230, 99);
border: 10px solid rgba(136, 136, 136, .5);
border-radius: 50%;
touch-action: none;
user-select: none;
}
#item:active {
background-color: rgba(168, 218, 220, 1.00);
}
#item:hover {
cursor: pointer;
border-width: 20px;
}
</style>
</head>
<body>
<div id="outerContainer">
<div id="container">
<div id="item">
</div>
</div>
</div>
<script>
var dragItem = document.querySelector("#item");
var container = document.querySelector("#container");
var active = false;
var currentX;
var currentY;
var initialX;
var initialY;
var xOffset = 0;
var yOffset = 0;
container.addEventListener("touchstart", dragStart, false);
container.addEventListener("touchend", dragEnd, false);
container.addEventListener("touchmove", drag, false);
container.addEventListener("mousedown", dragStart, false);
container.addEventListener("mouseup", dragEnd, false);
container.addEventListener("mousemove", drag, false);
function dragStart(e) {
if (e.type === "touchstart") {
initialX = e.touches[0].clientX - xOffset;
initialY = e.touches[0].clientY - yOffset;
} else {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
}
if (e.target === dragItem) {
active = true;
}
}
function dragEnd(e) {
initialX = currentX;
initialY = currentY;
active = false;
}
function drag(e) {
if (active) {
e.preventDefault();
if (e.type === "touchmove") {
currentX = e.touches[0].clientX - initialX;
currentY = e.touches[0].clientY - initialY;
} else {
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
}
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, dragItem);
}
}
function setTranslate(xPos, yPos, el) {
el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)";
}
</script>
</body>
</html>
如果您将所有这些内容粘贴到HTML文档中,您将在计算机上看到自己的此示例版本。无论如何,花点时间浏览一下你看到的一切。HTML和CSS应该看起来很简单。它们只是帮助我们定义可拖动元素及其所在的容器。脚本标记内的JavaScript是令人兴奋的地方,所以让我们更仔细地讨论它。
(1)变量声明
在最顶层,我们声明了一些我们将在整个地方使用的变量
var dragItem = document.querySelector("#item");
var container = document.querySelector("#container");
var active = false;
var currentX;
var currentY;
var initialX;
var initialY;
var xOffset = 0;
var yOffset = 0;
dragItem和容器变量存储对其各自DOM元素的引用。剩下的变量看起来有点神秘。不要过分担心他们现在所做的事情。我们将在使用它们时讲解到。
(2)监听事件
接下来是我们的事件相关代码,用于处理拖动手势的各种状态:
container.addEventListener("touchstart",dragStart,false);
container.addEventListener("touchend", dragEnd, false);
container.addEventListener("touchmove", drag, false);
container.addEventListener("mousedown", dragStart, false);
container.addEventListener("mouseup", dragEnd, false);
container.addEventListener("mousemove", drag, false);
第一批事件侦听器处理触摸事件,第二批事件侦听器处理鼠标事件。触摸和鼠标事件之间的配对如下:touchstart to mousedown,touchend to mouseup,touchmove to mousemove.考虑到这些事件对是相似的,我们将它们分配给同一个事件处理程序。dragStart,dragEnd和drag事件处理程序将负责在鼠标和触摸场景中拖动元素。
现在,您可能一直听说触摸事件和鼠标事件非常相似,您不必像我们在这里那样分别明确地分别监听它们。在90%的情况下,以上说法是正确的。您只能通过监听鼠标事件来伪装触摸行为。其余10%用于更高级的情况,例如我们的拖动操作。您可以使用的属性有所不同,因此我们无法跨鼠标和触摸重复使用相同的代码。当我们查看第一个事件处理程序dragStart函数时,您可以看到这一点。
(3)初始化拖曳
dragStart函数是使我们的拖动手势实际工作的网关,它看起来如下:
function dragStart(e) {
if (e.type === "touchstart") {
initialX = e.touches[0].clientX - xOffset;
initialY = e.touches[0].clientY - yOffset;
} else {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
}
if (e.target === dragItem) {
active = true;
}
}
当mousedown / touchstart事件被监听到时,将调用此事件处理函数。我们要做的第一件事就是设置指针的初始位置(initialX和initialY),我们通过检测事件是否基于触摸来实现。如果事件是基于触摸的事件(通过检查touchstart),我们获取位置的方式是通过我们的事件参数的touches [0]
对象访问clientX和clientY属性。对于基于鼠标的事件,我们的路径有点不同。我们可以通过直接在事件参数对象上访问clientX
和clientY
属性来获取初始位置。能够做到这一切的统一方式会很棒,但唉,目前还没有…
还有一件关于位置的事要说明。计算涉及当前位置以及减去xOffset和yOffset的值。**xOffset和yOffset最初都设置为0,但后续拖动操作不会出现这种情况。**我们将在短时间内了解所有定位logc的工作原理。
我们做的最后一件事是检查我们点击的元素是否是我们想要拖动的元素:
if (e.target === dragItem) {
active = true;
}
我们为什么这样做?如果你回到我们的事件监听器代码,请注意我们正在监听容器上的各种鼠标和触摸事件,而不是我们正在拖动的元素.
这样做有几个原因。主要原因是当您**快速拖动元素时,指针将离开您拖动元素的命中区域。**当发生这种情况时,你的拖曳会突然停止。那真是太尴尬了。我们可以通过监听被拖动元素的容器上的事件来避免这种情况。无论你多快地拖动你的元素,你几乎总是在容器的范围内。这可以确保您的事件仍然触发,并且您的拖动元素有机会赶上指针所在的位置。
结束这段代码,一旦我们将此事件识别为与拖动元素相关联,我们将活动变量设置为true:
if (e.target === dragItem) {
active = true;
}
唷。谁知道那三行代码需要这么多额外的解释?
(4)拖曳
当mousemove或者touchmove被触发时,drag函数被调用:
function drag(e) {
if (active) {
e.preventDefault();
if (e.type === "touchmove") {
currentX = e.touches[0].clientX - initialX;
currentY = e.touches[0].clientY - initialY;
} else {
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
}
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, dragItem);
}
}
我们要做的第一件事是通过检查我们在dragStart函数中设置的active变量的值来检查拖动是否处于活动状态。在那之后,一帆风顺。我们将currentX和currentY的值设置为当前指针位置,当前指针位置是根据我们之前设置的initialX和initialY调整而来的。还记得我们之前看到的xOffset和yOffset变量吗?他们现在被设置为当前位置。这允许将来的拖动操作从当前位置停止的位置拾取。
这个函数最后做的是设置拖动元素的新位置:
setTranslate(currentX, currentY, dragItem);
关于这个函数:
function setTranslate(xPos, yPos, el) {
el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)";
}
此函数只是设置translate3d变换的快捷方式,我们提供的是我们要设置其位置的元素以及设置转换的水平/垂直位置。您可以了解更多关于为什么translate3d可能是我们在本文中有效地设置位置的最佳方法之一。
关于设置拖动的元素位置
到目前为止,我们已经发现很多人对元素的位置如何设置感到困惑。我们有setTranslate函数和变量initialX,initialY,xOffset,yOffset,clientX,clientY以及它们都被使用的奇怪方式。设置一些东西的位置不是那么难,对吧?嗯,通常就是这种情况,但当你抛出鼠标和触摸,浏览器怪癖,四舍五入等等时,你最终会得到一些比我们这里更复杂的东西。需要注意的主要是
clientX
和clientY
返回容器元素内指针的绝对位置。offset
变量说明指针相对于拖动元素位置的位置。这样做是为了确保我们从正确的位置拖动,即使您单击/点击元素的边缘也是如此。当我们启动拖动时,我们不希望不自然地捕捉指针的位置。
(5)结束拖曳
我们代码的最后一部分是当mouseup和touchend事件触发时调用的事件处理程序,表示拖动结束:
function dragEnd(e) {
initialX = currentX;
initialY = currentY;
active = false;
}
我们将下一个拖动操作的初始位置设置为当前位置。由于我们的拖动现已完成,因此我们将active
变量设置为false。
4.结论
拖动元素应该更容易,但事实并非如此。实际代码本身并不存在困难。**难点是一个非常传统的HTML / CSS / JS问题,当你开始扩大范围超出传统的鼠标/键盘/桌面世界时,事情变得更加复杂。**虽然支持触控的设备已经存在很长时间了,但是所有浏览器对它们的开发支持仍在进行中。没关系。如果一切都太容易了,我们都会失业