個性簽名: 生如夏花,逝如冬雪;人生如此,何悔何怨。css
前言: 常常須要計算元素的大小或者所在頁面的位置,offsetWidth,clientWidth,scrollWidth,scrollTop這幾個關鍵字的出現更是屢見不鮮,每次碰到都須要事先實驗一番。爲了下次開發提升效率。在這裏一次性作個總結,以用來判斷元素是否在可視區域以及用原生js簡單實現懶加載。文末有個簡單的懶加載實現的demo,有須要的能夠看一下。html
目錄git
工欲善其事,必先利其器。在判斷元素是否在可視區域實現簡單的原生懶加載前,咱們先簡單回顧下如下幾個關鍵的概念。程序員
ps: 若是你對這些概念已經比較熟悉了,能夠直接跳到第五點查看關鍵代碼示例。github
偏移量(offset dimension),元素的可見大小由其高度、寬度決定,包括全部內邊距、滾動條和邊框大小(注意,不包括外邊距)。經過下列4個屬性能夠取得元素的偏移量。api
偏移量 | 概念 | 公式 |
---|---|---|
offsetHeight | 元素在垂直方向上佔用的空間大小,以像素計。包括元素的高度、(可見的) 水平滾動條的高度、上邊框高度和下邊框高度。 | offsetHeght = content + padding + border + scrollX |
offsetWidth | 元素在水平方向上佔用的空間大小,以像素計。包括元素的寬度、(可見的)垂 直滾動條的寬度、左邊框寬度和右邊框寬度。 | offsetWidth = content + padding + border + scrollY |
offsetLeft | 元素的左外邊框至**包含元素的左內邊框之間的像素距離。 | |
offsetTop | 元素的上外邊框至包含元素的上內邊框之間的像素距離。 |
其中,offsetLeft 和 offsetTop 屬性與包含元素有關,包含元素的引用保存在 offsetParent 屬性中。offsetParent 屬性不必定與 parentNode 的值相等。瀏覽器
以下圖顯示 微信
注意: 全部這些偏移量屬性都是隻讀的,並且每次訪問它們都須要從新計算。所以,應該儘可能避免重複訪問這些屬性;若是須要重複使用其中某些屬性的值,能夠將它們保 存在局部變量中,以提升性能。app
這也是上篇文章文字跑馬燈項目中(戳此跳轉),爲何增長padding後,textWidth須要從新獲取的緣由 wordpress
小結
偏移量: 只讀屬性;包括滾動條和邊框,不包括外邊距。
客戶區大小是隻讀的,每次訪問都要從新計算的。
客戶區大小 | 概念 | 公式 |
---|---|---|
clientWidth | clientWidth 屬性是元素內容區寬度加 上左右內邊距寬度; | clientWidth = content + padding |
clientHeight | 元素內容區高度加上上下內邊距高度 | clientHeight = content + padding |
最經常使用到這些屬性的狀況,就是肯定瀏覽器視口大小的時候(在 IE7 以前的版本中)。以下面的例子所示:
function getViewport(){
// 檢查 document.compatMode 屬性,以肯定瀏覽器是否運行在混雜模式。
// Safari3.1 以前的版本不支持這個屬性,所以就會自動執行 else 語句
if (document.compatMode == "BackCompat"){
return {
width: document.body.clientWidth,
height: document.body.clientHeight
};
} else {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
};
}
}
複製代碼
小結
客戶區大小: 只讀屬性;不包括滾動條和邊框,不包括外邊距。
概念 | |
---|---|
scrollHeight | 在沒有滾動條的狀況下,元素內容的總高度。 |
scrollWidth | 在沒有滾動條的狀況下,元素內容的總寬度。 |
scrollLeft | 被隱藏在內容區域左側的像素數。經過設置這個屬性能夠改變元素的滾動位置。 |
scrollTop | 被隱藏在內容區域上方的像素數。經過設置這個屬性能夠改變元素的滾動位置。 |
scrollWidth 和 scrollHeight 主要用於肯定元素內容的實際大小。
scrollLeft 和 scrollTop屬性既能夠肯定元素當前滾動的狀態,也能夠設置元素的滾動位 置。在元素還沒有被滾動時,這兩個屬性的值都等於 0。若是元素被垂直滾動了,那麼 scrollTop 的值 會大於 0,且表示元素上方不可見內容的像素高度。若是元素被水平滾動了,那麼 scrollLeft 的值會 大於 0,且表示元素左側不可見內容的像素寬度。這兩個屬性都是能夠設置的,所以將元素的 scrollLeft 和 scrollTop 設置爲 0,就能夠重置元素的滾動位置。好比:上篇文章文字跑馬燈項目中scrollLeft的使用(戳此跳轉)
小結
只讀屬性,不包括滾動條、border。
getBoundingClientRect
getBoundingClientRect的兼容性寫法:
對於不支持 getBoundingClientRect()的瀏覽器,能夠經過其餘手段取得相同的信息。通常來 說,right 和 left 的差值與 offsetWidth 的值相等,而 bottom 和 top 的差值與 offsetHeight 相等。綜合上述,就能夠建立出下面這個跨瀏覽器的函數:
function getElementLeft(element){
var actualLeft = element.offsetLeft;
var current = element.offsetParent;
while (current !== null){
actualLeft += current.offsetLeft;
current = current.offsetParent;
}
return actualLeft;
}
function getElementTop(element){
var actualTop = element.offsetTop;
var current = element.offsetParent;
while (current !== null){
actualTop += current. offsetTop;
current = current.offsetParent;
}
return actualTop;
}
function getBoundingClientRect(element) {
var scrollTop = document.documentElement.scrollTop;
var scrollLeft = document.documentElement.scrollLeft;
if (element.getBoundingClientRect) {
if (typeof arguments.callee.offset != "number") {
var temp = document.createElement("div");
temp.style.cssText = "position:absolute;left:0;top:0;"; document.body.appendChild(temp);
arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop; document.body.removeChild(temp);
temp = null;
}
var rect = element.getBoundingClientRect();
var offset = arguments.callee.offset;
return {
left: rect.left + offset,
right: rect.right + offset,
top: rect.top + offset,
bottom: rect.bottom + offset
};
} else {
var actualLeft = getElementLeft(element);
var actualTop = getElementTop(element);
return {
left: actualLeft - scrollLeft,
right: actualLeft + element.offsetWidth - scrollLeft,
top: actualTop - scrollTop,
bottom: actualTop + element.offsetHeight - scrollTop
}
}
}
複製代碼
知道了元素的大小以及所位於的區域外,咱們能夠作些什麼呢?咱們能夠經過上面學到的知識點來檢測元素是否在可視區域,再說大一點,這也是懶加載圖片的實現原理。
公式: el.offsetTop - document.documentElement.scrollTop <= viewPortHeight
function isInViewPortOfOne (el) {
// viewPortHeight 兼容全部瀏覽器寫法
const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const offsetTop = el.offsetTop
const scrollTop = document.documentElement.scrollTop
const top = offsetTop - scrollTop
console.log('top', top)
// 這裏有個+100是爲了提早加載+ 100
return top <= viewPortHeight + 100
}
複製代碼
公式: el.getBoundingClientReact().top <= viewPortHeight
其實, el.offsetTop - document.documentElement.scrollTop = el.getBoundingClientRect().top, 利用這點,咱們能夠用下面代碼代替方法一
function isInViewPortOfTwo (el) {
const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const top = el.getBoundingClientRect() && el.getBoundingClientRect().top
console.log('top', top)
return top <= viewPortHeight + 100
}
複製代碼
公式: intersectionRatio > 0 && intersectionRatio <= 1
// 定義一個交叉觀察器
const io = new IntersectionObserver(ioes => {
ioes.forEach(ioe => {
const el = ioe.target
const intersectionRatio = ioe.intersectionRatio
if (intersectionRatio > 0 && intersectionRatio <= 1) {
loadImg(el)
io.unobserve(el)
}
el.onload = el.onerror = () => io.unobserve(el)
})
})
// 執行交叉觀察器
function isInViewPortOfThree (el) {
io.observe(el)
}
複製代碼
在兼容性方面,咱們知道越原始的方法兼容性是最好的,那麼第二種方法和第三種方法可否代替第三種方法呢?咱們來看看。
從caniuse的數據來看,getBoundingClientReact的適配狀況很樂觀了。
因此,若是在移動端和桌面端都要作兼容適配的話,方法二徹底能夠代替方法一進行適配了。若是僅僅是桌面端適配(好比運營後臺),咱們或許能夠嘗試下新的IntersectionObserver方法,畢竟IntersectionObserver裏面還有更多豐富的功能等着咱們去體驗呢。
有時,咱們但願某些靜態資源(好比圖片),只有用戶向下滾動,它們進入視口時才加載,這樣能夠節省帶寬,提升網頁性能。這就叫作"惰性加載",也稱爲懶加載。
惰性加載預覽DEMO(放入你的本地圖片便可經過更換不一樣方法實現懶加載)
------------------------- 華麗的分割線 -----------------------------
關於我
一枚持證理財規劃師的程序員
感謝如下資料提供的參考信息
JavaScript高級程序設計
2019.2.22 更新
連接:戳這裏
內容:document.scrollingElement代替document.documentElement.scrollTop 和document.body.scrollTop
document.scrollingElement一統江湖 :
在桌面端document.scrollingElement就是document.documentElement;
在移動端document.scrollingElement就是document.body。
好處:避免了document.documentElement.scrollTop = 0(桌面端);document.body.scrollTop = 0(移動端);的兼容性使用