1. 程式人生 > 其它 >原生JS實現圖片懶載入(考慮不重複載入以及節流)

原生JS實現圖片懶載入(考慮不重複載入以及節流)

1.原生JS實現圖片懶載入(考慮不重複載入以及節流)

知識點:視口位置判斷,懶載入實現(data-set),節流等

1.Element.getBoundingClientRect()

該方法返回元素的大小及其相對於視口的位置,
具體解釋及用法參考 MDN.

通過 Element.getBoundingClientRect().top 與 window.innerHeight(當前視窗的高度)比較就可以判斷圖片是否出現在可視區域。
注意這個 top 是相對於當前視窗的頂部的top值而不是一開始的頂部。

2.通過 Element.dataset 可以獲取到為元素節點新增的data-*屬性,我們可以通過這個屬性來儲存圖片載入時的url。

<!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>Document</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        } 
        img {
            display: inline-block;
            width: 100%;
            height: 300px;
            background: gray;
        }
    </style>
</head>

<body>
    <div class="box-image">
        <img src="" class="image-item" lazyload="true" data-original="https://img.alicdn.com/imgextra/i2/252339290/TB29PYUXnIlyKJjSZFrXXXn2VXa_!!252339290-0-beehive-scenes.jpg_180x180xzq90.jpg_.webp" alt="">
        <img src="" class="image-item" lazyload="true" data-original="https://img.alicdn.com/imgextra/i4/2260152888/O1CN01vw2e251XCkJr0VPZU_!!2260152888-0-beehive-scenes.jpg_250x250xz.jpg" alt="">
        <img src="" class="image-item" lazyload="true" data-original="https://img.alicdn.com/imgextra/i3/24687421/TB2Xg1izsyYBuNkSnfoXXcWgVXa_!!24687421-0-beehive-scenes.jpg_250x250xz.jpg" alt="">
        <img src="" class="image-item" lazyload="true" data-original="https://img.alicdn.com/imgextra/i4/890151842/TB2II1mnZbI8KJjy1zdXXbe1VXa_!!890151842-0-beehive-scenes.jpg_250x250xz.jpg" alt="">
        <img src="" class="image-item" lazyload="true" data-original="https://img.alicdn.com/imgextra/i2/2096513830/TB2l1W0kRnTBKNjSZPfXXbf1XXa_!!2096513830-0-beehive-scenes.jpg_250x250xz.jpg" alt="">
        <img src="" class="image-item" lazyload="true" data-original="https://img.alicdn.com/imgextra/i3/2586222636/TB2RDGrpqAoBKNjSZSyXXaHAVXa_!!2586222636-0-daren.jpg_250x250xz.jpg" alt="">
        <img src="" class="image-item" lazyload="true" data-original="https://img.alicdn.com/imgextra/i2/1870112525/TB2Ae.xbOwIL1JjSZFsXXcXFFXa_!!1870112525-2-beehive-scenes.png_250x250xz.jpg" alt="">
        <img src="" class="image-item" lazyload="true" data-original="https://img.alicdn.com/imgextra/i2/2194952831/TB2PwWty7qvpuFjSZFhXXaOgXXa_!!2194952831-0-beehive-scenes.jpg_250x250xz.jpg" alt="">
    </div>
    <script>
        var viewHeight = document.documentElement.clientHeight; // = window.innerHeight?
        // 節流:加一個300ms的間隔執行
        function throttle(fn, wait) {
          let canRun = true
          return function (...args) {
            if (!canRun) return
            canRun = false 
            setTimeout(() => {
              fn.apply(this, args)
              canRun = true
            }, wait)
          }
        }
        function lazyload() {
          let imgs = document.querySelectorAll('img[data-original][lazyload]') // 獲取文件中所有擁有data-original lazyload屬性的<img>節點
          imgs.forEach(item => {
            if (item.dataset.original == '') {// HTMLElement.dataset屬性允許無論是在讀取模式和寫入模式下訪問在 HTML或 DOM中的元素上設定的所有自定義資料屬性(data-*)集。
              return
            }
            // 返回一個DOMRect物件,包含了一組用於描述邊框的只讀屬性——left、top、right和bottom,
            // 單位為畫素。除了 width 和 height 外的屬性都是相對於視口的左上角位置而言的。
            let rect = item.getBoundingClientRect()
            // 其top值是相對於當前視窗的頂部而言的而不是絕對的頂部,所以top值 < window.innerHeight的話圖片就出現在底部了就需要載入
            if (rect.bottom >= 0 && rect.top < viewHeight) {
              let img = new Image()
              img.src = item.dataset.original
              // 圖片載入完成觸發load事件?
              img.onload = function () {
                item.src = img.src
              }
              // 移除屬性的話就不會重複載入了
              item.removeAttribute('data-original')
              item.removeAttribute('lazyload')
            }
          })
        }
        // 先呼叫一次載入最初顯示在視窗中的圖片
        lazyload();
        let throttle_lazyload = throttle(lazyload, 300)
        document.addEventListener('scroll', throttle_lazyload)
    </script>
</body>
</html>

2.如何渲染幾萬條資料且不卡住頁面?

①利用文件碎片,實現一次性插入多個節點,減少迴流

②分批次地插入節點而不是一次性插入,防止阻塞

③使用requestAnimationFrame讓瀏覽器選擇最為合適的渲染間隔

<!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>Document</title>
  </head>
  <body>
    <ul>
      控制元件
    </ul>
    <script>
      const total = 100000 // 10萬條資料
      const once = 20      // 每輪插入的資料條目
      const loopCount = total / once // 渲染總次數
      let countOfRender = 0
      let ul = document.querySelector('ul')
      function add() {
        // 使用文件碎片優化效能
        const fragment = document.createDocumentFragment()
        for (let i = 0; i < once; i++) {
          const li = document.createElement('li')
          li.innerText = Math.floor(Math.random() * total)
          fragment.appendChild(li)
        }
        ul.appendChild(fragment)
        countOfRender+=1
        loop()
      }
      function loop() {
        if (countOfRender < loopCount) {
          window.requestAnimationFrame(add) // 使用requestAnimationFrame每隔16ms(瀏覽器自己選擇最佳時間)重新整理一次
        }
      }
    </script>
  </body>
</html>

3.寫函式實現任意標籤轉換成json形式

/*
  <div>
    <span>
      <a></a>
    </span>
    <span>
      <a></a>
      <a></a>
    </span>
  </div>
*/

function DOM2JSON(str) {
  let reg = /<(.+)>(.*?)<\/\1>/g
  let result = null
  let nodes = []
  while((result = reg.exec(str))!== null) {
    nodes.push({ tag: result[1], children: DOM2JSON(result[2])}) // exec返回的陣列,[0]匹配的字串 然後依次是捕獲的分組 然後有index和input屬性
  }
  return nodes.length > 1 ? nodes : nodes[0]
}

console.log(JSON.stringify(DOM2JSON('<div><span><a></a></span><span><a></a><a></a></span></div>')))
// {"tag":"div","children":[{"tag":"span","children":{"tag":"a"}},{"tag":"span","children":[{"tag":"a"},{"tag":"a"}]}]}

這裡主要利用了 exec 函式會在上一次匹配的結果之後繼續匹配,且如果未匹配成功會返回 null,然後注意下 exec 和正則表示式分組的使用即可。

4.判斷執行順序(事件迴圈)

console.log('begin'); // 1.begin
setTimeout(() => {
    console.log('setTimeout 1'); // 3.setTimeout 1
    Promise.resolve() // Promise.resolve().then ?
        .then(() => {
            console.log('promise 1'); // 5.promise 1
            setTimeout(() => {
                console.log('setTimeout2'); // 8. setTimeout2
            });
        })
        .then(() => {
            console.log('promise 2'); // 7. promise 2  注意7比8要快,會同時放入巨集任務和微任務佇列?
        });
    new Promise(resolve => {
        console.log('a'); // 4. a
        resolve();
    }).then(() => {
        console.log('b'); // 6. b
    });
}, 0);
console.log('end'); // 2.end

5.實現一個 sleep 函式

async function test () {
  console.log('start')
  await sleep(3000)
  console.log('3s has passed')
}

function sleep (ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve()
    }, ms)
  })
}

6.meta 標籤的作用

meta 標籤分兩大部分:HTTP 標題資訊(http-equiv)和頁面描述資訊(name)。
1、宣告文件使用的字元編碼

<meta charset='utf-8'>

以下設定更為詳細:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

該 meta 標籤定義了 HTML 頁面所使用的字符集為 utf-8 ,就是萬國碼。它可以在同一頁面顯示中文簡體、繁體及其它語言(如日文,韓文)等

2、宣告文件的相容模式

// 指示IE以目前可用的最高模式顯示內容
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> 

<meta http-equiv="X-UA-Compatible" content="IE=Emulate IE7" /> 
// 指示IE使用 <!DOCTYPE> 指令確定如何呈現內容。標準模式指令以IE7 標準模式顯示,而 Quirks 模式指令以 IE5 模式顯示

3、SEO 優化

<meta name="description" content="不超過150個字元" /> // 頁面描述
<meta name="keywords" content="html5, css3, 關鍵字"/> // 頁面關鍵詞
<meta name="author" content="魔法小棧" /> // 定義網頁作者

// 定義網頁搜尋引擎索引方式,robotterms是一組使用英文逗號「,」分割的值,通常有如下幾種取值:none,noindex,nofollow,all,index和follow。
<meta name="robots" content="index,follow" /> 

4、為移動裝置新增 viewport

<meta name ="viewport" content ="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">

/*
  content 引數解釋:
  width       viewport 寬度(數值/device-width)
  height         viewport 高度(數值/device-height)
  initial-scale  初始縮放比例
  maximum-scale  最大縮放比例
  minimum-scale  最小縮放比例
  user-scalable  是否允許使用者縮放(yes/no)
  minimal-ui     iOS 7.1 beta 2 中新增屬性,可以在頁面載入時最小化上下狀態列。這是一個布林值,可以直接這樣寫:
*/

<meta name="viewport" content="width=device-width, initial-scale=1, minimal-ui">

例如如下設定:

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
  • 強制讓文件與裝置的寬度保持 1:1 ;
  • 文件最大的寬度比列是1.0( initial-scale 初始刻度值和 maximum-scale 最大刻度值);
  • user-scalable 定義使用者是否可以手動縮放( no 為不縮放),使頁面固定裝置上面的大小;

更多內容參考 https://blog.csdn.net/xmtblog/article/details/46226717