<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【W(wǎng)eb技術】906- 徹底玩轉圖片懶加載及底層實現(xiàn)原理

          共 9178字,需瀏覽 19分鐘

           ·

          2021-03-26 08:41

          前言

          圖片懶加載其實已經(jīng)是一個近乎“爛大街”的詞語了,在大大小小的面試中也會被頻繁的問到,我在之前的面試中也被問到了圖片懶加載的原因、實現(xiàn)方式及底層原理,但由于自己平時很少做“圖片”相關的處理,對于“懶加載”也是知之甚少,所以在面試中問答的也不是很好。

          今天,我將首先從瀏覽器底層渲染機制來剖析為什么要去做圖片懶加載,之后我將帶大家一起來看下目前主流的幾種實現(xiàn)圖片懶加載的方式及其實現(xiàn)原理,最后會做一個展望。

          為什么要做圖片懶加載

          要問答這個問題,首先我們先來看下瀏覽器的底層渲染機制:

          1、構建 DOM 樹

          2、樣式計算

          3、布局階段

          4、分層

          5、繪制

          6、分塊

          7、光柵化

          8、合成

          而在構建DOM的過程中如果遇到img在新老版本的chrome中表現(xiàn)又是不一樣的:

          • 老版本:阻塞 DOM 渲染
          • 新版本:雖然不會阻塞 DOM 渲染,但每一個圖片請求都會占用一個 HTTP,而且 Chrome 最多允許對同一個 Host 同時建立六個 TCP 連接

          當你打開一個網(wǎng)站時,瀏覽器會做許多工作,這其中包括下載各種可能用到的資源,然后渲染呈現(xiàn)在你面前,假設你的網(wǎng)站有大量的圖片,那么加載的過程是很耗時的,尤其像那些電商類需要大量圖片的網(wǎng)站,可想而知,網(wǎng)站的初始加載時間會很長,再加上網(wǎng)絡等其它影響,用戶體驗會很差。

          相信你經(jīng)常遇到過一個網(wǎng)站卡在某個地方,一直在加載,這種體驗很不好。我們都希望一輸入網(wǎng)址,頁面立馬就呈現(xiàn)在眼前。

          總結一下就是:直接全部加載的話會減緩渲染速度,產(chǎn)生白屏等進而影響用戶體驗。

          基于原生 js 實現(xiàn)圖片懶加載

          相關 API

          先來看幾個后面會用到的API

          document.documentElement.clientHeight

          獲取屏幕可視區(qū)域的高度。

          圖片來源MDN[1]

          element.offsetTop

          獲取元素相對于文檔頂部的高度。

          圖片來源阮一峰博客[2]

          document.documentElement.scrollTop

          獲取瀏覽器窗口頂部與文檔頂部之間的距離,也就是滾動條滾動的距離。

          圖片來源Seven's Blog

          思路分析

          通過上面三個 API,我們獲得了三個值:可視區(qū)域的高度、元素相對于其父元素容器頂部的距離、瀏覽器窗口頂部與容器元素頂部的距離也就是滾動條滾動的高度。

          雖然這幾個API很簡單,但是單純的去說還是有點抽象,這里我們還是用圖來展示一下:

          看完上面這張圖片,我想你已經(jīng)明白了:如果滿足offsetTop-scroolTop<clientHeight,則圖片進入了可視區(qū)內,我們就去請求進入可視區(qū)域的圖片。

          代碼實現(xiàn)

          基于上面的分析,我們很容易就可以寫出如下代碼:

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <meta http-equiv="X-UA-Compatible" content="ie=edge">
              <title>基于原生 js 實現(xiàn)圖片懶加載</title>
              <style>
                  img {
                      display: block;
                      width100%;
                      height300px;
                      margin-bottom20px;
                  }
              
          </style>
          </head>
          <body>
              <img src="./img/1.png" alt="">
              <img src="./img/2.png" alt="">
              <img src="./img/3.png" alt="">
              <img src="./img/4.png" alt="">
              <img src="./img/5.png" alt="">
              <img src="./img/6.png" alt="">
              <img src="./img/7.png" alt="">
              <img src="./img/8.png" alt="">
          </body>
          <script>
                  var imgs = document.querySelectorAll('img');

                  //offsetTop是元素與offsetParent的距離,循環(huán)獲取直到頁面頂部
                  function getRealTop(e{
                      var realTop = e.offsetTop;
                      while(e = e.offsetParent) {
                          realTop += e.offsetTop;
                      }
                      return realTop;
                  }

                  function lazyLoad(imgs{
                      var H = document.documentElement.clientHeight;//獲取可視區(qū)域高度
                      var S = document.documentElement.scrollTop || document.body.scrollTop;
                      for (var i = 0; i < imgs.length; i++) {
                          if (H + S > getRealTop(imgs[i])) {
                              imgs[i].src = imgs[i].getAttribute('src');
                          }
                      }
                  }

                  window.onload = window.onscroll = function (//onscroll()在滾動條滾動的時候觸發(fā)
                      lazyLoad(imgs);
                  }
          </script>
          </html>

          但上面的代碼如果你在lazyLoad中打印,你會發(fā)現(xiàn)滾動條上下滾動時,lazyLoad會被頻繁調用,造成很大的性能損失,這里我們可以給事件加上節(jié)流throttle

          基于 getBoundingClientRect()實現(xiàn)圖片懶加載

          先來了解一下這個API吧:

          getBoundingClientRect()用于獲得頁面中某個元素的左,上,右和下分別相對瀏覽器視窗的位置。getBoundingClientRect()DOM元素到瀏覽器可視范圍的距離(不包含頁面看不見的部分)。

          該函數(shù)返回一個rectObject對象,該對象有 6 個屬性:top, left, bottom, right, width, height;這里的topleftcss中的理解很相似,widthheight是元素自身的寬高,但是right,bottomcss中的理解有點不一樣。right是指元素右邊界距窗口最左邊的距離,bottom是指元素下邊界距窗口最上面的距離。

          思路分析

          通過這個 API,我們就很容易獲取img元素相對于視口的頂點位置rectObject.top,只要這個值小于瀏覽器的高度window.innerHeight就說明進入可視區(qū)域:

          function isInSight(el){
            const bound = el.getBoundingClientRect();
            const clientHeight = window.innerHeight;
            return bound.top <= clientHeight;
          }

          代碼實現(xiàn)

          這里結合第一種實現(xiàn)方式,做下改造,就得到了:

          function loadImg(el){
           if(!el.src){
             const source = el.getAttribute('src');;
             el.src = source;
           }
          }
          function checkImgs(){
            const imgs = document.querySelectorAll('img');
            Array.from(imgs).forEach(el =>{
              if (isInSight(el)){
                loadImg(el);
              }
            })
          }
          window.onload = function(){
            checkImgs();
          }
          document.onscroll = function ({
            checkImgs();
          }

          基于 IntersectionObserver 實現(xiàn)圖片懶加載

          概念

          同樣,還是先來看一下概念。

          這里我們參考阮一峰大佬關于IntersectionObserver API[3]的介紹。

          我們在平時的開發(fā)中,常常需要了解某個元素是否進入了"視口"(viewport),即用戶能不能看到它。

          上圖的綠色方塊不斷滾動,頂部會提示它的可見性。

          傳統(tǒng)的實現(xiàn)方法是,監(jiān)聽到scroll事件后,調用目標元素(綠色方塊)的getBoundingClientRect()方法,得到它對應于視口左上角的坐標,再判斷是否在視口之內。這種方法的缺點是,由于scroll事件密集發(fā)生,計算量很大,容易造成性能問題。

          目前有一個新的 IntersectionObserver API,可以自動"觀察"元素是否可見,Chrome 51+ 已經(jīng)支持。由于可見(visible)的本質是,目標元素與視口產(chǎn)生一個交叉區(qū),所以這個 API 叫做交叉觀察器

          使用

          它的用法也非常簡單。

          var io = new IntersectionObserver(callback, option);

          上面代碼中,IntersectionObserver是瀏覽器原生提供的構造函數(shù),接受兩個參數(shù):callback是可見性變化時的回調函數(shù),option是配置對象(該參數(shù)可選)。

          構造函數(shù)的返回值是一個觀察器實例。實例的observe方法可以指定觀察哪個 DOM 節(jié)點。

          // 開始觀察
          io.observe(document.getElementById('container'));

          // 停止觀察
          io.unobserve(element);

          // 關閉觀察器
          io.disconnect();

          上面代碼中,observe的參數(shù)是一個 DOM 節(jié)點對象。

          如果要觀察多個節(jié)點,就要多次調用這個方法。

          io.observe(elementA);
          io.observe(elementB);

          代碼實現(xiàn)

          看完相關的API,下面就讓我們基于IntersectionObserver來實現(xiàn)圖片懶加載:

          const imgs = document.querySelectorAll('img'//獲取所有待觀察的目標元素
          var options = {}
          function lazyLoad(target{
            const observer = new IntersectionObserver((entries, observer) => {
              entries.forEach(entrie => {
                if (entrie.isIntersecting) {
                  const img = entrie.target;
                  const src = img.getAttribute('src');
                  img.setAttribute('src', src)
                  observer.unobserve(img); // 停止監(jiān)聽已開始加載的圖片
                }

              })
            }, options);
            observer.observe(target)
          }

          imgs.forEach(lazyLoad)

          img.loading=lazy

          最后這種相對就簡單很多了,它是 Chrome 自帶的原生 lazyload 屬性。我們先來看下他在各大瀏覽器的支持程度:

          其實支持程度還不是特別好,我們你的應用對于瀏覽器兼容性要求比較高的話,建議還是先觀望一波~

          它的使用也非常簡單,如標題所示:

          <img src="example.jpg" loading="lazy" alt="zhangxinxu" width="250" height="150">

          關于原生懶加載 loading=”lazy”的更多介紹可以參考張鑫旭大佬的瀏覽器 IMG 圖片原生懶加載 loading=”lazy”實踐指南[4]。

          參考資料

          [1]

          MDN: https://developer.mozilla.org/zh-CN/docs/Web/API/Element/clientHeight

          [2]

          阮一峰博客: http://www.ruanyifeng.com/blog/2009/09/find_element_s_position_using_javascript.html

          [3]

          IntersectionObserver API: http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html

          [4]

          瀏覽器 IMG 圖片原生懶加載 loading=”lazy”實踐指南: https://www.zhangxinxu.com/wordpress/2019/09/native-img-loading-lazy/


          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設計模式 重溫系列(9篇全)
          4. 正則 / 框架 / 算法等 重溫系列(16篇全)
          5. Webpack4 入門(上)|| Webpack4 入門(下)
          6. MobX 入門(上) ||  MobX 入門(下)
          7. 100+篇原創(chuàng)系列匯總

          回復“加群”與大佬們一起交流學習~

          點擊“閱讀原文”查看 100+ 篇原創(chuàng)文章

          瀏覽 47
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  啊啊啊额在线视频 | 久久无人区无码 | 午夜8050网站 | 大香蕉伊人网国产 | 高清一区二区三区日本久 |