<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>前端開發-拖拽上傳文件</title>
</head>
<body>
<script>
document.ondragover=(e)=> {
e.preventDefault()
}
document.ondrop=(e)=> {
e.preventDefault()
// 某些版本的 Firefox 無視 preventDefault
// 需要執行 stopImmediatePropagation 防止瀏覽器打開文件
e.stopImmediatePropagation()
const file=e.dataTransfer.files[0]
const img=new Image()
img.src=URL.createObjectURL(file)
document.body.appendChild(img)
console.log(file)
// 如何上傳文件?
// 參見 https://www.toutiao.com/article/7298667864926536242/
}
</script>
</body>
</html>
要為大家詳細介紹了HTML5拖放效果的實現代碼,拖放即抓取對象以后拖到另一個位置。感興趣的小伙伴們可以參考一下:
在 HTML5 中,拖放是標準的一部分,任何元素都能夠拖放。
Internet Explorer 9+, Firefox, Opera, Chrome, 和 Safari 支持拖動。但 Safari 5.1.2不支持拖動。
下面看看實現效果:
拖拽前
拖拽后
代碼實現:
把draggable屬性設置為true ,才能使元素可拖動。
然后,規定當元素被拖動時,會發生什么。
在上面的例子中,ondragstart 屬性調用了一個函數,drag(event),它規定了被拖動的數據。
dataTransfer.setData() 方法設置被拖數據的數據類型和值:
在這個例子中,數據類型是 "Text",值是可拖動元素的 id ("drag1")。
ondragover 事件規定在何處放置被拖動的數據。
默認地,無法將數據/元素放置到其他元素中。如果需要設置允許放置,我們必須阻止對元素的默認處理方式。
這要通過調用 ondragover 事件的 event.preventDefault() 方法:event.preventDefault()
當放置被拖數據時,會發生 drop 事件。
在上面的例子中,ondrop 屬性調用了一個函數,drop(event):
調用 preventDefault() 來避免瀏覽器對數據的默認處理(drop 事件的默認行為是以鏈接形式打開)
通過 dataTransfer.getData("Text") 方法獲得被拖的數據。該方法將返回在 setData() 方法中設置為相同類型的任何數據。
被拖數據是被拖元素的 id ("drag1")
把被拖元素追加到放置元素(目標元素)中
若要實現來回拖動:
若要在兩個地方來回拖動,只需將上面代碼稍作修改就行了.將body中的代碼改成:
然后在style樣式中加上#div2就可以了:
言
最近沒有更新文章,因為去字節實習了一陣,實在是沒有精力寫東西,所以就咕咕咕了。現在回學校了,就可以繼續更新啦,因為在字節做的業務和圖可視化還有拖拽關系比較大,所以這次就寫下拖拽相關的內容。
HTML5 Drag and Drop 接口
html5中提供了一系列Drag and Drop 接口,主要包括四部分:DragEvent,DataTansfer,DataTransferItem 和DataTransferItemList。
DragEvent
源元素和目標元素
image-20220314095928431.png
**源元素:**即被拖拽的元素。
**目標元素:**即合法的可釋放元素。
每個事件的事件主體都是兩者之一。
拖拽事件
事件事件處理程序事件主體觸發時機dragstartondragstart源元素當源元素開始被拖拽。dragondrag源元素當源元素被拖拽(持續觸發)。dragendondragend源元素當源元素拖拽結束(鼠標釋放或按下esc鍵)dragenterondragenter目標元素當被拖拽元素進入該元素。dragoverondragover目標元素當被拖拽元素停留在該元素(持續觸發)。dragleaveondragleave目標元素當被拖拽元素離開該元素。dropondrop目標元素當拖拽事件在合法的目標元素上釋放。
觸發順序及次數
我們綁定相關的事件,拖放一次來查看相關事件的觸發情況。
op.gif
我們讓相應事件處理程序打印事件名稱及事件觸發的主體是誰,下面截取部分展示。
image-20220314103603324-16473183157994.png
我們可以看到對于被拖拽元素,事件觸發順序是 dragstart->drag->dragend;對于目標元素,事件觸發的順序是 dragenter->dragover->drop/dropleave。
其中drag和dragover會分別在源元素和目標元素反復觸發。整個流程一定是dragstart第一個觸發,dragend最后一個觸發。
這里還有一個注意的點,如果某個元素同時設置了dragover和drop的監聽,那么必須阻止dragover的默認行為,否則drop將不會被觸發。
image-20220314111402189.png
DataTansfer
我們先用一張圖來直觀的感受一下:
image-20220315122157204-16473183290157.png
我們可以看到,DataTransfer如同它的名字,作用就是在拖放過程中對數據進行傳輸,其中setData用來存放數據,getData用來獲取數據,出于安全的考量,數據只能在drop時獲取,而effectAllowed和dropEffect則影響鼠標展示的樣式,下面我們用一個例子來進行展示:
sourceElem.addEventListener('dragstart', (event) => {
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', '放進來了');
});
targetElem.addEventListener('dragover', (event) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
});
targetElem.addEventListener('drop', (event) => {
event.target.innerHTML = event.dataTransfer.getData('text/plain');
});
復制代碼
drag2.gif
可以看到在藍色方塊設置的數據被成功取得了。
DataTransferItemList
屬性
length: 列表中拖動項的數量。
方法
add(): 向拖動項列表中添加新項 (File對象或String),該方法返回一個 DataTransferItem) 對象。
remove(): 根據索引刪除拖動項列表中的對象。
clear(): 清空拖動項列表。
DataTransferItem(): 取值方法:返回給定下標的DataTransferItem對象.
DataTransferItem
屬性
kind: 拖拽項的種類,string 或是 file。
type: 拖拽項的類型,一般是一個MIME 類型。
方法
getAsString: 使用拖拽項的字符串作為參數執行指定回調函數。
getAsFile: 返回一個關聯拖拽項的 File 對象 (當拖拽項不是一個文件時返回 null)。
實踐
學習了上面的基礎知識,我們從幾個常見的應用場景入手,來實踐上面的知識
可放置組件
知道上面幾個事件后,我們來完成一個簡單可放置組件,為了方便大家理解,這里不使用任何框架,以免增加不會框架同學的學習成本。
想讓組件可拖行,那么就要可以改變它的位置,有兩種思路:
我推薦第二種,首先translate是基于本身的移動,因此自身的坐標就作為原點(0,0),但是第一種,元素本身的top/left等可能并不為0,計算起來比較復雜。其次,第一種是通過cpu去計算,而第二種是通過gpu去計算,并且會提升到一個新的層,這樣做非常有利于頁面的性能。原因是 Chrome 這樣將 DOM 轉變成一個屏幕圖像:
但更新的幀可以走捷徑,不必經歷所有過程:
如果某些特定 CSS 屬性變化,并不需要發生重繪。Chrome 可以使用早已作為紋理而存在于 GPU 中的層來重新復合,但會使用不同的復合屬性(例如,出現在不同的位置,擁有不同的透明度等等)。
如果圖層中某個元素需要重繪,那么整個圖層都需要重繪 。所以提升為一個新的層,可以減少重繪的次數。因為只改變位置,所以可以復用紋理,提高性能。
更詳細的可以看我的另一篇文章:瀏覽器事件循環與渲染機制 \- 掘金 \(juejin.cn\)[1]
有了思路那么我們就開始吧!
首先我們要知道這次拖拽的向量是怎樣的,因為DragEvent繼承自MouseEvent ,所以我們可以通過MouseEvent接口的offsetX屬性和offsetY屬性獲取鼠標現在相對于該物體的位置差。而transform設置多個屬性值,效果就可以疊加,所以我們要獲得之前的移動效果,再加上現在的移動效果即可,之前的移動效果可以通過window.getComputedStyle(e.target).transform獲得。
sourceElem.addEventListener('dragend', (e) => {
const startPosition = window.getComputedStyle(e.target).transform;
e.target.style.transform = `${startPosition} translate(${e.offsetX}px, ${e.offsetY}px)`;
}, true);
復制代碼
我們給要拖拽的元素加上這段處理程序似乎就大功告成了。
wrong.gif
但是實際使用時,這個元素并沒有停在預覽的位置,而是左上角移動到鼠標的位置,顯然不符合預期,相信大家都能猜到,我們少考慮了鼠標在元素的位置,而鼠標初始的位置同樣可以通過MouseEvent接口的offsetX屬性和offsetY屬性獲取(dragstart)。改善如下:
function enableDrag(element) {
let mouseDiff = null;
element.addEventListener('dragstart', (e) => {
//初始時鼠標與元素的位置差
mouseDiff = `translate(${-e.offsetX}px, ${-e.offsetY}px)`
}, true);
element.addEventListener('dragend', (e) => {
//開始時元素的位置
const startPosition = window.getComputedStyle(e.target).transform;
//鼠標移動的位置
const mouseMove = `translate(${e.offsetX}px, ${e.offsetY}px)`;
e.target.style.transform = `${mouseDiff} ${startPosition} ${mouseMove}`;
}, true);
}
enableDrag(souceElement);
復制代碼
drag.gif
圖的連線
節點使用DOM渲染,連線我們使用SVG來渲染,框架使用React,但除了state盡量使用較少的框架相關的,以防非React技術棧的同學看不懂。
首先我們要先組織我們的state,作為一個圖,顯然應該由nodes和edges兩部分組成,我們都使用數組存儲,我們給每個node一個唯一的id,使用Map去映射id與對應的positon 形如 [x,y]的關系,而edge有源端與終端的id,通過id去獲得對應的坐標。
我們先假設節點可以完成所有功能了,只考慮連線,可以定義如下的組件
const Edge = ({nodes:[sourceNode,targetNode]}) =>(
<svg key={sourceNode.id + targetNode.id||''}
style={{position:'absolute',
overflow:'visible',
zIndex:'-1',
transform:'translate(15px,15px)'}}>
<path d={`M ${sourceNode.position[0]} ${sourceNode.position[1]}
C
${(targetNode.position[0] + sourceNode.position[0])/2} ${sourceNode.position[1]}
${(targetNode.position[0] + sourceNode.position[0])/2} ${targetNode.position[1]}
${targetNode.position[0]} ${targetNode.position[1]} `}
strokeWidth={6}
stroke={'red'}
fill='none'
></path>
</svg>
)
復制代碼
首先我們應該從什么時候生成一個連線呢,顯然是dragstart,但這時還沒有對應的終端,因此不應該通過加入edges來循環渲染,而是單獨渲染一個出來。在dragstart我們設置一個虛擬節點temNode,并記錄開始節點的id。
并如果有temNode則渲染一條預覽的edge。
temNode && (<Edge nodes = {[sourceNode,temNode]}></Edge>)
復制代碼
drag3.gif
然后加入這條edge后,我們刪除虛擬節點,變為循環渲染來展示所有的邊。
edges.map(([sourceId,targetId])=>{
const sourceNode = getNode(sourceId);
const targetNode = getNode(targetId);
return (
<Edge nodes = {[sourceNode,targetNode]}></Edge>);
})
復制代碼
那么我們只考慮邊的展示了,節點的功能應該如何完善呢?
首先是dragstart,我們要設置起始節點和虛擬節點
onDragStart={()=>{
setStartNodeId(uid);
setTemNode({position:[x,y]})
}}
復制代碼
然后既然邊可以跟著動,我們必然要在drag中動態的改變虛擬節點的位置
onDrag={(event)=>{
position=[x+event.nativeEvent.offsetX,y+event.nativeEvent.offsetY];
setTemNode({position})
}}
復制代碼
然后drop時,我們加入一條新的邊
onDrop={
(event)=>{
event.preventDefault();
setEdges(edges.concat([[startNodeId,uid]]))
}
}
復制代碼
最重要的是,不論在哪里事件結束了,要刪除虛擬節點
onDragEnd={
()=>{
setTemNode(null);
}
}
復制代碼
下面是最終成果:
drag4.gif
參考
關于本文
https://juejin.cn/post/7075918201359433758
*請認真填寫需求信息,我們會在24小時內與您取得聯系。