原生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

posted @ 2021-06-22 17:36  zc-lee  阅读(241)  评论(0编辑  收藏  举报