拖拽功能是目前网页上一种非常常见的功能,例如“登录弹窗”的拖拽。本文将使用transform来实现这一功能。

一、拖拽的用户行为分析与原理解析

二、代码实现

三、总结

本文所涉及的案例可能会用到的一些必备的知识点:

1、JavaScript中的DOM2级事件绑定

2、正则的编写与匹配

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。

二、代码实现

1.初始化工作

按照第一部分的分析,我们需要在按下鼠标的那一刻获取元素的“上边距离”和“左边距离”。在传统的采用【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。

我们需要在代码中获取该元素的transform的计算后的值,代码如下:

export function getStyle(el,

attr){

if( typeof window.getComputedStyle !== 'undefined' ){

return window.getComputedStyle(el, null)[attr]

}else if(typeof el.currentStyle !== 'undefiend' ){

return el.currentStyle[attr]

}

return ''

}复制代码

2.绑定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 matrix3dReg = /^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'

)

// 使用正则解析matrix

let matrix3dArrValue =

matrix3dSourceValue.match( matrix3dReg ) || 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】函数。

3.绑定mousemove事件移动元素

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上,因为在快速移动过程中,鼠标实际上会移出元素,如果直接将该回调函数绑定到元素上,可能会导致移动过程异常终止。

4.绑定mouseup解除功能

mouseup事件的回调函数如下:/**

* mouseup事件

* @param {MouseEvent} evte 鼠标事件对象

* @returns {undefined}

**/

function bindMouseUpEvent(evte){

evte.stopPropagation()

evte.preventDefault()

document.removeEventListener('mousemove', bindMouseMoveEvent)

} 复制代码

我们需要将绑定到document上的mousemove回调事件函数移除。

5.初始化事件绑定/**

* 绑定事件

* @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来获取更优的用户体验。

期待点赞;不足之处,欢迎指出。

2019-09-20

知乎专栏:前端小知识

css元素可拖动,使用css-transform实现更好的拖拽功能相关推荐

  1. draggable禁止拖动_通过 JS 实现简单的拖拽功能并且可以在特定元素上禁止拖拽...

    前言 关于讲解 JS 的拖拽功能的文章数不胜数,我确实没有必要大费周章再写一篇重复的文章来吸引眼球.本文的重点是讲解如何在某些特定的元素上禁止拖拽.这是我在编写插件时遇到的问题,其实很多插件的拖拽功能 ...

  2. php 元素 拖拉,Draggable Elements 元素拖拽功能实现代码_javascript技巧

    当然我们可以研究js库的源码, 也可以自己去发明轮子试试看, 其过程还是挺有趣的...下面我就来实现下页面元素的拖拽功能 现在就开始着手实现, 让我们从最顶层的方法讲起, 它用于初始化一个drag o ...

  3. 拖拽功能之水平拖动图片

    1实现技术 项目运行环境:ant pro 拖拽技术插件: react-sortable-hoc        实现拖拽功能 array-move                   实现拖拽过程中数据 ...

  4. swift 拖动按钮_Swift下使用UICollectionView 实现长按拖拽功能

    导读 简单用Swift写了一个collectionview的拖拽点击排序效果; 拖拽排序是新闻类的App可以说是必有的交互设计,如今日头条,网易新闻等. 效果 主要代码 手势长按移动 1.给Colle ...

  5. html5播放器禁止拖拽功能实例(教学内容禁止拖动观看)

    html5播放器禁止拖拽功能实例(常用于场景:企业培训.在线教学内容禁止学员拖动视频进行观看) 实例1:参数开启后,视频教学内容或视频课件将不允许拖动进度条. <div id="pla ...

  6. vue拖动缩放组件(vue-drag-zoom) 增加禁止缩放拖拽功能

    前段时间写了一个类似于百度ICOR,可拖拽缩放图片并在图片上框选文字的功能,这里的拖拽缩放功能就用到了vue-drag-zoom组件,组件是从npm下载的VUE2代码,放在VUE3+vite项目里面也 ...

  7. js分隔条实现拖拽功能(支持左右拖动)

    这次记录就不贴代码了,只是记录一下原理和思路,因为代码封装的太严格,所以不好拿出来. lz的页面是左右各一个分隔条实现拖拽的,左边是框架自带一个功能没问题,右边是lz模仿做的,结果样子是做出来了,就是 ...

  8. 拖动小游戏html,Cocos Creator 入门篇-拖拽小游戏(一)

    前言 Cocos Creator的官方文档还是非常友好的,有中英文两个版本. [强烈建议] 初学者先把官方文档看一遍.里面还包含了很多demo. 今天主要先带大家简单熟悉一下Cocos Creator ...

  9. antd modal 拖动_antd的Modal可移动(可拖拽)

    通过第三方插件实现 使用方法: npm install dragm -S 2.安装后运行如果出现缺少.less,则在命令行>yarn install(或直接运行 yarn),然后再运行 3.新建 ...

最新文章

  1. RAC RMAN 通道配置 RMAN-12001 RMAN-12001 RMAN-10008 RMAN-10003 ORA-01017 错误
  2. wxWidgets 示例展示了 wxSecretStore 类的使用
  3. 查看ssh端口号_萌新云服务器折腾记-SSH配置
  4. scala 函数中嵌套函数_Scala中的嵌套函数 用法和示例
  5. Java比较两个实体属性值是否相同,将不同的属性输出
  6. VS 2013 所有产品密钥
  7. 为计算机快捷方式是什么原因,为什么我的电脑界面上的东西都变成快捷方式??...
  8. 2015年 教师全员远程培训挂机 教师远程全员培训挂机 教师继续教育挂机 挂机软件 全国通用版...
  9. Qt学习(一)ui界面的设计
  10. 微信小程序入门12-微信小程序开发设置中服务器域名和业务域名
  11. 2022年8种高级威胁预测出炉、FBI就零日漏洞发出警报|11月22日全球网络安全热点
  12. 400企业智能服务器,全球领先的企业级服务器、存储、融合系统及解决方案-H3C与HPE...
  13. 什么是服务器、ip以及域名以及他们之间的联系
  14. 绩效/加薪/年终奖,虐你如初恋
  15. (完全解决)argparse中dest是什么意思
  16. 小朋友学数学(22):三角函数
  17. 计算机网络技术课程答案网课,《计算机网络技术》大学生网课答案.docx
  18. ticklength
  19. 华为荣耀总裁刘江峰正式宣告离职
  20. 项目建设方案的基本元素

热门文章

  1. 视角不平衡立体匹配研究
  2. 爬虫笔记8实例淘宝商品比价爬虫
  3. 深度学习在遥感图像目标检测中的应用综述
  4. 【Java开发】生成二维码
  5. 长安大学第三届ACM-ICPC程序设计竞赛 L题
  6. 中的挂起是什么意思_书房装饰挂什么画好 书法字画给你想要的诗意生活
  7. mybatis多对一处理两种处理方式
  8. Ⅶ:教你一招利用zookeeper作为服务的配置中心
  9. linux的基本命令--常用
  10. 《计算机组成原理》实验报告——TEC-2实验系统——微程序控制器实验