這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 背景 最近在項目中要實現一個拖拽頭像的移動效果,一直對JS Dom拖拽這一塊不太熟悉,甚至在網上找一個示例,都看得雲里霧裡的,發現遇到最大的攔路虎就是JS Dom各種各樣的距離,讓人頭暈眼花,看到一個距離屬性,大腦中的印象極其模糊,如同有 ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
背景
最近在項目中要實現一個拖拽頭像的移動效果,一直對JS Dom拖拽這一塊不太熟悉,甚至在網上找一個示例,都看得雲里霧裡的,發現遇到最大的攔路虎就是JS Dom各種各樣的距離,讓人頭暈眼花,看到一個距離屬性,大腦中的印象極其模糊,如同有一團霧一樣,不知其確切含義。果然是基礎不牢,地動山搖。今天決心夯實一下基礎,親自動手驗證一遍dom各種距離的含義。
JS Dom各種距離釋義
下麵我們進入正題, 筆者不善於畫圖, 主要是藉助瀏覽器開發者工具,通過獲取的數值給大家說明一下各種距離的區別。
第一個發現 window.devicePixelRatio 的存在
本打算用截圖軟體丈量尺寸,結果發現截圖軟體顯示的屏幕寬度與瀏覽器開發者工具獲取的寬度不一致,這是為什麼呢?
- 截圖軟體顯示的屏幕寬度是1920
- window.screen.width顯示的屏幕寬度是1536
這是怎麼回事?原來在PC端,也存在一個設備像素比的概念。它告訴瀏覽器一個css像素應該使用多少個物理像素來繪製。要說設備像素比,得先說一下像素和解析度這兩個概念。
- 像素 屏幕中最小的色塊,每個色塊稱之為一個像素(Pixel)
- 設備像素比
設備像素比的定義是:
window.devicePixelRatio
=顯示設備物理像素解析度顯示設備CSS像素解析度\frac{顯示設備物理像素解析度}{顯示設備CSS像素解析度}顯示設備CSS像素解析度顯示設備物理像素解析度
根據設備像素比的定義, 如果知道顯示設備橫向的css像素值,根據上面的公式,就能計算出顯示設備橫向的物理像素值。
顯示設備寬度物理像素值= window.screen.width * window.devicePixelRatio;
設備像素比在我的筆記本電腦上顯示的數值是1.25, 代表一個css邏輯像素對應著1.25個物理像素。
我前面的公式計算了一下,與截圖軟體顯示的像素數值一致。這也反過來說明,截圖軟體顯示的是物理像素值。
- window.devicePixelRatio 是由什麼決定的 ?
發現是由筆記本電腦屏幕的縮放設置決定的,如果設置成100%, 此時window.screen.width與筆記本電腦的顯示器解析度X軸方向的數值一致,都是1920(如右側圖所示), 此時屏幕上的字會變得比較小,比較傷視力。
- 邏輯像素是為瞭解決什麼問題?
邏輯像素是為瞭解決屏幕相同,解析度不同的兩台顯示設備, 顯示同一張圖片大小明顯不一致的問題。比如說兩台筆記本都是15英寸的,一個解析度是1920*1080
,一個解析度是960*540
, 在1920*1080
解析度的設備上,每個格子比較小,在960*540
解析度的設備上,每個格子比較大。一張200*200
的圖片,在高分率的設備上看起來會比較小,在低解析度的設備上,看起來會比較大。觀感不好。為了使同樣尺寸的圖片,在兩台屏幕尺寸一樣大的設備上,顯示尺寸看起來差不多一樣大,發明瞭邏輯像素這個概念。規定所有電子設備呈現的圖片等資源尺寸統一用邏輯像素表示。然後在高解析度設備上,提高devicePixelRatio, 比如說設置1920*1080
設備的devicePixelRatio(dpr)等於2, 一個邏輯像素占用兩個格子,在低解析度設備上,比如說在960*540
設備上設置dpr=1, 一個css邏輯像素占一個格子, 這樣兩張圖片在同樣的設備上尺寸大小就差不多了。通常設備上的邏輯像素是等於物理像素的,在高解析度設備上,物理像素是大於邏輯像素數量的。由此也可以看出,物理像素一齣廠就是固定的,而設備的邏輯像素會隨著設備像素比設置的值不同而改變。但圖片的邏輯像素值是不變的。
document.body、document.documentElement和window.screen的寬高區別
差別是很容易辨別的,如下圖所示:
- document.body -- body標簽的寬高
- document.documentElement -- 網頁可視區域的寬高(不包括滾動條)
- window.screen -- 屏幕的寬高
- 網頁可視區域不包括滾動條
如下圖所示,截圖時在未把網頁可視區域的滾動條高度計算在內的條件下, 截圖工具顯示的網頁可視區域高度是168, 瀏覽器顯示的網頁可視區域的高度是167.5, 誤差0.5,由於截圖工具是手動截圖,肯定有誤差,結果表明,網頁可視區域的高度 不包括滾動條高度。寬度同理。
- 屏幕和網頁可視區域的寬高區別如下:
屏幕寬高是個固定值,網頁可視區域寬高會受到縮放視窗影響。
- 屏幕高度和屏幕可用高度區別如下:
屏幕可用高度=屏幕高度-屏幕下方任務欄的高度,也就是:
window.screen.availHeight = window.screen.height - 系統任務欄高度
scrollWidth, scrollLeft, clientWidth關係
scrollWidth(滾動寬度,包含滾動條的寬度)=scrollLeft(左邊捲去的距離)+clientWidth(可見部分寬度); // 同理 scrollHeight(滾動高度,包含滾動條的高度)=scrollTop(上邊捲去的距離)+clientHeight(可見部分高度);
需要註意的是,上面這三個屬性,都取的是溢出元素的父級元素屬性。而不是溢出元素本身。本例中溢出元素是body(document.body),其父級元素是html(document.documentElement)。另外,
溢出元素的寬度(document.body.scrollWidth)=父級元素的寬度(document.documentElement.scrollWidth) - 滾動條的寬度(在谷歌瀏覽器上滾動條的寬度是19px)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> --> <title>JS Dom各種距離</title> <style> html, body { margin: 0; } body { width: 110%; border: 10px solid blue; } .rect { height: 50px; background-color: green; } </style> </head> <body> <div id="rect" class="rect"></div> </body> </html>
元素自身和父級元素的scrollWidth和scrollLeft關係?
從下圖可以看出:
- 元素自身沒有X軸偏移量,元素自身的滾動寬度不包含滾動條
- 父級元素有X軸便宜量, 父級元素滾動寬度包含滾動條
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> --> <title>JS Dom各種距離</title> <style> div { border: 1px solid #000; width: 200px; height: 600px; padding: 10px; background-color: green; margin: 10px; } </style> </head> <body> <div class="rect"> 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 </div> </body> <script> </script> </html>
offsetWidth和clientWidth的關係?
offsetWidth和clientWidth的共同點是都包括 自身寬度+padding , 不同點是offsetWidth包含border。
如下圖所示:
- rect元素的clientWidth=200px(自身寬度) + 20px(左右padding) = 220px
- rect元素的offsetWidth=200px(自身寬度) + 20px(左右padding) + 2px(左右boder) = 222px
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> --> <title>JS Dom各種距離</title> <style> div { border: 1px solid #000; width: 200px; height: 100px; padding: 10px; background-color: green; margin: 10px; } </style> </head> <body> <div class="rect">111111111111111111111111111111111111111111111111</div> </body> <script> </script> </html>
event.clientX,event.clientY, event.offsetX 和 event.offsetY 關係
代碼如下,給rect元素添加一個mousedown事件,列印出事件源的各種位置值。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> --> <title>JS Dom各種距離</title> <style> html, body { margin: 0; } body { width: 200px; padding: 10px; border: 10px solid blue; } .rect { height: 50px; background-color: green; } </style> </head> <body> <div id="rect" class="rect"></div> </body> <script> const rectDom = document.querySelector('#rect'); rectDom.addEventListener('mousedown', ({ offsetX, offsetY, clientX, clientY, pageX, pageY, screenX, screenY }) => { console.log({ offsetX, offsetY, clientX, clientY, pageX, pageY, screenX, screenY }); }) </script> </html>
我們通過y軸方向的高度值,瞭解一下這幾個屬性的含義。 綠色塊的高度是50px, 我們找個特殊的位置(綠色塊的右小角)點擊一下,如下圖所示:
- offsetY=49, 反推出這個值是相對於元素自身的頂部的距離
- clientY=69, body標簽的border-top是10,paiding是10, 反推出這個值是相對網頁可視區域頂部的距離
- screenY=140,目測肯定是基於瀏覽器視窗,
所以它們各自的含義,就很清楚了。
事件源屬性 | 表示的距離 |
---|---|
event.offsetX、event.offsetY | 滑鼠相對於事件源元素(srcElement)的X,Y坐標, |
event.clientX、event.clientY | 滑鼠相對於瀏覽器視窗可視區域的X,Y坐標(視窗坐標),可視區域不包括工具欄和滾動偏移量。 |
event.pageX、event.pageY | 滑鼠相對於文檔坐標的x,y坐標,文檔坐標系坐標 = 視口坐標系坐標 + 滾動的偏移量 |
event.screenX、event.screenY | 滑鼠相對於用戶顯示器屏幕左上角的X,Y坐標 |
- pageX和clientX的關係
我們點擊下圖綠色塊的右下角,把pageX和clientX值列印出來。如下圖所示:
- 可視區域的寬度是360,點擊點的clientX=359(由於是手動點擊,有誤差也正常)
- 水平方向的偏移量是56
- pageX是415,360+56=416,考慮到點擊誤差,可以推算出
ele.pageX = ele.clientX + ele.scrollLeft
getBoundingClientRect獲取的top,bottom,left,right的含義
從下圖可以看出,上下左右這四個屬性,都是相對於瀏覽器可視區域左上角而言的。
從下圖可以看出,當有滾動條出現的時候,right的值是359.6,而不是360+156(x軸的偏移量), 說明通過getBoundingClientRect獲取的屬性值是不計算滾動偏移量的,是相對瀏覽器可視區域而言的。
想移動元素,mouse和drag事件怎麼選?
mouse事件相對簡單,只有mousedown(開始),mousemove(移動中),mouseup(結束)三種。與之對應的移動端事件是touch事件,也是三種touchstart(手指觸摸屏幕), touchmove(手指在屏幕上移動), touchend(手指離開屏幕)。
相對而言, drag事件就要豐富一些。
- 被拖拽元素事件
事件名 | 觸發時機 | 觸發次數 |
---|---|---|
dragstart | 拖拽開始時觸發一次 | 1 |
drag | 拖拽開始後反覆觸發 | 多次 |
dragend | 拖拽結束後觸發一次 | 1 |
- 目標容器事件
事件名 | 觸發時機 | 觸發次數 |
---|---|---|
dragenter | 被拖拽元素進入目標時觸發一次 | 1 |
dragover | 被拖拽元素在目標容器範圍內時反覆觸發 | 多次 |
drop | 被拖拽元素在目標容器內釋放時(前提是設置了dropover事件) | 1 |
想要移動一個元素,該如何選擇這兩種事件類型呢? 選擇依據是:
類型 | 選擇依據 |
---|---|
mouse事件 | 1. 要求絲滑的拖拽體驗 2. 無固定的拖拽區域 3. 無需傳數據 |
drag事件 | 1. 拖拽區域有範圍限制 2. 對拖拽流暢性要求不高 3. 拖拽時需要傳數據 |
現在讓我們寫個拖拽效果
光說不練假把式, 掃清了學習障礙後,讓我們自信滿滿地寫一個相容PC端和移動端的拖動效果。不積跬步無以至千里,幻想一口吃個胖子,是不現實的。這一點在股市上體現的淋漓盡致。都是有耐心的人賺急躁的人的錢。所以,要我們沉下心來,打牢基礎,硬骨頭啃一點就會少一點,步步為營,穩扎穩打,硬骨頭也會被啃成渣。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>拖拽水潭</title> <style> .water { position: absolute; width: 100px; height: 100px; border-radius: 100px; cursor: grab; z-index: 10; } </style> </head> <body> <img class="water" src="./water.png" alt="" /> </body> <script> let evtName = getEventName(); // 確保圖片載入完 window.onload = () => { let offsetX = 0, offsetY = 0; // const water = document.querySelector(".water"); const moveAt = ({ pageX, pageY }) => { water.style.cssText = `left:${pageX - offsetX}px;top:${pageY - offsetY}px;`; }; water.addEventListener(evtName.start, (event) => { // 足球的偏移距離是針對球體錶面, 不能把球體錶面到球內滑鼠位置的距離算在內 // 否則足球一移動,就會出現向下,向右不自然的偏移 offsetX = event.clientX - water.getBoundingClientRect().left; offsetY = event.clientY - water.getBoundingClientRect().top; // 設置初始偏移 moveAt(event); // 監聽滑鼠相對於可視視窗移動的距離 document.addEventListener(evtName.move, moveAt); }); // 拖動停止時,釋放document上綁定的移動事件 // 不然移動滑鼠,不拖拽時白白產生性能開銷 water.addEventListener(evtName.end, () => { document.removeEventListener(evtName.move, moveAt); }); }; // 區分是移動端還是PC端移動事件 function getEventName() { if ("ontouchstart" in window) { return { start: "touchstart", move: "touchmove", end: "touchend", }; } else { return { start: "mousedown", move: "mousemove", end: "mouseup", }; } } </script> </html>
彩蛋
在chrome瀏覽器上發現一個奇怪的現象,設置的border值是整數,計算出來的值卻帶有小數
而當border值是4的整數倍的時候,計算值是正確的
看了這篇文章解釋說,瀏覽器可能只能渲染具有整數物理像素的border值,不是整數物理像素的值時,計算出的是近似border值。這個解釋似乎講得通,在設備像素比是window.devicePixelRatio=1.25的情況下, 1px對應的是1.25物理像素,1.25*4的倍數
才是整數,所以設置的邏輯像素是4的整數倍數,顯示的渲染計算值與設置值一致,唯一讓人不理解的地方,為什麼padding,margin,width/height卻不遵循同樣的規則。