页面元素拖拽是一种非常实用的前端效果,基于元素拖拽可以实现很多不同的功能,增加客户端许多操作的便捷性,大大提高用户体验。日常生活中大家多多少少都见过这种效果,所以就不废话了,直接开干吧。
预期目标
实现一个 Class 类,通过该 Class,可以将任意 DOM 元素(比如 div)一键变为可拖拽状态,也可以恢复成原来的状态,例如这样:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> #box1 { height: 50px; width: 50px; background-color: cadetblue; } #box2 { height: 50px; width: 50px; background-color: blue; } #box3 { height: 50px; width: 50px; background-color: red; } </style> </head> <body> <div id="box1">1</div> <a id="box2">2</a> <div id="box3">3</div> </body> <script type="module"> // 我们要完成的目标 Class import DragElement from './DragElement.js' // 使 3 个元素可拖拽 let box1 = new DragElement(document.querySelector("#box1")) let box2 = new DragElement(document.querySelector("#box2")) let box3 = new DragElement(document.querySelector("#box3")) // box2 解除拖拽效果,恢复为原来的样子 // box2.dragRelease() </script> </html>
原本的样子
随意拖放
一、算法思路
1.1 拖拽的行为描述
我们先思考如何描述拖拽这一行为。我的思路是这样的:
- 先对拖拽这一行为进行定义:在指定的元素上,若保持鼠标按下状态,则该元素将会跟随鼠标移动。当鼠标松开,该元素将不再跟随鼠标移动。如果是移动端的话,鼠标的角色改为触摸(touch)即可。
根据定义,我们可以确定几个关键信息:
- 鼠标移动,是拖拽算法本身的作用范围。
- 鼠标按下,开启拖拽
- 鼠标松开,关闭拖拽
可以看到,完整的拖拽功能分为 3 个部分,分别是开启、运行与关闭。分别对应鼠标的按下、运行、松开事件。 因此我们至少需要设计相应的 3 个函数,作为事件的回调。在这里我分别命名为 dragStart()、dragMoving()、dragEnd()。
这里就出现了第一个重点:如何描述拖拽功能的状态变化?
显然,鼠标的按下与松开,将会决定DOM 元素是否能够被拖拽,这是一种 “状态” 的变化。这种状态的变化,在编码上,可以通过一个变量来实现,也可以通过不断地添加 or 移除回调函数来实现。如果通过变量的话,在鼠标没有按下时,鼠标移动事件也会触发进行状态判断,这其实是没有必要的,因此方案上我们选择后者,鼠标按下与松开时,分别添加和移除实现拖拽的函数。
以上是拖拽本身的行为,此外,由于我们需要 DOM 元素能够在原本的状态和可拖拽状态之间进行转换,因此我们还需要 2 个函数,一个用于将 DOM 元素变为可拖拽状态,另一个用于卸载这些状态。前者我称为 dragActive(),后者我称为 dragRelease()。它们做的事情,就是添加和解除事件监听。
现在第一个问题解决了,我们来解决第二个问题,那就是:拖拽函数怎么实现?
1.2 拖拽的实现
首先看核心的,拖拽本身应该怎么计算,如何让元素跟着鼠标走。
同样的,我们继续想象实际的场景。鼠标按下时,我们假设鼠标的坐标处于(x0, y0) 点,鼠标移动,假设移动到了(x1, y1) 点。那么该元素,相对自身初始位置便移动了(x1-x0, y1-y0) 的距离。这种相对于自身移动的,在 CSS 上可以通过相对定位,也可以通过 transform: translate 或 translate3d 来实现,由于定位在布局中很常用,我们也不知道指定的 DOM 元素到底是什么样式,为了尽量不影响原来的布局,所以我们采用 transform。
再回到具体计算上,鼠标的位置 x 和 y,可以通过事件回调函数传入的参数 event 得到,在 PC 端是 event.clientX 和 event.clientY,移动端是 event.changedTouches[0].pageX 和 event.changedTouches[0].pageY。而 mousemove 事件是连续触发的,我们的拖动也要让元素跟着鼠标连续运动,因此需要不停更新 (x0, y0),(x1, y1) 的值,在每个细小的运动中都进行差值计算,就像微积分一样。为了方便记录和更新,我们不妨把拖动中需要的变量用一个对象表示,称为 dragInfo,挂载到 document 元素上,这样在不同的函数、对象之间都可以访问。
class DragElement { constructor(element) { this.element = element document.dragInfo = { element: this.element, x0: 0, y0: 0, x1: 0, y1: 0 } } }
element 表示拖拽的元素,x 和 y 分别为计算所需的变量。
获取鼠标位置的函数:
updateDragPosition = (event) => { return { x: event.clientX || (event.changedTouches "text-align: center">获取鼠标位置的函数写完后,就可以写拖拽的函数了:
dragMoving = (event) => { document.dragInfo.x1 = this.updateDragPosition(event).x - document.dragInfo.x0 + document.dragInfo.x1 document.dragInfo.y1 = this.updateDragPosition(event).y - document.dragInfo.y0 + document.dragInfo.y1 document.dragInfo.x0 = this.updateDragPosition(event).x document.dragInfo.y0 = this.updateDragPosition(event).y document.dragInfo.element.style.transform = 'translate3d(' + document.dragInfo.x1 + 'px, ' + document.dragInfo.y1 + 'px, 0)'; }但此时问题就来了,由于 document 上只有一个 dragInfo,不同的组件之间坐标冲突如何解决?其实这个简单,只需要在 this.element 上添加一个对象记录每次拖拽后的位置即可,每当点击一个拖拽元素时,就将该元素的信息注入 document.dragInfo。
this.element.dragPostion = { x: 0, y: 0 }综上,我们已经解决了最核心的流程描述与算法部分,接下来只要编码就可以了。
二、编码实现
请根据之前说的思路,自行阅读代码,整体逻辑还是非常清晰的,如果有一些细节不懂,可以在评论区提出,或者我有空了再补充。
class DragElement { constructor(element) { this.element = element document.dragInfo = { element: this.element, x0: 0, y0: 0, x1: 0, y1: 0 } document.updateDragPosition = this.updateDragPosition this.dragActive() } // 更新鼠标位置 updateDragPosition = (event) => { return { x: event.clientX || (event.changedTouches "block" this.element.addEventListener('mousedown', this.dragStart, false) this.element.addEventListener('touchstart', this.dragStart, false) this.element.addEventListener('mouseup', this.dragEnd, false) // 释放 this.element.addEventListener('touchend', this.dragEnd, false) this.element.addEventListener('touchcancel', this.dragEnd, false) // 为该元素添加一个对象,保存当前位置 this.element.dragPostion = { x: 0, y: 0 } } // 释放配置 dragRelease = () => { this.element.removeEventListener('mousedown', this.dragStart) this.element.removeEventListener('touchstart', this.dragStart) this.element.removeEventListener('mouseup', this.dragEnd) // 释放 this.element.removeEventListener('touchend', this.dragEnd) this.element.removeEventListener('touchcancel', this.dragEnd) this.element.style.display = "" return this.element } // 点击捕获拖拽元素,初始化相应信息 dragStart = (event) => { document.dragInfo.element = this.element document.dragInfo.x0 = this.updateDragPosition(event).x document.dragInfo.y0 = this.updateDragPosition(event).y document.dragInfo.x1 = this.element.dragPostion.x document.dragInfo.y1 = this.element.dragPostion.y // 屏蔽默认行为 event.preventDefault(); // mousemove 绑定在 document 上,防止鼠标过快可能导致的元素跟丢 document.addEventListener('mousemove', this.dragMoving, false) document.addEventListener('touchmove', this.dragMoving, false) } // 实时计算、更新相对位置变化 dragMoving = (event) => { document.dragInfo.x1 = this.updateDragPosition(event).x - document.dragInfo.x0 + document.dragInfo.x1 document.dragInfo.y1 = this.updateDragPosition(event).y - document.dragInfo.y0 + document.dragInfo.y1 document.dragInfo.x0 = this.updateDragPosition(event).x document.dragInfo.y0 = this.updateDragPosition(event).y document.dragInfo.element.style.transform = 'translate3d(' + document.dragInfo.x1 + 'px, ' + document.dragInfo.y1 + 'px, 0)'; } // 关闭拖拽 dragEnd = () => { // 保存当前位置 this.element.dragPostion.x = document.dragInfo.x1 this.element.dragPostion.y = document.dragInfo.y1 // 解绑 document.removeEventListener('touchmove', this.dragMoving) document.removeEventListener('mousemove', this.dragMoving) } } export default DragElement
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
稳了!魔兽国服回归的3条重磅消息!官宣时间再确认!
昨天有一位朋友在大神群里分享,自己亚服账号被封号之后居然弹出了国服的封号信息对话框。
这里面让他访问的是一个国服的战网网址,com.cn和后面的zh都非常明白地表明这就是国服战网。
而他在复制这个网址并且进行登录之后,确实是网易的网址,也就是我们熟悉的停服之后国服发布的暴雪游戏产品运营到期开放退款的说明。这是一件比较奇怪的事情,因为以前都没有出现这样的情况,现在突然提示跳转到国服战网的网址,是不是说明了简体中文客户端已经开始进行更新了呢?
更新日志
- 小骆驼-《草原狼2(蓝光CD)》[原抓WAV+CUE]
- 群星《欢迎来到我身边 电影原声专辑》[320K/MP3][105.02MB]
- 群星《欢迎来到我身边 电影原声专辑》[FLAC/分轨][480.9MB]
- 雷婷《梦里蓝天HQⅡ》 2023头版限量编号低速原抓[WAV+CUE][463M]
- 群星《2024好听新歌42》AI调整音效【WAV分轨】
- 王思雨-《思念陪着鸿雁飞》WAV
- 王思雨《喜马拉雅HQ》头版限量编号[WAV+CUE]
- 李健《无时无刻》[WAV+CUE][590M]
- 陈奕迅《酝酿》[WAV分轨][502M]
- 卓依婷《化蝶》2CD[WAV+CUE][1.1G]
- 群星《吉他王(黑胶CD)》[WAV+CUE]
- 齐秦《穿乐(穿越)》[WAV+CUE]
- 发烧珍品《数位CD音响测试-动向效果(九)》【WAV+CUE】
- 邝美云《邝美云精装歌集》[DSF][1.6G]
- 吕方《爱一回伤一回》[WAV+CUE][454M]