拖拽功能是目前网页上一种非常常见的功能,例如“登录弹窗”的拖拽。本文将使用 transform 来实现这一功能。
拖拽功能是目前网页上一种非常常见的功能,例如“登录弹窗”的拖拽。本文将使用 transform 来实现这一功能。
本文所涉及的案例可能会用到的一些必备的知识点: 1、JavaScript 中的 DOM2 级事件绑定 2、DOM 元素的相关位置属性 3、获取元素计算后的样式的相关 API 4、鼠标坐标的位置获取 5、ES6 的模板字符串语法 6、另外,为了能够顺利使用到 transform,读者可能还需要对 CSS3 的一些样式规则有些了解 因此,如果读者对以上这些知识点的了解还有欠缺,可以在在此之前捎带预习一下。
另外,本文配套的这个案例虽然采用的 webpack 构建运行,但核心代码与之无关。 如果读者不熟悉 webpack 的构建方式,也不用担心会看不懂代码。 文章内容难度:☆
拖拽的用户行为分析与原理解析如果读者熟悉了这个过程并也熟知了其中的原理,可以忽略此部分
拖拽的整个过程大致可以使用此图来描述:
元素的上边距离页面顶部的距离值(以下简称“上边距离”)从 Y(a)变成了 Y(b),“左边距离”从 X(a)变成了 X(b),也即完成了元素的移动。
在整个的变化过程中,有这样的一个隐藏信息:鼠标相对于元素的坐标(distX, distY)在整个移动过程中是没有发生变化的,用图上的关系即可以表示为:cX(b) - X(b) = cX(a) - X(a) = distX,cY(b) - Y(b) = cY(a) - Y(a) = distY。那么,在整个移动过程中,元素的“上边距离”= 鼠标移动中任意时刻的 Y 坐标 - distY,“左边距离”= 鼠标移动中任意时刻的 X 坐标 - distX。
那么怎么求出 distX 和 distY 呢?
我们在按下鼠标的那一刻,浏览器就会告诉我们鼠标的坐标(cX, cY),同时,我们也可以求出目标元素的“上边距离”(Y)和“左边距离”(X),这样 distX = cX - X,distY = cY - Y。
代码实现 初始化工作按照第一部分的分析,我们需要在按下鼠标的那一刻获取元素的“上边距离”和“左边距离”。
在传统的采用【position: absolute】定位的实现方式中,这一步我们可以通过 DOM 的【offsetTop】和【offsetLeft】来分别获取它们的值。但既然我们采用 transform 的方式来实现,就不再使用这两个属性了。我们首先设置元素的一些关键样式(部分 UI 样式已忽略)
.drag-box-translate3d{ transform: translate3d(0, 0, 1px); -moz-transform: translate3d(0, 0, 1px); -webkit-transform: translate3d(0, 0, 1px); will-change: transform; -moz-will-change: transform; -webkit-will-change: transform; }
值得注意的是,我们采用 translate3d 的属性值并设置了 z 轴的值为 1px,这样做的目的是强制浏览器使用 GPU 加速,从而获得更加流畅的体验。判断浏览器是否启用 GPU 加速,可以在定位到该元素之后,查看元素的计算后的样式:transform 的值是 matrix 还是 matrix3d,显示为后者时,即表示已开启 GPU 加速。
如果我们使用【position: absolute】来实现,那么初始位置的 X(a)和 Y(a)分别以 left 和 top 的值来分别指定,但采用 transform 来实现时,我们就可以使用 translateX 和 translateY 来分别指定 X(a)和 Y(a)。在上面的 CSS 设置中,X(a)和 Y(a)就被分别设置为 0 和 0。
绑定 mousedown 事件并获取 distX 和 distY我们准备一个独立的文件 drag.matrix.js 来编写我们的代码,用来实现模块化的编程。
我们首先定义一个模块内的全局变量用来承载需要绑定拖拽功能的元素。
/* 定义元素变量 */ let ELEMENT = null再定义一个模块内的全局对象用来存储计算用到的各个距离与尺寸数据。 /* 定义距离尺寸的存储池 */ let E_SIZER = {}
mousedown 事件的回调函数如下:
/** * mousedown 事件 * @param {MouseEvent} evte 鼠标事件对象 * @returns {undefined} **/ function bindMouseDownEvent( evte ){ // 阻止冒泡 evte.stopPropagation() // 阻止默认事件 evte.preventDefault() // 解析 matrix 的正则 let matrix3dReg1 = /^matrix3d\((?:[-\d.]+,\s*){12}([-\d.]+),\s*([-\d.]+)(?:,\s*[-\d.]+){2}\)/, matrixReg = /^matrix\((?:[-\d.]+,\s*){4}([-\d.]+),\s*([-\d.]+)\)$/ // 获取解析后的 transform 样式属性值(计算后的样式) let matrix3dSourceValue = util.getStyle( evte.target, 'transform' ) let matrix3dArrValue = matrix3dSourceValue.match( matrix3dReg1 ) || matrix3dSourceValue.match( matrixReg ) // 记录鼠标点击时的坐标 E_SIZER['clientX'] = evte.clientX E_SIZER['clientY'] = evte.clientY // 记录 matrix 解析后的 translateX & translateY 的值 E_SIZER['targetX'] = matrix3dArrValue[1] E_SIZER['targetY'] = matrix3dArrValue[2] // 计算坐标边界巨鹿 E_SIZER['distX'] = E_SIZER['clientX'] - E_SIZER['targetX'] E_SIZER['distY'] = E_SIZER['clientY'] - E_SIZER['targetY'] // 绑定 mousemove 事件 document.addEventListener('mousemove', bindMouseMoveEvent, false) }
被设置了 transform 属性值为 translate3d 的元素,浏览器会将这个样式的属性值计算为 matrix3d(...)的矩阵。
那么怎么获取到 translateX 和 translateY 的值呢?
这里提供两个正则,用来解析 matrix 或 matrix3d 的值并得到 translateX 和 translateY 的值:
/^matrix3d((?:[-\d.]+,\s){12}([-\d.]+),\s([-\d.]+)(?:,\s[-\d.]+){2})//^matrix((?:[-\d.]+,\s){4}([-\d.]+),\s*([-\d.]+))$/
这两个正则可以直接使用,例如:有了以上的分析和知识储备,我们就可以在鼠标按下的那一刻,获取到元素的初始 X(a)和 Y(a)的值了,也即上述的【bindMouseDownEvent】函数。
mouseover 事件的回调函数如下:
/** * mousemove 事件 * @param {MouseEvent} evte 鼠标事件对象 * @returns {undefined} **/ function bindMouseMoveEvent( evte ){ evte.stopPropagation() evte.preventDefault() let moveX = evte.clientX - E_SIZER['distX'] let moveY = evte.clientY - E_SIZER['distY'] // 写入 style ELEMENT.style.transform = ELEMENT.style.mozTransform = ELEMENT.style.webkitTransform = `translate3d(${moveX}px, ${moveY}px, 1px)` }
如果读者对本文第一部分的分析理解了的话,对于这一段函数应该会比较容易理解了。我们只要将鼠标在移动中的坐标值“转换”到元素的身上,即可完成对元素的实时移动了。
我们需要将【bindMouseMoveEvent】绑定到 document 上,因为在快速移动过程中,鼠标实际上会移出元素,如果直接将该回调函数绑定到元素上,可能会导致移动过程异常终止。
绑定 mouseup 解除功能mouseup 事件的回调函数如下:
/** * mouseup 事件 * @param {MouseEvent} evte 鼠标事件对象 * @returns {undefined} **/ function bindMouseUpEvent( evte ){ evte.stopPropagation() evte.preventDefault() document.removeEventListener('mousemove', bindMouseMoveEvent) }
我们需要将绑定到 document 上的 mousemove 回调事件函数移除。
初始化事件绑定 /** * 绑定事件 * @param {MouseEvent} evte 鼠标事件对象 * @returns {undefined} **/ function initBindEvent(){ // 绑定 mousedown 事件 ELEMENT.addEventListener('mousedown', bindMouseDownEvent, false) // 绑定 mouseup 事件 document.addEventListener('mouseup', bindMouseUpEvent, false) }
同样的,我们需要将 mouseup 事件的回调函数绑定到 document。
这样,我们我完成了拖拽功能的主体部分的开发工作,只要将其功能绑定到指定的元素上即可(可以访问文章尾部的 github 地址来体验)。
总结今天我们使用 transform 对传统的使用 position: absolute 的拖拽功能进行了升级,避免了在页面元素在移动过程中的不断的回流重绘,从而提升了功能性能。
在不考虑对老旧浏览器的兼容的情况下,可以尽量地使用 CSS 来获取更优的用户体验。
本例的 github:func-matirx-drag
期待点赞;不足之处,欢迎指出。
本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。
阅读全文: http://gitbook.cn/gitchat/activity/5d87546261fc7474eb91f1b2
您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。