Skip to content
On this page

scroll+getBoundingClientRect实现

index.js

typescript
<img v-lazy="item.src" :alt="item.title" />

每个img都有v-lazy

typescript
import LazyLoad from './LazyLoad'

export default {
    install(Vue, options) {

        const lazyClass = LazyLoad(Vue)
        const lazyIns = new lazyClass(options)

        Vue.directive('lazy', {
            bind: lazyIns.bindLazy.bind(lazyIns)
        })
    }

}

Vue安装插件时,使用的是一个实例,因为可以公用一份options

typescript
import Vue from 'vue'
import App from './App.vue'
// import VueLazyload from './modules/vue-lazyload';// pp小野
import VueLazyload from './modules/my-lazyload';

Vue.config.productionTip = false

Vue.use(VueLazyload, {
  loading: 'http://localhost:3000/images/loading.gif',
  error: 'http://localhost:3000/images/error.jpg',
  preload: 1
})

new Vue({
  render: h => h(App),
}).$mount('#app')

LazyLoad.js

为当前el,也就是img标签的父级元素添加监听 通过事件委托addEventlistener监听滚动事件 首次加载,没有滚动事件触发,需要手动调用一次 以触发img内容初始化(设置src)

typescript
import LazyImg from "./LazyImg";

function getScrollParent(el) {
  let _parent = el.parentNode;
  while (_parent) {
    const styleOverflow = getComputedStyle(_parent)["overflow"];
    // style只能获取行内设置的,所以用getComputedStyle
    if (/(scroll)|(auto)/.test(styleOverflow)) {
      return _parent;
    }
    _parent = _parent.parentNode;
  }
}

export default function LazyLoad(Vue) {
  return class Lazy {
    constructor(options) {
      this.options = options;
      this.lazyImgList = [];
    }

    bindLazy(el, bindings, vnode) {
      Vue.nextTick(() => {
        const lazyImg = new LazyImg({
          el: el,
          src: bindings.value,
          options: this.options,
        });
        const scrollParent = getScrollParent(el);
        scrollParent &&
          scrollParent.addEventListener(
            "scroll",
            this.handleScroll.bind(this),
            false
          );

        this.lazyImgList.push(lazyImg);
        this.handleScroll();
      });
    }

    handleScroll() {
      console.log('scroll监听',)
      // 滚动事件可以用 timeout 和 requestAnimationFrame 实现节流
      this.lazyImgList.forEach((lazyImg) => {
        !lazyImg.loaded && lazyImg.setImgSrc();
      });
    }
  };
}

LazyImg.js

是否在可视区的判断

preload 就是多加载多少区域,这样用户刚滚动就能看到,不会有等待的体验 const { top } = this.el.getBoundingClientRect();

Element.getBoundingClientRect() 方法返回一个 DOMRect 对象,其提供了元素的大小及其相对于视口的位置。

top < window.innerHeight * (this.options.preload || 1.3); image.png

typescript
function imgLoad(src) {
  return new Promise((resolve, reject) => {
    const oImg = new Image();
    oImg.src = src;
    oImg.onload = resolve;
    oImg.onerror = reject;
  });
}
export default class LazyImg {
  constructor({ el, src, options }) {
    this.src = src;
    this.el = el;
    this.options = options;

    this.loaded = false;

    this.state = {
      loading: false,
      error: false,
    };
  }
  isClient() {
    const { top } = this.el.getBoundingClientRect();
    console.log('isClient', top, this.el.clientTop);

    return top < window.innerHeight * (this.options.preload || 1.3);
  }
  imgRender(state) {
    const { loading, error } = this.state;
    let src = '';
    switch (state) {
      case 'loading':
        src = loading || '';
        break;
      case 'error':
        src = error || '';
        break;
      default:
        src = this.src;
        break;
    }
    this.el.setAttribute('src', src);
  }
  setImgSrc() {
    this.imgRender('loading');
    if (this.isClient() && !this.loaded) {
      imgLoad(this.src).then(
        () => {
          this.state.loading = true;
          this.imgRender('ok');
          this.loaded = true;
        },
        () => {
          this.state.error = true;
          this.imgRender('error');
          this.loaded = true;
        }
      );
    }
  }
}

Intersection Observer交叉器

https://blog.csdn.net/qq_54753561/article/details/122820632

typescript
IntersectionObserver.observe()     使`IntersectionObserver`开始监听一个目标元素。
IntersectionObserver.unobserve()   使IntersectionObserver停止监听特定目标元素。
IntersectionObserver.disconnect()  使IntersectionObserver对象停止监听工作。
IntersectionObserver.takeRecords()  返回所有观察目标的IntersectionObserverEntry对象数组。
typescript
let ob = new IntersectionObserver(
    entries => {
        // entries 是个数组包含所有的监听对象和视口的交叉位置
        entries.forEach(item => {
            if (item.isIntersecting) {
                let target = item.target; // 拿到当前监听的盒子
                console.log(target);
                // el.src = bing.value;
                target.src = target.getAttribute('data-src');
                ob.unobserve(target); // 加载完移除监听
            }
        });
    },
    {
        threshold: [1] // 0 一露头 0.5 露一半 1 完全出现 // threshold 控制交叉状态在什么养的情况下触发上面的回调
    }
);

export default {
    install(Vue, options) {
        // bind: function () {},
        // inserted: function () {},
        // update: function () {},
        // componentUpdated: function () {},
        // unbind: function () {}
        Vue.directive('lazy2', function (el) {
            console.log('lazy2----', el);
            ob.observe(el);
        });
    }
};

粤ICP备2024285819号