当前位置:   article > 正文

vant4.0 正式发布了,分析其源码学会用 vue3 写一个图片懒加载组件!

vant4.0
  1. this.options = {
  2. ListenEvents: listenEvents || DEFAULT_EVENTS,
  3. }
  4. /*
  5. * add or remove eventlistener
  6. * @param {DOM} el DOM or Window
  7. * @param {boolean} start flag
  8. * @return
  9. */
  10. initListen(el, start) {
  11. this.options.ListenEvents.forEach((evt) =>
  12. (start ? on : off)(el, evt, this.lazyLoadHandler)
  13. );
  14. }
  15. 复制代码

8.4.1 on、off 监听事件,移除事件

  1. export function on(el, type, func) {
  2. el.addEventListener(type, func, {
  3. capture: false,
  4. passive: true,
  5. });
  6. }
  7. 复制代码
  1. // vant/packages/vant/src/lazyload/vue-lazyload/util.js
  2. export function off(el, type, func) {
  3. el.removeEventListener(type, func, false);
  4. }
  5. 复制代码

8.5 initIntersectionObserver 初始化

  1. /**
  2. * init IntersectionObserver
  3. * set mode to observer
  4. * @return
  5. */
  6. initIntersectionObserver() {
  7. if (!hasIntersectionObserver) {
  8. return;
  9. }
  10. this.observer = new IntersectionObserver(
  11. this.observerHandler.bind(this),
  12. this.options.observerOptions
  13. );
  14. if (this.listeners.length) {
  15. this.listeners.forEach((listener) => {
  16. this.observer.observe(listener.el);
  17. });
  18. }
  19. }
  20. 复制代码

8.6 observerHandler 观测,触发 load 事件

mdn 文档:IntersectionObserverEntry

  1. /**
  2. * init IntersectionObserver
  3. * @return
  4. */
  5. observerHandler(entries) {
  6. entries.forEach((entry) => {
  7. if (entry.isIntersecting) {
  8. this.listeners.forEach((listener) => {
  9. // 如果加载完成了,就移除监听
  10. if (listener.el === entry.target) {
  11. if (listener.state.loaded)
  12. return this.observer.unobserve(listener.el);
  13. listener.load();
  14. }
  15. });
  16. }
  17. });
  18. }
  19. 复制代码

8.7 实例方法 addLazyBox 添加要懒加载的组件到队列(数组)

  1. /*
  2. * add lazy component to queue
  3. * @param {Vue} vm lazy component instance
  4. * @return
  5. */
  6. addLazyBox(vm) {
  7. this.listeners.push(vm);
  8. // 浏览器环境
  9. if (inBrowser) {
  10. //
  11. this.addListenerTarget(window);
  12. // 如果是监听 observer 模式,监听 new IntersectionObserver().observe(vm.el)
  13. this.observer && this.observer.observe(vm.el);
  14. if (vm.$el && vm.$el.parentNode) {
  15. // 加入父级
  16. this.addListenerTarget(vm.$el.parentNode);
  17. }
  18. }
  19. }
  20. 复制代码

8.8 实例方法 removeComponent 移除组件

  1. /*
  2. * remove lazy components form list
  3. * @param {Vue} vm Vue instance
  4. * @return
  5. */
  6. removeComponent(vm) {
  7. if (!vm) return;
  8. remove(this.listeners, vm);
  9. this.observer && this.observer.unobserve(vm.el);
  10. // 移除父级
  11. if (vm.$parent && vm.$el.parentNode) {
  12. this.removeListenerTarget(vm.$el.parentNode);
  13. }
  14. // 移除 window 元素
  15. this.removeListenerTarget(window);
  16. }
  17. 复制代码

8.9 addListenerTarget 添加事件的目标元素

比如 window 等。

  1. /*
  2. * add listener target
  3. * @param {DOM} el listener target
  4. * @return
  5. */
  6. addListenerTarget(el) {
  7. if (!el) return;
  8. let target = this.targets.find((target) => target.el === el);
  9. if (!target) {
  10. target = {
  11. el,
  12. id: ++this.targetIndex,
  13. childrenCount: 1,
  14. listened: true,
  15. };
  16. this.mode === modeType.event && this.initListen(target.el, true);
  17. this.targets.push(target);
  18. } else {
  19. target.childrenCount++;
  20. }
  21. return this.targetIndex;
  22. }
  23. 复制代码

8.10 removeListenerTarget 移除事件的目标元素

  1. /*
  2. * remove listener target or reduce target childrenCount
  3. * @param {DOM} el or window
  4. * @return
  5. */
  6. removeListenerTarget(el) {
  7. this.targets.forEach((target, index) => {
  8. if (target.el === el) {
  9. target.childrenCount--;
  10. if (!target.childrenCount) {
  11. this.initListen(target.el, false);
  12. this.targets.splice(index, 1);
  13. target = null;
  14. }
  15. }
  16. });
  17. }
  18. 复制代码

9. 总结

大致流程:

  • 事件模式
  1. 1. 初始化在元素(比如是 window,但不一定是 window)添加监听滚动和其他相关事件
  2. 2. 使用 Element.getBoundingClientRect API 获取元素的大小及其相对于视口的位置,判断是否进入可视化区
  3. 3. 进入可视区触发 load 事件,将图片设置 src 真实的图片路径,从而自动加载图片
  4. 4. 离开销毁监听的事件、和移除绑定事件的元素
  5. 复制代码
  • observer 模式

主要是第二步用 IntersectionObserver API。

  1. // 把 Vue 实例对象 this 添加到 lazy 实例中
  2. lazyManager.addLazyBox(this);
  3. // 执行 lazyLoaderHandler 函数(发现节点(元素)在视口 checkInView,触发 load 事件)
  4. lazyManager.lazyLoadHandler();
  5. // 移除组件
  6. lazyManager.removeComponent(this);
  7. 复制代码

在 load 事件中,调用 loadImageAsync 函数。

  1. const image = new Image();
  2. image.src = xxx;
  3. image.onload = () => {}
  4. image.onerror = () => {}
  5. 复制代码

行文至此,我们就算分析完了 lazyload 组件

其中,有很多细节处理值得我们学习。 比如:

  • 监听事件,不仅仅是 scroll 事件,还有'scroll','wheel','mousewheel','resize','animationend','transitionend','touchmove'
  • 监听本身数组存起来了
  • 目标元素也用数组存起来了。

install 函数主要有以下实现:

  • 把 lazy 实例对象添加到全局上
  • 注册懒加载组件
  • 注册图片组件
  • 注册指令 lazy
  • 注册指令 lazy-container 没有分析。

但限于篇幅原因,组件源码还有指令部分没有分析。 感兴趣的小伙伴可以自行分析学习。

如果看完有收获,欢迎点赞、评论、分享支持。你的支持和肯定,是我写作的动力

10. 加源码共读群交流

最后可以持续关注我@若川。我会写一个组件库源码系列专栏,欢迎大家关注。

我倾力持续组织了一年每周大家一起学习200行左右的源码共读活动,感兴趣的可以点此扫码加我微信 ruochuan12 参与

作者:若川
链接:https://juejin.cn/post/7171227417246171149
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/823122
推荐阅读
相关标签
  

闽ICP备14008679号