# 懒加载实现

// 获取屏幕可视区域的高度 (不包括滚动条)
document.documentElement.clientHeight
1
2

mdn上的解释:

// 获取元素相对于文档顶部的高度
element.offsetTop
1
2
// 获取浏览器窗口顶部与文档顶部之间的距离,也就是滚动条滚动的距离
document.documentElement.scrollTop
1
2

通过判断

element.offsetTop - document.documentElement.scrollTop < document.documentElement.clientHeight

来确定元素是否进入到了可视区域

<img data-src="./images/1.jpeg" alt="" />
<img data-src="./images/2.png" alt="" />
<img data-src="./images/3.png" alt="" />
<img data-src="./images/4.png" alt="" />
<img data-src="./images/5.png" alt="" />
<img data-src="./images/6.jpeg" alt="" />
<img data-src="./images/7.png" alt="" />

<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.20/lodash.min.js"></script>
<script>
  let imgs = document.querySelectorAll('img');
  function getTop(e) {
    let T = e.offsetTop;
    while ((e = e.offsetParent)) {
      T += e.offsetTop;
    }
    return T;
  }
  function lazyLoad(imgs) {
    let H = document.documentElement.clientHeight; // 获取可视区域高度
    let S = document.documentElement.scrollTop || document.body.scrollTop;
    imgs.forEach((item) => {
      if (H + S > getTop(item)) {
        item.src = item.getAttribute('data-src');
      }
    });
  }
  window.onload = window.onscroll = () => {
    lazyLoad(imgs);
  };
  _.throttle(window.onscroll, 1000);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

这里用到了一个知识:

img标签在没有src属性的时候是不会去请求的。

# Element.getBoundingClientRect()

返回元素的大小及其相对于视口的位置(相对于可视区域左上角)

const bound = el.getBoundingClientRect();
1

当bound == clientHeight 时,说明元素即将进入可视区域了 。

所以bound <= clientHeight ,图片是在可视区域内的

bound<0 图片离开了可视区域

# Intersection Observer API

mdn (opens new window)的解释:

Intersection Observer API提供了一种异步检测目标元素与祖先元素或 viewport (opens new window) 相交情况变化的方法。

<img data-src="./images/1.jpeg" alt="" />
<img data-src="./images/2.png" alt="" />
<img data-src="./images/3.png" alt="" />
<img data-src="./images/4.png" alt="" />
<img data-src="./images/5.png" alt="" />
<img data-src="./images/6.jpeg" alt="" />

<script>
  let imgs = document.querySelectorAll('img');
  let callback = (e) => {
    // console.log(e);
    let el = e[0].target;
    // set src attribute to load img
    el.src = el.dataset.src;
  };
  let observer = new IntersectionObserver(callback);
  imgs.forEach((img, i) => {
    observer.observe(img);
  });
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

demo (opens new window)

参考文档:

https://zhuanlan.zhihu.com/p/55311726

Intersection Observer API (opens new window)

# 关于md-editor的图片懒加载

即v-html的图片懒加载

可以使用自定义指令


const lazyLoadDirective: ObjectDirective = {
    mounted: (el: HTMLElement) => {
        console.log('el: ', el)
        nextTick(() => {
            const imgObserver = new IntersectionObserver((entries, observer) => {
                entries.forEach((entry) => {
                    if (entry.isIntersecting) {
                        const img = entry.target as HTMLImageElement
                        img.src = img.dataset.src || ''
                        observer.unobserve(img)
                    }
                })
            })
            const imgs = el.querySelectorAll('img')
            console.log('imgs: ', imgs)
            imgs.forEach((img) => {
                img.dataset.src = img.src
                img.src = ''
                imgObserver.observe(img)
            })
        })
    }
}
// 这里最重要的步骤是遍历imgs将图片的data-src赋值,并清空data属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

但是这么做也会有个问题

NetWork中会大量canceled的图片请求,虽然无关紧要,但是并不美观

因此最好是在v-html这一步就去设置data-src属性

这样,上面代码中的 img.dataset.src = img.src; img.src = ''这一步也就不需要了。