最近忽然看到了有關圖片懶加載的問題,大體意思就是初始狀態下頁面只加載瀏覽器可視區域的圖片,剩餘圖片在當瀏覽器可視區域滾動到其位置時纔開始加載。貌似如今許多大型網站都有實現懶加載,因此我便就此問題思考了一下。首先第一個問題是瀏覽器沒有相關的 API 方法能夠檢測某個元素是否在可視區域,那麼就只能咱們人工計算,因此這裏就涉及到了元素長寬,滾動條位置的知識。本文涉及的到的知識有元素長寬 clientWidth/offsetWidth/scrollWidth 的區別、以及 clientTop/offsetTop/scrollTop 的區別,並給了獲取元素座標的源代碼。javascript
一般你們獲取元素的長寬的時候都會使用一些框架封裝好的方法,好比 jQuery.prototype.width() ,這些框架使用起來方便快捷,不過其中涉及到的知識仍是很是多的,關於元素的長寬,有多種的獲取方法,其表明的實際意義也是不一樣的。html
簡單來講能夠使用下列公式:java
clientWidth = width(可視區) + paddingnode
offsetWidth = width(可視區) + padding + border瀏覽器
scrollWidth = width(內容區) 緩存
假設有咱們如下一個元素:框架
1 #test { 2 width: 100px; 3 height: 100px; 4 margin: 10px; 5 border: 10px solid #293482; 6 padding: 10px; 7 background-color: yellow; 8 overflow: auto; 9 }
clientWidth | offsetWidth | scrollWidth |
以上 DEMO 是常規狀況下的區別,下面加上一個滾動條咱們們再來觀察如下:函數
clientWidth | offsetWidth | scrollWidth |
注意這裏不包括滾動條的長度工具 |
這裏實際上至關於內容的寬度網站
|
|
咱們使用如下公式:
clientTop = border
offsetTop = 元素邊框外圍至父元素邊框內圍
scrollTop = 元素可視區域頂部至實際內容區域的頂部
給定如下兩個元素 container 和 test
1 #container { 2 background-color: #F08D8D; 3 padding: 10px; 4 } 5 #test { 6 position: relative; 7 top: 10px; 8 width: 100px; 9 height: 100px; 10 margin: 20px; 11 border: 15px solid #293482; 12 padding: 10px; 13 background-color: yellow; 14 }
clientTop | offsetTop | scrollTop |
有了以上知識基礎以後,咱們如今須要考慮的問題是,如何獲取頁面元素的絕對位置,也就是在文檔流內容區的位置。咱們知道,元素的 offsetTop 屬性能夠獲取當前元素邊框外圍至父元素邊框內圍的的距離,clientTop 能夠獲取元素邊框的寬度。那麼如今用一個遞歸的公式就能夠求得當前元素在頁面中的絕對位置:
Element.absoluteTop = Element.parent.absoluteTop + Element.offsetTop + Element.clientTop;
同理,咱們用參照元素的長寬減去 left 和 top 和定位,便可獲得 right 和 bottom 的定位;
因此咱們能夠編寫如下工具來獲取元素的絕對位置,也就是在內容區的定位(參照元素必須是目標元素的祖先元素):
1 var Position = {}; 2 (function () { 3 Position.getAbsolute = function (reference, target) { 4 //由於咱們會將目標元素的邊框歸入遞歸公式中,這裏先減去對應的值 5 var result = { 6 left: -target.clientLeft, 7 top: -target.clientTop 8 } 9 var node = target; 10 while(node != reference && node != document){ 11 result.left = result.left + node.offsetLeft + node.clientLeft; 12 result.top = result.top + node.offsetTop + node.clientTop; 13 node = node.parentNode; 14 } 15 if(isNaN(reference.scrollLeft)){ 16 result.right = document.documentElement.scrollWidth - result.left; 17 result.bottom = document.documentElement.scrollHeight - result.top; 18 }else { 19 result.right = reference.scrollWidth - result.left; 20 result.bottom = reference.scrollHeight - result.top; 21 } 22 return result; 23 } 24 })();
此方法能夠獲取一個元素相對於一個父元素的定位,若是要獲取元素在整張頁面,直接傳入 document 便可:
1 Position.getAbsolute(document, targetNode); //{left: left, right: right, top: top, bottom: bottom}
在上一小節中,咱們封裝了一個函數,這個函數能夠用來獲取一個元素的相對於一個祖先元素的絕對定位座標,在這一小節中,咱們來獲取元素相對於瀏覽器窗口可視區域的定位座標。在上一個函數中,咱們能夠獲取一個元素在 document 當中的定位,還記得咱們在第二小節中的 scrollTop 屬性嗎?該屬性能夠獲取滾動窗口可視區域頂端距離內容區頂端的距離,咱們用元素的絕對定位座標減去 document 的滾動定位就是咱們想要的瀏覽器窗口定位啦(相對於瀏覽器左上角):
ViewportTop = Element.absoluteTop - document.body.scrollTop;
這裏須要注意一個兼容性的問題,在 Chrome 中能夠用 document.body.scrollTop 和 window.pageYOffset,IE 7/8 只能經過 document.documentElement.scrollTop 獲取, FireFox 和 IE9+ 能夠用 document.documentElement.scrollTop 和 window.pageYOffset 獲取,Safari 須要 window.pageYOffset 獲取。因此這裏咱們須要作一下瀏覽器兼容:
scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
注意這裏的順序,在 IE7/8 中 window.pageYOffset 是 undefined ,document.body.scrollTop 在任何瀏覽器中都有,只是不支持的值爲 0,若是表達式返回 undefined ,會影響後面的計算操做。而 || 運算符是一個短路取真運算符,因此咱們要全部瀏覽器都有的 document.body.scrollTop 方法放在最後,關於 || 運算符的問題,能夠參考 《探尋 JavaScript 邏輯運算符(與、或)的真諦》。
咱們在剛纔的工具上添加一個方法:
1 var Position = {}; 2 (function () { 3 Position.getAbsolute = function (reference, target) { 4 var result = { 5 left: -target.clientLeft, 6 top: -target.clientTop 7 } 8 var node = target; 9 while(node != reference && node != document){ 10 result.left = result.left + node.offsetLeft + node.clientLeft; 11 result.top = result.top + node.offsetTop + node.clientTop; 12 node = node.parentNode; 13 } 14 if(isNaN(reference.scrollLeft)){ 15 result.right = document.documentElement.scrollWidth - result.left; 16 result.bottom = document.documentElement.scrollHeight - result.top; 17 }else { 18 result.right = reference.scrollWidth - result.left; 19 result.bottom = reference.scrollHeight - result.top; 20 } 21 return result; 22 } 23 Position.getViewport = function (target) { 24 var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; 25 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft; 26 var absolutePosi = this.getAbsolute(document, target); 27 var Viewport = { 28 left: absolutePosi.left - scrollLeft, 29 top: absolutePosi.top - scrollTop, 30 } 31 return Viewport; 32 } 33 })();
經過 Position.getViewport 方法能夠獲取元素相對於瀏覽器窗口的定位:
1 Postion.getViewport(targetNode); //{left: left, top: top}
在上面的幾個方法中,咱們能夠獲取元素的文檔流定位和視窗定位,不過這仍是不能判斷一個元素是否在可視區域內,由於視窗定位能夠是很是大的數字,這樣元素就在視窗的後面。這裏咱們須要使用瀏覽器視窗高度 window.innerHeight 屬性,在 IE8 如下須要用 document.documentElement.clientHeight 來獲取。
windowHeight = window.innerHeight || document.documentElement.clientHeight;
如今,咱們用窗口的高度,減去相對於瀏覽器窗口的定位,便可獲取相對於瀏覽器窗口右下角的定位;
1 var Position = {}; 2 (function () { 3 Position.getAbsolute = function (reference, target) { 4 var result = { 5 left: -target.clientLeft, 6 top: -target.clientTop 7 } 8 var node = target; 9 while(node != reference && node != document){ 10 result.left = result.left + node.offsetLeft + node.clientLeft; 11 result.top = result.top + node.offsetTop + node.clientTop; 12 node = node.parentNode; 13 } 14 if(isNaN(reference.scrollLeft)){ 15 result.right = document.documentElement.scrollWidth - result.left; 16 result.bottom = document.documentElement.scrollHeight - result.top; 17 }else { 18 result.right = reference.scrollWidth - result.left; 19 result.bottom = reference.scrollHeight - result.top; 20 } 21 return result; 22 } 23 Position.getViewport = function (target) { 24 var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; 25 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft; 26 var windowHeight = window.innerHeight || document.documentElement.offsetHeight; 27 var windowWidth = window.innerWidth || document.documentElement.offsetWidth; 28 var absolutePosi = this.getAbsolute(document, target); 29 var Viewport = { 30 left: absolutePosi.left - scrollLeft, 31 top: absolutePosi.top - scrollTop, 32 right: windowWidth - (absolutePosi.left - scrollLeft), 33 bottom: windowHeight - (absolutePosi.top - scrollTop) 34 } 35 return Viewport; 36 } 37 })();
如今咱們使用 Position.getViewport(targetNode) 方法能夠獲取元素左上角相對於窗口4個方向的定位:
1 Position.getViewport(targetNode); //{left: left, top: top, right: right, bottom: bottom}
有了這個方法,如今就能夠真正的判斷元素是否在可視區域內了:
1 var Position = {}; 2 (function () { 3 Position.getAbsolute = function (reference, target) { 4 //由於咱們會將目標元素的邊框歸入遞歸公式中,這裏先減去對應的值 5 var result = { 6 left: -target.clientLeft, 7 top: -target.clientTop 8 } 9 var node = target; 10 while(node != reference && node != document){ 11 result.left = result.left + node.offsetLeft + node.clientLeft; 12 result.top = result.top + node.offsetTop + node.clientTop; 13 node = node.parentNode; 14 } 15 if(isNaN(reference.scrollLeft)){ 16 result.right = document.documentElement.scrollWidth - result.left; 17 result.bottom = document.documentElement.scrollHeight - result.top; 18 }else { 19 result.right = reference.scrollWidth - result.left; 20 result.bottom = reference.scrollHeight - result.top; 21 } 22 return result; 23 } 24 Position.getViewport = function (target) { 25 var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; 26 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft; 27 var windowHeight = window.innerHeight || document.documentElement.offsetHeight; 28 var windowWidth = window.innerWidth || document.documentElement.offsetWidth; 29 var absolutePosi = this.getAbsolute(document, target); 30 var Viewport = { 31 left: absolutePosi.left - scrollLeft, 32 top: absolutePosi.top - scrollTop, 33 right: windowWidth - (absolutePosi.left - scrollLeft), 34 bottom: windowHeight - (absolutePosi.top - scrollTop) 35 } 36 return Viewport; 37 } 38 Position.isViewport = function (target) { 39 var position = this.getViewport(target); 40 //這裏須要加上元素自身的寬高,由於定位點是元素的左上角 41 if(position.left + target.offsetWidth < 0 || position.top + target.offsetHeight < 0){ 42 return false; 43 } 44 if(position.bottom < 0 || position.right < 0){ 45 return false; 46 } 47 return true; 48 } 49 })();
判斷理由很簡單,若是有一邊的定位是負值,那麼元素就不在視窗內。
1 Position.getAbsolute(document, targetNode); //獲取元素在文檔流中的絕對座標 2 Position.getViewport(targetNode); //獲取元素相對於瀏覽器視窗的座標 3 Position.isViewport(targetNode); //判斷元素是否在瀏覽器視窗內
瀏覽器兼容性:
Chrome | FireFox | IE | Safari | Edge |
Support | Support | IE8+ Support | Support | Support |
IE7 也能夠使用,不過結果可能會有一點差別。
在文章的開始,咱們提到過圖片懶加載的問題,那麼具體須要怎麼實現呢?這裏只是給出一個思路:
初始狀態下不設置 img 的 src,將圖片的真實 url 緩存在 Img 標籤上,咱們能夠設置爲 data-src ,這樣圖片就不會加載了,隨後給鼠標添加 mousescroll 事情,每次鼠標滾動的時候將進入可視區域的圖片的 src 還原,這樣也就實現了圖片懶加載效果。不過初始狀態下須要將頁面可視區域的圖片先加載出來。
阮一峯 — 用 JavaScript 獲取元素頁面元素位置
張媛媛 — js實現一個圖片懶加載插件