国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

原生JS手寫絲滑流暢的元素拖拽效果

開發 前端
我們需要知道鼠標的三個事件,分別是 mousedown,mousemove,mouseup ,當點擊按下的時候,克隆一個絕對定位的元素,并標識下"拖拽中"的狀態,接著在 mousemove 中就可以判斷應該執行的具體方法,從而讓元素隨著鼠標移動起來。

前言

提到元素拖拽,通常都會先想到用 HTML5 的拖拽放置 (Drag 和 Drop) 來實現,它提供了一套完整的事件機制,看起來似乎是首選的解決方案,但實際卻不是那么美好,主要是它的樣式太過簡陋,無法實現更高級的用戶體驗:

這是瀏覽器默認的拖拽效果,點住拖拽任意圖片或文字都會產生。

筆者因為之前有個小項目需要經常參考稿定設計,一直有留意其元素拖拽的效果(如下圖),所以接下來我將以這種效果為藍本,使用原生 JS 實現一個富有動感的 自定義拖拽 效果,話不多說直接開摸。

實現原理

首先說下思路,我們需要知道鼠標的三個事件,分別是 mousedown,mousemove,mouseup ,當點擊按下的時候,克隆一個絕對定位的元素,并標識下"拖拽中"的狀態,接著在 mousemove 中就可以判斷應該執行的具體方法,從而讓元素隨著鼠標移動起來。

在監聽事件的 event 對象中,有幾個參數是比較重要的:clientX,clientY 標識的鼠標當前橫坐標和縱坐標,offsetX 和 offsetY 表示相對偏移量,可以在 mousedown 鼠標按下時記錄初始坐標,在 mouseup 鼠標抬起時判斷是否在目標區域中,如果是則用鼠標獲取到的當前的偏移量 - 初始坐標得到元素實際在目標區域中的位置。

為了閱讀體驗,以下所有代碼均有部分省略,文末可查看完整源碼地址,代碼量并不多。

基礎界面

先簡單實現一個兩欄布局界面,并應用上一些 CSS 效果:

<div id="app">
<div class="slide">
<div id="list">
<img class="item" src="......." />
<img .........
</div>
</div>
<div class="content"></div>
</div>
#app {
width: 100vw;
height: 100vh;
display: flex;
}
.active {
cursor: grabbing;
}

.slide {
width: 260px;
height: 100%;
overflow: scroll;
border-right: 1px solid rgba(0,0,0,.15);
#list {
user-select: none;
.item {
background: rgba(0,0,0,.15);
width: 120px;
display: inline-block;
break-inside: avoid;
margin-bottom: 4px;
}
.item:hover {
cursor: grab;
filter: brightness(90%);
}
.item:active {
cursor: grabbing;
}
}
.grid {
column-count: 2;
column-gap: 0px;
}
}
.slide::-webkit-scrollbar {
display: none; /* Chrome Safari */
}

#content {
position: relative;
flex: 1;
height: 100%;
margin-left: 45px;
background: rgba(0,0,0,.07);
.item {
position: absolute;
transform-origin: top left;
}
}

利用濾鏡 filter: brightness(90%); 調節明亮度可以快速實現一個鼠標覆蓋的動態效果,無需額外制作遮罩:

使用偽類激活 cursor 的 grab 和 grabbing 可以設置抓取動作的圖標:

實現元素抓取

利用事件委托機制為選擇列表添加 mousedown 事件監聽,實現抓取的原理是在鼠標按下時克隆按下的元素,并把克隆出來的元素設置成絕對定位,讓它"浮"起來:

let dragging = false
let cloneEl = null // 克隆元素
let initial = {} // 初始化數據記錄
......
// 選中了元素
cloneEl = e.target.cloneNode(true) // 克隆元素
cloneEl.classList.add('flutter') // 使其浮動
e.target.parentElement.appendChild(cloneEl) // 加入到列表中
dragging = true // 標記拖動開始

// TODO:
........
.flutter {
position: absolute;
z-index: 9999;
pointer-events: none;
}

將鼠標的坐標設置為克隆元素的絕對定位值(left、top),就會像下圖所示這樣,此時減去 offset 偏移量,就能讓克隆元素覆蓋在本體上面。

初始化的值需要記錄起來方便后續計算,同時我們用 dragging 變量標記了狀態(拖動中),接下來配合移動鼠標的監聽事件就能將元素“抓”起來了:

// 鼠標移動
window.addEventListener("mousemove", (e) => {
if (dragging && cloneEl) {
// TODO:
// x 軸(left)計算方法:e.clientX - initial.offsetX
// y 軸(top)計算方法:e.clientY - initial.offsetY
}
})

上面只是實現了元素的拖動,但是"克隆"的效果實在太明顯了,為了讓元素看起來更像是拖出來的而不是復制出來的,我們還要讓本體隱藏,同時DOM結構不能丟失,這時只需在按下拖動時給本體元素設置個 opacity: 0,結束時再改回透明度1就能搞定。

雖然到這功能就算實現了,但實際效果還是有點僵硬,參考稿定設計中的元素放開時會固定回到一個位置,然后再收回去,這個過渡又有點鬼畜,不夠流暢。其實只需讓元素回退過程有一個自然地動畫就行,transition 就能實現:

.is_return {
transition: all 0.3s;
}
// 鼠標抬起
window.addEventListener("mouseup", (e) => {
dragging = false
if (cloneEl) {
cloneEl.classList.add('is_return') // 加上過渡動畫
changeStyle(......) // 設置回元素的初始位置
setTimeout(() {
cloneEl.remove() // 移除元素
}, 300)
}
})

最終我在動作結束時給克隆元素添加了過渡屬性,然后直接設置回初始坐標讓克隆元素回到它的出生地點,用定時器在過渡動畫持續的相同時間后移除克隆元素,這樣就有了一個平滑穩定的回退動畫。

性能優化

由于在改變元素狀態的過程中需要頻繁進行多個 CSS 操作,為降低回流重繪的成本,最好將多個操作合并起來處理,這里利用了 cssText 來實現:

// 改變漂浮元素:x、y、縮放倍率
function moveFlutter(x, y, d = 0){
const scale = d ? initial.width + d < initial.fakeSize ? `transform: scale(${(initial.width + d) / initial.width})` : null : null
const options = [`left: ${x}px`, `top: ${y}px`]
scale && options.push(scale)
// 將CSS處理成數組,然后丟進DOM操作方法中一次執行
changeStyle(options)
}
// 合并多個操作
function changeStyle(arr){
const original = cloneEl.style.cssText.split(';')
original.pop()
cloneEl.style.cssText = original.concat(arr).join(';') + ';'
}

實現拖拽放大

放大我們可以使用 transform: scale 來實現,只需要將拖動位置之間的距離當做變化系數(假設為d),那么scale變化數值即為(元素寬度 + d)/元素寬度,而放大的最終倍數必定為 圖片實際寬度/元素的寬度,只要判斷不超過這個邊界就可以。(這個圖片實際寬高在真實業務場景中建議在上傳資源時就記錄在數據庫,這里我是模擬的隨機一個原圖尺寸)。

兩點間距離計算公式為:

代碼實現:

// 計算兩點之間距離
function distance({ clientX, clientY }){
const { clientX: x, clientY: y } = initial // 獲取初始的坐標
const b = clientX - x;
const a = clientY - y;
return Math.sqrt(Math.pow(b, 2) + Math.pow(a, 2))
}

window.addEventListener("mousemove", (e) => {
if (dragging && cloneEl) {
const d = distance(e) // 計算距離
moveFlutter(e.clientX - initial.offsetX, e.clientY - initial.offsetY, d)
}
})
function moveFlutter(x, y, d = 0){
let scale = ''
// 如果距離大于0,且寬度+距離小于實際寬度
if( d && initial.width + d <= initial.fakeSize ) {
scale = `transform: scale(${(initial.width + d) / initial.width})`
}
// TODO ... changeStyle ...
}

效果演示:

注意元素都要設置 transform-origin: top left; 改變縮放原點到左上角,否則默認(中心為原點)的轉換會發生比較明顯的偏移。

實現放置

其實拖拽放置有點像是"復制"與"粘貼",前面我們實現了復制,放置主要就是將元素粘貼到畫布當中,流程步驟如下:

  • 如果鼠標在目標區域,拷貝元素到畫布中,如果不在畫布中,執行倒退動畫
  • 刪除元素
// 完成處理
function done(x, y) {
if (!cloneEl) { return }
const newEl = cloneEl.cloneNode(true)
newEl.classList.remove('flutter')
newEl.src = cloneEl.getAttribute('raw') // 設置原圖地址
newEl.style.cssText = `left: ${x - initial.offsetX}px; top: ${y - initial.offsetY}px;`
document.getElementById('content').appendChild(newEl)
// TODO:
}

判斷是否在畫布內抬起很簡單,往畫布上綁定mouseup監聽事件即可,克隆的新元素必須刪除無用的屬性和class,此時設置元素的left、top即可將元素放置進畫布中,關鍵點在于畫布內的target有可能是錯的,因為如果鼠標抬起的區域已經放置了元素,那么相對偏移量就得我們自己計算了,使用getBoundingClientRect方法獲取畫布本身相對于視窗的偏移,鼠標坐標減去畫布本身的偏移就是元素在畫布中的位置了。

document.getElementById('content').addEventListener("mouseup", (e) => {
if (e.target.id !== 'content') {
const lostX = e.x - document.getElementById('content').getBoundingClientRect().left
const lostY = e.y - document.getElementById('content').getBoundingClientRect().top
done(lostX, lostY)
} else { done(e.offsetX, e.offsetY) }
})

只貼了部分關鍵代碼,完整代碼文末查看。

邊界判斷

如果不對邊界情況進行處理可能會導致拖動時發生意外的中斷,無法正確回收克隆元素。

// 鼠標離開了視窗
document.addEventListener("mouseleave", (e) {
end()
})
// 用戶可能離開了瀏覽器
window.onblur = () {
end()
}

體驗優化

參考稿定設計中元素拖拽是直接賦值原圖的,原圖大小通常無法控制,免不了需要加載時間,造成卡頓空白的問題,在網絡不夠快時體驗尤其尷尬:

我的優化思路是利用瀏覽器加載過同一張圖片就會優先讀緩存的機制,先用一個Image加載原圖,等其加載完畢再把拖拽元素的src改成原圖,這樣瀏覽器會"自動"幫我們優化這個過程,只需要注意一點,由于這是個異步任務,所以一定要做好對應標記,不然手速快的時候控制不好觸發順序。

function simulate(url, flag) {
cloneEl.setAttribute('raw', url)
const image = new Image()
image.src = url
image.onload = function () {
// 異步任務,克隆節點可能已不存在,flag標記是否拖動的還是當前目標
cloneEl && initial.flag === flag && (cloneEl.src = url)
}
}

效果演示,故意加大了圖片的分辨率差異:

以上就是文章的全部內容,感謝看到這里,希望對你有所幫助或啟發!創作不易,如果覺得文章寫得不錯,可以點贊收藏支持一下,也歡迎關注,我會更新更多實用的前端知識與技巧,我是茶無味的一天,期待與你共同成長~

相關鏈接

[1] 完整代碼地址: https://juejin.cn/post/7145447742515445791/#heading-9

?[2] 關于作者: https://book.palxp.com

責任編輯:武曉燕 來源: 今日頭條
相關推薦

2023-09-07 07:35:59

JS操作網頁

2022-04-27 16:24:59

iOS蘋果升級

2020-09-07 07:00:09

AI 數據人工智能

2024-05-21 10:28:51

API設計架構

2023-03-03 17:00:00

部署Linux內核

2012-10-10 10:22:57

JavaScriptJSjQuery

2022-08-16 08:37:09

視頻插幀深度學習

2025-03-03 12:00:00

JavaScriptfor 循環語言

2020-07-22 15:15:28

Vue前端代碼

2025-07-03 09:56:49

2023-07-18 07:56:20

2021-05-10 20:58:11

數據庫擴容用戶

2022-12-19 14:53:07

模型訓練

2024-05-16 12:03:54

Python代碼開發
點贊
收藏

51CTO技術棧公眾號

韩国成人动漫| 日韩天天综合| 日韩一区二区福利| 欧美xxxx网站| 91av成人在线| 亚洲资源av| 久久精品香蕉视频| 亚洲精品ww久久久久久p站 | 福利一区福利二区微拍刺激| 无人视频在线观看免费| 日韩一区二区中文字幕| 阿v免费在线观看| 亚洲欧美在线一区| 亚洲黄页网站| 一本一道久久久a久久久精品91 | 黄色片视频在线免费观看| 最新久久zyz资源站| 国产在线电影| 中文字幕av一区中文字幕天堂| a级日韩大片| 香蕉视频免费版| 欧美伊人久久大香线蕉综合69 | 91久久精品日日躁夜夜躁欧美| 一区二区视频免费完整版观看| 91青草视频久久| 国产精品丝袜91| 羞羞网站在线免费观看| 国产精品久久久久久久久久久不卡| 国产精品外国| 亚洲人av在线| 国产玖玖精品视频| 国产精品美女一区二区在线观看| 国产精品久久久久久久久免费高清 | 日本免费一二区| 亚洲欧美日韩网| 日本怡春院一区二区| 4hu永久免费入口| 另类视频在线观看| 开心九九激情九九欧美日韩精美视频电影| 作爱视频免费观看视频在线播放激情网| 在线看片第一页欧美| 91色乱码一区二区三区| 国产一区二区三区| 成人一区二区三| 欧美在线国产精品| 亚洲国产中文字幕在线视频综合 | 精品一区二区三区国产| 欧美巨大另类极品videosbest | 国产精品免费精品自在线观看| 亚洲中文字幕无码专区| 国产一区二区三区在线观看网站 | 琪琪五月天综合婷婷| 国内精品偷拍| 国产成人啪精品视频免费网| 国产欧美精品一区二区色综合朱莉 | 久久久久久久久久电影| 国产一区二区三区日韩精品| 韩国三级日本三级少妇99| 成人日韩在线观看| 亚洲综合色自拍一区| 手机成人在线| 婷婷综合久久| 91精品视频播放| 国产精品一国产精品| 午夜精品一区二区三区av| 亚洲超碰在线观看| 国产精品中文久久久久久久| 神马午夜dy888| 精品日韩99亚洲| 日日夜夜综合| 日韩女优人人人人射在线视频| 天海翼精品一区二区三区| 精品国产一区久久久| 三级电影一区| 欧美一级爱爱视频| 成人香蕉视频| 国产精品午夜av在线| 日本亚洲视频在线| 亚洲三级一区| 中文字幕五月欧美| 成人黄色在线| 玛丽玛丽电影原版免费观看1977| 麻豆精品一区二区综合av| 午夜伦全在线观看| 91麻豆桃色免费看| 国产精品福利一区| 日韩黄色影视| 久久先锋资源网| 欧美人与禽猛交乱配| 香蕉网站在线观看| 久久成人免费视频| 色偷偷久久一区二区三区| 一区二区三区四区激情| 国产精品视区| av在线精品| 国产福利免费在线观看| 国产一区免费| 欧美精品18videos性欧| 亚洲国产精品久久久久秋霞不卡| 粉嫩av一区二区三区粉嫩| 日韩国产在线| 欧美风情在线视频| 男人的天堂在线免费视频| 欧美日韩亚洲一| 欧美日韩无遮挡| 欧美中文在线视频| 国产一区二区三区18| 日韩一区二区精品| 亚洲综合色丁香婷婷六月图片| 国产一区二区视频在线播放| 日产精品一区二区| 日韩一二三区| 欧美free嫩15| 亚洲丝袜一区| 九色网友自拍视频手机在线| 色国产在线视频| 久久久国产欧美| 国产美女永久无遮挡| 五月天国产一区| 日韩精品不卡| 亚洲看片网站| 波多野结衣激情| 一本色道婷婷久久欧美| 欧美人与物videos另类| 麻豆91av| 亚洲精品第一区二区三区| 久久福利电影| 日韩av高清| 一区二区三区国产福利| 日韩国产欧美一区| 伊人久久av导航| 手机看片日韩国产| 成人午夜免费剧场| 精品无码国模私拍视频| av无码精品一区二区三区| 久久综合色视频| 中文字幕视频在线免费观看| 国产对白在线| 欧美激情二区| 日本成人三级电影| 日韩有码一区| 亚洲一级电影| 美日韩一区二区| 国产日韩欧美在线一区| 亚洲美女少妇撒尿| 91精品在线麻豆| 亚洲美腿欧美激情另类| 久久视频中文字幕| 亚洲免费观看高清完整版在线观看熊| 米奇777在线欧美播放| |精品福利一区二区三区| 欧美激情在线狂野欧美精品| 在线观看免费黄色片| 视频一区二区三区不卡| 一区二区三区四区电影| 亚洲妇熟xx妇色黄| 国产欧美一区二区白浆黑人| 欧美午夜aaaaaa免费视频| 超薄肉色丝袜脚交一区二区| 蜜桃在线一区二区三区| 亚洲白虎美女被爆操| 亚洲最大免费| 国产激情在线播放| 国产一区二区视频在线| 中文字幕不卡av| 国产三区在线视频| 91电影在线播放| 91精品国产视频| 亚洲aaa精品| 国产精品日韩久久久久| 五月婷婷六月丁香激情| av自拍一区| 国产精品传媒视频| 深夜福利国产精品| 久久手机在线视频| 成人97精品毛片免费看| 久久综合丝袜日本网| 久久国产精品久久精品| avove在线观看| 国产剧情在线观看一区| 国产精品色呦呦| 国产精品视频不卡| 黄色的视频在线免费观看| 99riav1国产精品视频| 制服丝袜国产精品| 欧美尺度大的性做爰视频| 国产欧美中文字幕| 香蕉视频在线免费看| 国产在线播放一区二区三区| 久久久精品视频成人| 亚洲一区二区三区成人| 玖玖国产精品视频| 欧美精品情趣视频| 免费观看成人高潮| 国产成a人亚洲精| 国产精品日韩在线| 宅男网站在线免费观看| av在线播放成人| 国产精品69av| 1区2区在线|