面试中vue-lazyload实现原理

vue-lazyload实现

vue面试常被问到 vue-lazyload 实现原理,其实考察的是我们对vue指令的掌握

使用

先来使用一下这个 vue-lazyload 插件

插件引入

npm install vue-lazyload

使用

<template>
  <ul class="box">
    <li v-for="(img, index) in list" :key="index">
      <img v-lazy="img" />
    </li>
  </ul>
</template>

<script>
import axios from "axios";
import LazyLoad from "vue-lazyload";
import Vue from "vue";
import logo from "@/assets/logo.png";

Vue.use(LazyLoad, {
  loading: logo,
  preload: 1.2, //加载距离屏幕1.2倍处图片
});
export default {
  name: "LazyLoadTest",
  data() {
    return { list: [] };
  },
  // 请求放到created还是mounted?,服务端渲染支持created 所以希望大家把请求写在这里保持和服务端一致
  async mounted() {
    const { data: imgs } = await axios.get("http://localhost:4000/api/list");
    this.list = imgs;
    console.log(this.list);
  },
};
</script>

<style>
.box {
  width: 400px;
  height: 400px;
  overflow: scroll;
}
img {
  width: 100px;
  height: 150px;
}
</style>

实现vue-lazyload

import _ from 'lodash';
const VueLazyLoad = {
    //vue插件实现,Vue.use(插件名,options),vue内容会调用install,并将Vue和options传入,这样能保证Vue的版本
    install(Vue, options) {
        //获取LazyLoad类
        const LazyLoad = lazy(Vue);
        //获取实例
        const instance = new LazyLoad(options)
        //指令
        Vue.directive('lazy', {
            bind: instance.bind.bind(instance), //这里注意需要改变this指向
            unbind: instance.unbind.bind(instance)
        })
    }
}

function lazy(Vue) {
    //存储每个图片的属性
    class ReactiveListener {
        constructor({
            el,
            src,
            options
        }) {
            this.el = el; //图片元素
            this.src = src; //图片地址
            this.options = options; //用户配置的参数
            this.state = {
                loading: false
            }; //图片是否已经加载
        }

        //判断是否在可视区
        checkInView() {
            const {
                top
            } = this.el.getBoundingClientRect();
            return top < window.innerHeight * this.options.preload;
        }

        //加载图片
        load() {
            //渲染loading图
            render(this, 'loading');
            //加载真实图片
            loadImg(this.src, () => {
                //加载真实图片成功回调,渲染真实图片
                render(this, 'success')
            }, () => {
                //加载真实图片失败的回调,渲染失败图
                render(this, 'error')
            })
        }
    }
    return class LazyLoad {
        constructor(options) {
            this.options = options;
            this.listeners = []; //存储所有图片元素
            this.bindHandler = false; //是有已经绑定过
        }

        //绑定
        bind(el, bindings) {
            Vue.nextTick(() => {
                //图片地址
                const src = bindings.value;
                const listener = new ReactiveListener({
                    el,
                    src,
                    options: this.options
                });
                //存储每一个图片元素
                this.listeners.push(listener);
                //获取滚动的父元素
                const pElm = parentScoll(el);
                //绑定事件
                if (!this.bindHandler) {
                    //这里使用lodash节流
                    const lazyHandler = _.throttle(this.lazyLoadHandler.bind(this));
                    pElm.addEventListener('scroll', lazyHandler, {
                        passive: true //不阻止其他事件
                    });
                    this.bindHandler = true; //保证滚动事件注册一次
                }
                //初始化,当前要展示的
                this.lazyLoadHandler();
            })
        }
        //解绑
        unbind() {

        }

        //
        lazyLoadHandler() {
            this.listeners.forEach(listener => {
                //如果没有加载过,并且在加载区域
                //已经加载过的
                if (listener.state.loading) return;
                listener.checkInView() && listener.load()
            })
        }
    }
}

//获取滚动的父元素
function parentScoll(el) {
    let parent = el.parentNode;
    while (parent) {
        if (/scroll/.test(getComputedStyle(parent)['overflow'])) {
            return parent;
        }
        parent = parent.parentNode;
    }
    return parent;
}

//加载真实图片
function loadImg(src, resolve, reject) {
    const img = new Image();
    img.src = src;
    img.onload = resolve;
    img.onerror = reject;
}

//渲染图片,真实图,加载图,错误图
function render(listener, state) {
    let src = '';
    switch (state) {
        case 'loading': //显示加载图
            src = listener.options.loading;
            break;
        case 'success': //显示真实图
            src = listener.src;
            break;
        case 'loading': //显示错误图
            src = listener.options.error;
            break;
    }
    listener.el.setAttribute('src', src);
}

export default VueLazyLoad;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值