用js建立一個可拖曳的元素
我們一直使用並且理所當然的常見手勢是能夠在螢幕上拖動元素。儘管這種拖拽手勢有多麼常見,但是沒有很好的內建支援來使網路上的元素可拖動。如果我們希望超越滑鼠並支援觸控之類的東西,那就更是如此!這就是本教程的用武之地。在接下來的幾節中,我們將介紹一個純粹的基於JavaScript的解決方案(也就是沒有jQuery),它允許您將任何無聊的元素轉換為可以在頁面上拖動的元素。
繼續~
1.例子
2.Drag Me Up,Scotty!
現在我們已經很好地瞭解了我們將要建立的內容,現在是時候實際建立它了。正如您將在稍後看到的那樣,使任何元素可拖動的程式碼並不是火箭科學。困難的部分只是理解典型拖動操作中涉及的各個階段。
3.拖動操作解剖
假設我們有一個想要拖動或可拖動的元素,
要啟動拖動,我們首先按下元素,
按下這一操作通過滑鼠或者我們的手指。為了簡化術語,我將滑鼠游標或手指(或手寫筆)更一般地稱為指標。
當我們用指標按下元素,我們移動指標到新的位置。
移動過程中經過的每個點,可動元素跟蹤指標的位置並且移動。
一旦移動指標到最後的目的地,我們釋放指標:
當釋放按壓時,我們的可拖動元素停止跟蹤指標並且保持在最後的位置上。拖曳過程結束。
3.敲程式碼了!
我們有理由非常詳細地研究拖動手勢的每個微小步驟。您即將看到的程式碼將所有這些視覺效果和文字轉換為我們的瀏覽器實際理解的內容。
我們的拖動示例的完整HTML,CSS和JavaScript如下所示:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport"
content="width=device-width,
initial-scale=1.0,
user-scalable=no" />
<title>Drag/Drop/Bounce</title>
<style>
#container {
width: 100%;
height: 400px;
background-color : #333;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
border-radius: 7px;
touch-action: none;
}
#item {
width: 100px;
height: 100px;
background-color: rgb(245, 230, 99);
border: 10px solid rgba(136, 136, 136, .5);
border-radius: 50%;
touch-action: none;
user-select: none;
}
#item:active {
background-color: rgba(168, 218, 220, 1.00);
}
#item:hover {
cursor: pointer;
border-width: 20px;
}
</style>
</head>
<body>
<div id="outerContainer">
<div id="container">
<div id="item">
</div>
</div>
</div>
<script>
var dragItem = document.querySelector("#item");
var container = document.querySelector("#container");
var active = false;
var currentX;
var currentY;
var initialX;
var initialY;
var xOffset = 0;
var yOffset = 0;
container.addEventListener("touchstart", dragStart, false);
container.addEventListener("touchend", dragEnd, false);
container.addEventListener("touchmove", drag, false);
container.addEventListener("mousedown", dragStart, false);
container.addEventListener("mouseup", dragEnd, false);
container.addEventListener("mousemove", drag, false);
function dragStart(e) {
if (e.type === "touchstart") {
initialX = e.touches[0].clientX - xOffset;
initialY = e.touches[0].clientY - yOffset;
} else {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
}
if (e.target === dragItem) {
active = true;
}
}
function dragEnd(e) {
initialX = currentX;
initialY = currentY;
active = false;
}
function drag(e) {
if (active) {
e.preventDefault();
if (e.type === "touchmove") {
currentX = e.touches[0].clientX - initialX;
currentY = e.touches[0].clientY - initialY;
} else {
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
}
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, dragItem);
}
}
function setTranslate(xPos, yPos, el) {
el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)";
}
</script>
</body>
</html>
如果您將所有這些內容貼上到HTML文件中,您將在計算機上看到自己的此示例版本。無論如何,花點時間瀏覽一下你看到的一切。HTML和CSS應該看起來很簡單。它們只是幫助我們定義可拖動元素及其所在的容器。指令碼標記內的JavaScript是令人興奮的地方,所以讓我們更仔細地討論它。
(1)變數宣告
在最頂層,我們聲明瞭一些我們將在整個地方使用的變數
var dragItem = document.querySelector("#item");
var container = document.querySelector("#container");
var active = false;
var currentX;
var currentY;
var initialX;
var initialY;
var xOffset = 0;
var yOffset = 0;
dragItem和容器變數儲存對其各自DOM元素的引用。剩下的變數看起來有點神祕。不要過分擔心他們現在所做的事情。我們將在使用它們時講解到。
(2)監聽事件
接下來是我們的事件相關程式碼,用於處理拖動手勢的各種狀態:
container.addEventListener("touchstart",dragStart,false);
container.addEventListener("touchend", dragEnd, false);
container.addEventListener("touchmove", drag, false);
container.addEventListener("mousedown", dragStart, false);
container.addEventListener("mouseup", dragEnd, false);
container.addEventListener("mousemove", drag, false);
第一批事件偵聽器處理觸控事件,第二批事件偵聽器處理滑鼠事件。觸控和滑鼠事件之間的配對如下:touchstart to mousedown,touchend to mouseup,touchmove to mousemove.考慮到這些事件對是相似的,我們將它們分配給同一個事件處理程式。dragStart,dragEnd和drag事件處理程式將負責在滑鼠和觸控場景中拖動元素。
現在,您可能一直聽說觸控事件和滑鼠事件非常相似,您不必像我們在這裡那樣分別明確地分別監聽它們。在90%的情況下,以上說法是正確的。您只能通過監聽滑鼠事件來偽裝觸控行為。其餘10%用於更高階的情況,例如我們的拖動操作。您可以使用的屬性有所不同,因此我們無法跨滑鼠和觸控重複使用相同的程式碼。當我們檢視第一個事件處理程式dragStart函式時,您可以看到這一點。
(3)初始化拖曳
dragStart函式是使我們的拖動手勢實際工作的閘道器,它看起來如下:
function dragStart(e) {
if (e.type === "touchstart") {
initialX = e.touches[0].clientX - xOffset;
initialY = e.touches[0].clientY - yOffset;
} else {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
}
if (e.target === dragItem) {
active = true;
}
}
當mousedown / touchstart事件被監聽到時,將呼叫此事件處理函式。我們要做的第一件事就是設定指標的初始位置(initialX和initialY),我們通過檢測事件是否基於觸控來實現。如果事件是基於觸控的事件(通過檢查touchstart),我們獲取位置的方式是通過我們的事件引數的touches [0]
物件訪問clientX和clientY屬性。對於基於滑鼠的事件,我們的路徑有點不同。我們可以通過直接在事件引數物件上訪問clientX
和clientY
屬性來獲取初始位置。能夠做到這一切的統一方式會很棒,但唉,目前還沒有…
還有一件關於位置的事要說明。計算涉及當前位置以及減去xOffset和yOffset的值。**xOffset和yOffset最初都設定為0,但後續拖動操作不會出現這種情況。**我們將在短時間內瞭解所有定位logc的工作原理。
我們做的最後一件事是檢查我們點選的元素是否是我們想要拖動的元素:
if (e.target === dragItem) {
active = true;
}
我們為什麼這樣做?如果你回到我們的事件監聽器程式碼,請注意我們正在監聽容器上的各種滑鼠和觸控事件,而不是我們正在拖動的元素.
這樣做有幾個原因。主要原因是當您**快速拖動元素時,指標將離開您拖動元素的命中區域。**當發生這種情況時,你的拖曳會突然停止。那真是太尷尬了。我們可以通過監聽被拖動元素的容器上的事件來避免這種情況。無論你多快地拖動你的元素,你幾乎總是在容器的範圍內。這可以確保您的事件仍然觸發,並且您的拖動元素有機會趕上指標所在的位置。
結束這段程式碼,一旦我們將此事件識別為與拖動元素相關聯,我們將活動變數設定為true:
if (e.target === dragItem) {
active = true;
}
唷。誰知道那三行程式碼需要這麼多額外的解釋?
(4)拖曳
當mousemove或者touchmove被觸發時,drag函式被呼叫:
function drag(e) {
if (active) {
e.preventDefault();
if (e.type === "touchmove") {
currentX = e.touches[0].clientX - initialX;
currentY = e.touches[0].clientY - initialY;
} else {
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
}
xOffset = currentX;
yOffset = currentY;
setTranslate(currentX, currentY, dragItem);
}
}
我們要做的第一件事是通過檢查我們在dragStart函式中設定的active變數的值來檢查拖動是否處於活動狀態。在那之後,一帆風順。我們將currentX和currentY的值設定為當前指標位置,當前指標位置是根據我們之前設定的initialX和initialY調整而來的。還記得我們之前看到的xOffset和yOffset變數嗎?他們現在被設定為當前位置。這允許將來的拖動操作從當前位置停止的位置拾取。
這個函式最後做的是設定拖動元素的新位置:
setTranslate(currentX, currentY, dragItem);
關於這個函式:
function setTranslate(xPos, yPos, el) {
el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)";
}
此函式只是設定translate3d變換的快捷方式,我們提供的是我們要設定其位置的元素以及設定轉換的水平/垂直位置。您可以瞭解更多關於為什麼translate3d可能是我們在本文中有效地設定位置的最佳方法之一。
關於設定拖動的元素位置
到目前為止,我們已經發現很多人對元素的位置如何設定感到困惑。我們有setTranslate函式和變數initialX,initialY,xOffset,yOffset,clientX,clientY以及它們都被使用的奇怪方式。設定一些東西的位置不是那麼難,對吧?嗯,通常就是這種情況,但當你丟擲滑鼠和觸控,瀏覽器怪癖,四捨五入等等時,你最終會得到一些比我們這裡更復雜的東西。需要注意的主要是
clientX
和clientY
返回容器元素內指標的絕對位置。offset
變數說明指標相對於拖動元素位置的位置。這樣做是為了確保我們從正確的位置拖動,即使您單擊/點選元素的邊緣也是如此。當我們啟動拖動時,我們不希望不自然地捕捉指標的位置。
(5)結束拖曳
我們程式碼的最後一部分是當mouseup和touchend事件觸發時呼叫的事件處理程式,表示拖動結束:
function dragEnd(e) {
initialX = currentX;
initialY = currentY;
active = false;
}
我們將下一個拖動操作的初始位置設定為當前位置。由於我們的拖動現已完成,因此我們將active
變數設定為false。
4.結論
拖動元素應該更容易,但事實並非如此。實際程式碼本身並不存在困難。**難點是一個非常傳統的HTML / CSS / JS問題,當你開始擴大範圍超出傳統的滑鼠/鍵盤/桌面世界時,事情變得更加複雜。**雖然支援觸控的裝置已經存在很長時間了,但是所有瀏覽器對它們的開發支援仍在進行中。沒關係。如果一切都太容易了,我們都會失業