v-lazy

/*
vue插件一枚
1.长列表中图片是否加载完,没有加载完显示一张默认的加载图片
2.100张图片 默认优先加载可视区域(1屏幕2屏幕)的图片,不显示的不加载,
3. 滚动的时候图片元素是否在规定的加载区域没,如果在就加载 
   * 添加滚动的监听
   * 谁有overflow scroll auto 
4. new Image() onload() 
5. 对外抛出的不是一个类 外部没有实例化
*/
//创建了一个检测父元素有没有scroll的样式,要一层一层找
function getScrollParent(el) {
  // 获取子元素绑定overflow: scroll, auto 属性的父元素
  //console.log(el); 这里的el是img元素
  let parent = el.parentNode;
  while (parent) {
    if (/(scroll)|(auto)/.test(getComputedStyle(parent)["overflow"])) {
      return parent;
    }
    parent = parent.parentNode;//要每次都要重新给parent赋值,需要一层层向上找,有的话就把这个父元素的标签返回出来,没有就返回null
  }
  return null;
}

// 同步加载图片方法,在函数中加载图片的时候使用
function asyncImgLoad(src, resove, reject) {
  let img = new Image();
  img.src = src;
  img.onload = resove;
  img.error = reject;
}

// 对逻辑功能进行拆分
const MyLazy = (Vue) => {
  // 为了实现元素和对象的关联 硬生生的创建一个绑定对象
  class ReactiveListene { //下面在实例化listener的时候用,把这些参数传进来
    constructor({ el, src, renderEl, preload }) {
      this.el = el;
      this.src = src;
      this.renderEl = renderEl;
      this.preload = preload
      this.state = { loading: false }; // 还没加载
    }
    // 检测是不是在可视区域的范围内,如果在就需要检测
    checkInView() {
      
      let { top } = this.el.getBoundingClientRect();
      //console.log(window.innerHeight,top);
      return top < window.innerHeight * this.preload;
    }
    // 加载图片的方法
    load() {
      // 优先加载loading
      // console.log(this);//这里的this也是每一个listener
      this.renderEl(this, "loading");
      // 加载正常图片,就是执行外部的asyncImgLoad函数
      asyncImgLoad(
        this.src,
        () => {
          // 图片加载成功了后 渲染成功图片,这里是resolve函数
          this.state.loading = true;
          this.renderEl(this, "success");
        },
        () => {
          this.state.loading = true;
          // 图片加载失败了后 渲染失败图片
          this.renderEl(this, "error");
        }
      );
    }
  }

  return class LazyClass {  //返回一个类,里面的的options是在main.js里面在进行vue.user的时候传递的参数,包括loading,preload等参数
    constructor(options) {
      const {preload, loading} = options;//把参数进行解构
      this.preload = preload || 1;
      this.loading = loading;
     
      this.bindScrollEvent = false;
      // 保存所有的元素
      this.listenerArr = [];//这里保存了所有的listener,在下面实例化的listener
    }
    handleScroll() {
      // 滚动的监听事件
      this.listenerArr.map((listener) => { //对之前存的每一个listener进行触发加载loadshi事件,只有满足isInView的时候才会加载,也就是不在可视化里面的图片不进行加载,只有滚动到了可视化的才开始加载图片
        // 图片是否已加载 -> 是不是在可视区内需要加载
        if (!listener.state.loading) {
          let isInView = listener.checkInView();
          isInView && listener.load();
        }
        return listener;
      });
    }
    add(el, bindings) {
      // 1.添加滚动监听 2.将与图片对应的listener对象存起来
      
      //console.log("要绑定的元素", el, bindings, this);
      Vue.nextTick(() => {
        const scrollBox = getScrollParent(el);//找到绑定scroll的父元素进行绑定滚动事件
        if (scrollBox && !this.bindScrollEvent) {
          console.log("xxxxxxxxxx");
          this.bindScrollEvent = true;
          scrollBox.addEventListener("scroll", this.handleScroll.bind(this));
        }
        // 在绑定元素的时候填添加检测对象
        const listener = new ReactiveListene({ //在执行完这个函数的时候,就会实例化了listener这个对象了,因为在用的时候通过v-for,所以执行了多次这个函数,也就创建了多个listener,所以把每个listener添加到listenerArr数组中
          el,
          src: bindings.value,//这里的bindings里的value就是自定义组件里面=后面的值,在这里就是图片的路径
          renderEl: this.renderEl.bind(this),
          preload: this.preload
        });
      
        this.listenerArr.push(listener);
        // 初始化触发
        this.handleScroll();
      });
    }
    // 渲染方法
    renderEl(listener, state) {
      let el = listener.el;
      let src = "";
      switch (state) {
        case "loading":
          src = this.loading
          break;
        case "error":
          src = "";
          break;
       case 'success':
          src = listener.src;
          break;
      }
      el.setAttribute("src", src);
    }
  };
};

// 注册自定义指令
const MyLazyLoad = {
  install(Vue, options) {
    console.log(Vue, options);
    // 创建自定义指令
    const LazyClass = MyLazy(Vue);
    const lazy = new LazyClass(options);
    Vue.directive("hehe", {
      bind: lazy.add.bind(lazy), // 希望我们add方法的时候 this一直指向的是实例
    });
  },
};
export default MyLazyLoad;

思路:

刚开始通过install方法进行挂载一个插件,也就是我们自己封装的v-lazy,有两个参数,一个vue:是vue的构造器,一个options:是在vue.use的时候传入的值,在自定义参数的时候用的bind方法,用到里面前两个参数,一个是el:用指令的dom,一个是building参数:里面还好几个参数name:插件的名字,value,用插件里面的值,在用v-lazy='11111',就是111111

//接下来就是我们在bind函数的操作了,首先我们在全局封装了两个方法,一个是getScrollParent用来查找有滚动事件的父元素,参数是el,也就是传入的绑定我们自定义指令的dom,一个是asyncImgLoad,用来图片的加载三个参数,一个是图片的地址,一个是加载成功后的函数,另一个是加载失败的函数

//我们又创建了一个函数MyLazy,返回值是一个LazyClass类,接下来就是这个函数的逻辑

  //我们把vue当作参数穿进去,首先创建一个类ReactiveListene,这也就是相当于每一个绑定之类的dom,我们把他分装成一个类,有助于传值,定义他的方法,在constructor里面有参数el:dom,src:图片地址,renderEl:渲染方法,有个重要的state:{load:false},后续他通过这个判断加不加载,另外还有两个方法checkInView:判断元素是否在可视范围内,一个图片加载的方法load,在里面里面就进用了我们在全局写的图片加载方法,所以只有调用load方法图片才会加载不掉用的话就不加载//注意我们想图片一开始先加载一个我们预先设定的图片,所以在load函数调用renderEl方法的时候我们第二各参数传递“loading”,在renderEl函数进行swich判断的时候,先加载我们预定的图片,之后在执行我们全局asyncImgLoad加载图片的函数,在,并把state里面的loading改为true,在以后判断是true已经加载过得就不用加载了,在调用renderEl方法,把图片修改成真正的图片地址

    //我们又封装了一个类,也就是return的LazyClass类,在里面我们把options传进去,获取到app.use里面传的的,例如loading,图片首次加载的地址,然后定义了一个add方法,在里面进行了给具有滚动事件的父元素绑定滚动事件,并把所有绑定v-lazy自定义事件的元素,添加到数组listenerArr中,在滚动事件中进行对

    //listenerArr进行遍历,判断哪一个元素在可视化里面,并且没有加载过(loading是flase),就执行load方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值