如何自定義 drag 樣式
歡迎關注我的公眾號: 前端偵探
在 web 中,圖片預設是可以拖拽的,通常是一個 半透明的預覽圖 (下面簡稱“預覽圖”),如下
這個預覽圖是系統自動生成的,一般無法進行自定義,但有時候會覺得拖拽的預覽圖尺寸太大了,不方便放置,例如在這種場景下
這是一個拖拽圖片儲存的功能,需要將圖片拖拽至指定區域觸發儲存操作。 由於拖拽時預覽圖太大,導致放置區域都被遮住了,茫茫的一片,體驗非常不好 。
如果預覽圖的尺寸可以小一點,例如這樣:
是不是體驗就會好很多?下面一起來探討一下
一、預覽圖的預設尺寸
首先,拖拽預覽圖的尺寸是和頁面上圖片的CSS 尺寸相關聯的,也就是說展示的圖片越大,拖拽的預覽圖越大
但是,預覽圖的大小並不是無限制的, 據我測試,預覽圖的最大尺寸是 400 400 ,比如下方圖片的尺寸是 500 500, 但是拖拽出來的預覽圖只有 400 * 400,但對實際開發而言,這個尺寸還是過大
以上是 MacOS Chrome 下的表現效果,Firefox 表現要好很多,不會出現預覽圖過大的情況
另外,測試了一下 Windows Chrome 的表現效果,比較類似,只不過最大尺寸是 200 * 200 ,表現如下
二、藉助 setDragImage 修改預覽圖
原生的拖拽只能通過setDragImage 來修改預覽圖,看著好像很強大,實則非常雞肋,簡單使用如下
function dragstart_handler(ev) { var img = new Image(); img.src = 'example.jpg'; ev.dataTransfer.setDragImage(img, 0, 0); }
這裡指定了這樣一張圖片作為預覽圖
需要繫結在 dragstart
事件上,這裡採用事件委託的方式
document.addEventListener('dragstart', ev => { if (ev.target.tagName === 'IMG') { dragstart_handler(ev) } })
效果是這樣的
為什麼是第二次拖拽才出現自定義預覽呢?這就是雞肋的地方了,官方規定 example.jpg
為一個已經存在的圖片,如 果這個還沒有建立或者還未載入完成,那麼瀏覽器將會使用預設的拖動圖片 ,在第二次拖拽的時候圖片載入完了,所以才生效。可以說,幾乎所有 setDragImage
不起作用的情況都是因為這個原因。
除此之外,還有一個問題, 無法直接指定預覽圖的大小 ,例如
function dragstart_handler(ev) { var img = new Image(); img.src = 'example.jpg'; img.width = 50;//無效 img.height = 50; ev.dataTransfer.setDragImage(img, 0, 0); }
這樣也是無效的,記憶體中的圖片會按原始大小來展示
三、指定預覽圖為頁面元素
其實,要想修改預覽圖的大小還 可以通過 canvas 渲染原圖,然後修改尺寸再生成圖片 ,但是 canvas 在渲染圖片時經常會出現 跨域 的問題,並且 canvas 也有一定的門檻,所以這裡先不採用。
再回過來看看官方的語法
dataTransfer.setDragImage(img | element, xOffset, yOffset);
可以看到,第一個引數不僅支援動態建立的圖片,也支援 頁面上實際存在的元素 (頁面上存在的元素肯定是載入完成的)
假設我們將自定義預覽圖新增在頁面
<img ondragstart="dragstart_handler" src="head.jpg"> <img id="img" src="example.jpg"><!--預覽圖-->
然後將預覽圖指定為該圖片 example.jpg
ev.dataTransfer.setDragImage(img, 0, 0);
效果如下
可以看到, 拖拽圖片就像拖拽 example.jpg
一樣 ,而且也可以通過CSS直接修改尺寸!
#img{ width: 100px; height: 100px; }
預覽圖大小也是跟隨 example.jpg
的
如果把 example.jpg
換成和原始圖相同的連結 ,是不是就相當於可以自定義預覽圖的大小了呢?
現在需要將自定義圖片隱藏起來,畢竟只是借用一下,頁面上不需要顯示,這裡需要一點小技巧了,不能真正的將這個圖片隱藏起來,比如
#img{ display: none; /*或者*/ opacity: 0; /*或者*/ visibility: none }
這些方式都不行,由於預覽圖是跟隨這張自定義圖片的,如果直接隱藏了都會導致預覽圖直接消失不見。
這時採取的方式可以 將這張圖片移到視區之外 ,比如這樣
#img{ position: absolute; top: -9999px; }
這樣就沒問題了
實際工作中,這張圖應該是自動生成的,而且自動獲取當前拖拽圖片的連結,完整實現就是
/* * custom_drag_img * author: xboxyan */ const img = document.createElement('img'); img.style = "position: absolute; top: -9999px; max-width: 100px; max-height: 100px;" document.body.append(img) document.addEventListener('dragstart', ev => { if (ev.target.tagName === 'IMG') { img.src = ev.target.src; ev.dataTransfer.setDragImage(img, 0, 0); } })
可以將這段程式碼注入其他地方,試試效果,比如原始效果如下
注入以上程式碼之後的效果如下:
在某些場景下是不是體驗好很多呢?
四、自定義預覽圖樣式
上述預覽圖不僅可以指定圖片元素,也可以其他元素,比如 div
,新增一些修飾
<div class="drag_img"> <img src="xxx"> </div>
.drag_img{ position: absolute; top: -9999px; max-width: 100px; max-height: 100px; border: 3px solid yellow; }
這樣就可以給預覽圖加上一個黃色邊框(強調作用)了
亦或是通過偽元素加個角標
.drag_img::after{ content: ''; position: absolute; top: 0; right: 0; width: 20px; height: 20px; background: linear-gradient(-135deg, #f44336 50%, transparent 51%); }
但是新增CSS濾鏡沒有生效,仍然是原始樣式
.drag_img{ position: absolute; top: -9999px; max-width: 100px; max-height: 100px; filter: sepia(1); /*褐色濾鏡*/ }
這可能跟系統生成預覽圖的策略有關,無從得知,也無法修改
五、完全的自定義方案 draggable-polyfill
其實上述都只是針對 setDragImage
做的一些自定義,本質上還是原生預覽圖,也僅適用於圖片拖拽(但是效能超好),如果想完全自定義拖拽效果,自定義普通可拖拽元素,就必須通過 JS 模擬實現了。大致思路如下
- 去除預設的預覽圖
- 複製一份當前目標元素,cloneObj
- 監聽拖拽事件,通過 transform 改變cloneObj的位置
- 拖拽結束移除cloneObj
詳細原理可以參考我之前的一篇文章: 實現一個美化原生拖拽的draggable-polyfill ,雖然是自定義實現的,但是完全不影響原有業務拖拽邏輯,這正是 polyfill 的魅力,非常適合原生拖拽實現的場景
專案地址: http://github.com/XboxYan/draggable-polyfill ,非常輕量,100 來行程式碼,不影響業務邏輯,非常適合學習和時使用,歡迎 star~
3 年前的老程式碼了,最近優化了一波,非常實用,歡迎 star
六、總結一下
以上就完成了圖片拖拽預覽圖的自定義,其實主要用於修改預覽尺寸,在某些場景下還是挺有用的,這裡總結一些要點:
- 在拖拽時產生的預覽圖是系統生成的,無法直接修改
- 在macOS Chrome下,預覽圖的尺寸最大為 400 * 400,在某些場景下體驗不太友好
- setDragImage 可以修改預覽圖
- setDragImage 需要圖片已經載入完成才會生效,否則仍然是預設樣式
- setDragImage 可以指定頁面上的其他元素,不僅僅是 img
- setDragImage 指定的元素不能真正的隱藏,否則預覽圖將不可見,可以通過移出到視線之外的方式
- 以上方案適用於圖片拖拽,並且自定義的樣式有限
- 如果需要完全自定義拖拽,可以採用 draggable-polyfill 方案,歡迎star~
總的來說,整體實現還是非常簡單的,由於是原生方案,效能就不用說了,也不會影響原有功能,起到了美化的作用,如果有類似的需求,就趕緊用起來吧~ 最後,如果覺得還不錯,對你有幫助的話,歡迎點贊、收藏、轉發❤❤❤
歡迎關注我的公眾號: 前端偵探
- 基於 Nebula Graph 構建百億關係知識圖譜實踐
- 元宇宙 3D 開荒場 - 探味奇遇記
- 摺疊面板元件的設計與實現
- web技術分享| 【高德地圖】實現自定義的軌跡回放
- Vue3中的teleport節點傳送
- Flutter 常見異常分析
- React Native如何做線上錯誤與效能監控
- shell指令碼程式設計學習筆記——變數
- Object.prototype.toString.call()的原理
- 玩轉 AbortController 控制器
- Go十大常見錯誤第2篇:benchmark效能測試的坑
- 技術分享 | dbslower 工具學習之探針使用
- TypeScript 中令人迷惑的物件型別:Object、{}和 object
- 探針技術-JavaAgent 和位元組碼增強技術-Byte Buddy
- 解決方案| 快對講綜合排程系統
- 解鎖Markdown高階用法,提升寫作效率
- MAUI模板專案閃退問題
- 關於這個知識點,我被讀者罵到回家種田
- 2022 年你手機裡有哪些堪稱神器的 App?
- webpack打包時如何修改檔名