css元素可拖动,使用css-transform实现更好的拖拽功能
拖拽功能是目前網頁上一種非常常見的功能,例如“登錄彈窗”的拖拽。本文將使用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实现更好的拖拽功能的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: kafka增加服务器,kafka增加to
- 下一篇: 李笑来 css,李笑来都想投资千万美金的