css元素可拖动,使用css-transform实现更好的拖拽功能
拖拽功能是目前網(wǎng)頁(yè)上一種非常常見(jiàn)的功能,例如“登錄彈窗”的拖拽。本文將使用transform來(lái)實(shí)現(xiàn)這一功能。
一、拖拽的用戶行為分析與原理解析
二、代碼實(shí)現(xiàn)
三、總結(jié)
本文所涉及的案例可能會(huì)用到的一些必備的知識(shí)點(diǎn):
1、JavaScript中的DOM2級(jí)事件綁定
2、正則的編寫與匹配
3、獲取元素計(jì)算后的樣式的相關(guān)API
4、鼠標(biāo)坐標(biāo)的位置獲取
5、ES6的模板字符串語(yǔ)法
6、另外,為了能夠順利使用到transform,讀者可能還需要對(duì)CSS3的一些樣式規(guī)則有些了解
因此,如果讀者對(duì)以上這些知識(shí)點(diǎn)的了解還有欠缺,可以在在此之前捎帶預(yù)習(xí)一下。
另外,本文配套的這個(gè)案例雖然采用的webpack構(gòu)建運(yùn)行,但核心代碼與之無(wú)關(guān)。
如果讀者不熟悉webpack的構(gòu)建方式,也不用擔(dān)心會(huì)看不懂代碼。
文章內(nèi)容難度:☆復(fù)制代碼
一、拖拽的用戶行為分析與原理解析如果讀者熟悉了這個(gè)過(guò)程并也熟知了其中的原理,可以忽略此部分
拖拽的整個(gè)過(guò)程大致可以使用此圖來(lái)描述:
元素的上邊距離頁(yè)面頂部的距離值(以下簡(jiǎn)稱“上邊距離”)從Y(a)變成了Y(b),“左邊距離”從X(a)變成了X(b),也即完成了元素的移動(dòng)。
在整個(gè)的變化過(guò)程中,有這樣的一個(gè)隱藏信息:鼠標(biāo)相對(duì)于元素的坐標(biāo)(distX, distY)在整個(gè)移動(dòng)過(guò)程中是沒(méi)有發(fā)生變化的,用圖上的關(guān)系即可以表示為:cX(b) - X(b) = cX(a) - X(a) = distX,cY(b) - Y(b) = cY(a) - Y(a) = distY。那么,在整個(gè)移動(dòng)過(guò)程中,元素的“上邊距離”= 鼠標(biāo)移動(dòng)中任意時(shí)刻的Y坐標(biāo) - distY,“左邊距離”= 鼠標(biāo)移動(dòng)中任意時(shí)刻的X坐標(biāo) - distX。那么怎么求出distX和distY呢?
我們?cè)诎聪率髽?biāo)的那一刻,瀏覽器就會(huì)告訴我們鼠標(biāo)的坐標(biāo)(cX, cY),同時(shí),我們也可以求出目標(biāo)元素的“上邊距離”(Y)和“左邊距離”(X),這樣distX = cX - X,distY = cY - Y。
二、代碼實(shí)現(xiàn)
1.初始化工作
按照第一部分的分析,我們需要在按下鼠標(biāo)的那一刻獲取元素的“上邊距離”和“左邊距離”。在傳統(tǒng)的采用【position: absolute】定位的實(shí)現(xiàn)方式中,這一步我們可以通過(guò)DOM的【offsetTop】和【offsetLeft】來(lái)分別獲取它們的值。但既然我們采用transform的方式來(lái)實(shí)現(xiàn),就不再使用這兩個(gè)屬性了。
我們首先設(shè)置元素的一些關(guān)鍵樣式(部分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;
}復(fù)制代碼值得注意的是,我們采用translate3d的屬性值并設(shè)置了z軸的值為1px,這樣做的目的是強(qiáng)制瀏覽器使用GPU加速,從而獲得更加流暢的體驗(yàn)。
判斷瀏覽器是否啟用GPU加速,可以在定位到該元素之后,查看元素的計(jì)算后的樣式:transform的值是matrix還是matrix3d,顯示為后者時(shí),即表示已開(kāi)啟GPU加速。
如果我們使用【position: absolute】來(lái)實(shí)現(xiàn),那么初始位置的X(a)和Y(a)分別以left和top的值來(lái)分別指定,但采用transform來(lái)實(shí)現(xiàn)時(shí),我們就可以使用translateX和translateY來(lái)分別指定X(a)和Y(a)。在上面的CSS設(shè)置中,X(a)和Y(a)就被分別設(shè)置為0和0。
我們需要在代碼中獲取該元素的transform的計(jì)算后的值,代碼如下:
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 ''
}復(fù)制代碼
2.綁定mousedown事件并獲取distX和distY
我們準(zhǔn)備一個(gè)獨(dú)立的文件drag.matrix.js來(lái)編寫我們的代碼,用來(lái)實(shí)現(xiàn)模塊化的編程。
我們首先定義一個(gè)模塊內(nèi)的全局變量用來(lái)承載需要綁定拖拽功能的元素。/* 定義元素變量 */
let ELEMENT = null復(fù)制代碼
再定義一個(gè)模塊內(nèi)的全局對(duì)象用來(lái)存儲(chǔ)計(jì)算用到的各個(gè)距離與尺寸數(shù)據(jù)。/* 定義距離尺寸的存儲(chǔ)池 */
let E_SIZER = {}
復(fù)制代碼
mousedown事件的回調(diào)函數(shù)如下:/**
* mousedown事件
* @param {MouseEvent} evte 鼠標(biāo)事件對(duì)象
* @returns {undefined}
**/
function bindMouseDownEvent(evte){
// 阻止冒泡
evte.stopPropagation()
// 阻止默認(rèn)事件
evte.preventDefault()
// 解析matrix的正則
let matrix3dReg = /^matrix3d\((?:[-\d.]+,\s*){12}([-\d.]+),\s*([-\d.]+)(?:,\s*[-\d.]+){2}\)/,
matrixReg = /^matrix\((?:[-\d.]+,\s*){4}([-\d.]+),\s*([-\d.]+)\)$/
// 獲取解析后的transform樣式屬性值(計(jì)算后的樣式)
let matrix3dSourceValue = util.getStyle(
evte.target,
'transform'
)
// 使用正則解析matrix
let matrix3dArrValue =
matrix3dSourceValue.match( matrix3dReg ) || matrix3dSourceValue.match( matrixReg )
// 記錄鼠標(biāo)點(diǎn)擊時(shí)的坐標(biāo)
E_SIZER['clientX'] = evte.clientX
E_SIZER['clientY'] = evte.clientY
// 記錄matrix解析后的translateX & translateY的值
E_SIZER['targetX'] = matrix3dArrValue[1]
E_SIZER['targetY'] = matrix3dArrValue[2]
// 計(jì)算坐標(biāo)邊界巨鹿
E_SIZER['distX'] = E_SIZER['clientX'] - E_SIZER['targetX']
E_SIZER['distY'] = E_SIZER['clientY'] - E_SIZER['targetY']
// 綁定mousemove事件
document.addEventListener('mousemove', bindMouseMoveEvent, false)
} 復(fù)制代碼
被設(shè)置了transform屬性值為translate3d的元素,瀏覽器會(huì)將這個(gè)樣式的屬性值計(jì)算為matrix3d(...)的矩陣。
那么怎么獲取到translateX和translateY的值呢?
這里提供兩個(gè)正則,用來(lái)解析matrix或matrix3d的值并得到translateX和translateY的值:
/^matrix3d\((?:[-\d.]+,\s*){12}([-\d.]+),\s*([-\d.]+)(?:,\s*[-\d.]+){2}\)/
/^matrix\((?:[-\d.]+,\s*){4}([-\d.]+),\s*([-\d.]+)\)$/復(fù)制代碼
這兩個(gè)正則可以直接使用,例如:
有了以上的分析和知識(shí)儲(chǔ)備,我們就可以在鼠標(biāo)按下的那一刻,獲取到元素的初始X(a)和Y(a)的值了,也即上述的【bindMouseDownEvent】函數(shù)。
3.綁定mousemove事件移動(dòng)元素
mouseover事件的回調(diào)函數(shù)如下:/**
* mousemove事件
* @param {MouseEvent} evte 鼠標(biāo)事件對(duì)象
* @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)`
} 復(fù)制代碼
如果讀者對(duì)本文第一部分的分析理解了的話,對(duì)于這一段函數(shù)應(yīng)該會(huì)比較容易理解了。我們只要將鼠標(biāo)在移動(dòng)中的坐標(biāo)值“轉(zhuǎn)換”到元素的身上,即可完成對(duì)元素的實(shí)時(shí)移動(dòng)了。
我們需要將【bindMouseMoveEvent】綁定到document上,因?yàn)樵诳焖僖苿?dòng)過(guò)程中,鼠標(biāo)實(shí)際上會(huì)移出元素,如果直接將該回調(diào)函數(shù)綁定到元素上,可能會(huì)導(dǎo)致移動(dòng)過(guò)程異常終止。
4.綁定mouseup解除功能
mouseup事件的回調(diào)函數(shù)如下:/**
* mouseup事件
* @param {MouseEvent} evte 鼠標(biāo)事件對(duì)象
* @returns {undefined}
**/
function bindMouseUpEvent(evte){
evte.stopPropagation()
evte.preventDefault()
document.removeEventListener('mousemove', bindMouseMoveEvent)
} 復(fù)制代碼
我們需要將綁定到document上的mousemove回調(diào)事件函數(shù)移除。
5.初始化事件綁定/**
* 綁定事件
* @param {MouseEvent} evte 鼠標(biāo)事件對(duì)象
* @returns {undefined}
**/
function initBindEvent(){
// 綁定mousedown事件
ELEMENT.addEventListener('mousedown', bindMouseDownEvent, false)
// 綁定mouseup事件
document.addEventListener('mouseup', bindMouseUpEvent, false)
}復(fù)制代碼
同樣的,我們需要將mouseup事件的回調(diào)函數(shù)綁定到document。
這樣,我們我完成了拖拽功能的主體部分的開(kāi)發(fā)工作,只要將其功能綁定到指定的元素上即可(可以訪問(wèn)文章尾部的github地址來(lái)體驗(yàn))。‘
三、總結(jié)
今天我們使用transform對(duì)傳統(tǒng)的使用position: absolute的拖拽功能進(jìn)行了升級(jí),避免了在頁(yè)面元素在移動(dòng)過(guò)程中的不斷的回流重繪,從而提升了功能性能。
在不考慮對(duì)老舊瀏覽器的兼容的情況下,可以盡量地使用CSS來(lái)獲取更優(yōu)的用戶體驗(yàn)。
期待點(diǎn)贊;不足之處,歡迎指出。
2019-09-20
知乎專欄:前端小知識(shí)
總結(jié)
以上是生活随笔為你收集整理的css元素可拖动,使用css-transform实现更好的拖拽功能的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: kafka增加服务器,kafka增加to
- 下一篇: 李笑来 css,李笑来都想投资千万美金的