1. 项目实现介绍

vue 项目搭建参考《Webpack4 搭建 Vue 项目》

文档使用 vuepress, 官方文档 https://vuepress.vuejs.org

发布文档 github pages + gh-page

项目地址 https://github.com/zxpsuper/vui-vue

文档地址 https://zxpsuper.github.io/vui-vue

处于自我摸索阶段,期待留下您的宝贵意见!

2. v-lazy 的基本实现

图片懒加载的基本原理:

  1. 先用占位图代替目标图片的 src 属性值
  2. 当图片的 offsetTop < innerHeight + scrollTop 时,即图片出现在窗口内部,此时修改 src 值为 data-src 的值
  3. 当然,这一切需要不断地监听滚动事件

先实现一个懒加载函数

var img = document.getElementsByTagName('img');function lazyload() {        //监听页面滚动事件        var seeHeight = window.innerHeight; //可见区域高度        var scrollTop =            document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度        for (var i = 0; i < img.length; i++) {            if (img[i].getAttribute('data-image-show')) continue; // 如果已经加载完成,则不需走下面流程            if (img[i].offsetTop < seeHeight + scrollTop) {                console.log(img[i].offsetTop, seeHeight, scrollTop);                if (img[i].getAttribute('src') == Vue.$vuiLazyLoad.img) {                    img[i].src = img[i].getAttribute('data-src');                    img[i].setAttribute('data-image-show', 'true'); // 给个标识,表示已经加载完成                }            }        }    }

滚动监听

滚动监听,不断滚动便会不断触发滚动监听的函数,影响性能,因此在此需要加入一个防抖函数

// 防抖函数function throttle(event, time) {    let timer = null;    return function(...args) {        if (!timer) {            timer = setTimeout(() => {                timer = null;                event.apply(this, args);            }, time);        }    };}

添加监听和去除监听

window.removeEventListener('scroll', throttle(lazyload, 800));window.addEventListener('scroll', throttle(lazyload, 800));

添加指令

这里用到了自定义指令中的三个钩子函数 bind,inserted,unbind, 我们要让指令中占位图可修改,因此写成函数形式

const lazyload = function(Vue) {    var img = document.getElementsByTagName('img');    function lazyload() {        //监听页面滚动事件        var seeHeight = window.innerHeight; //可见区域高度        var scrollTop =            document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度        for (var i = 0; i < img.length; i++) {            if (img[i].getAttribute('data-image-show')) continue; // 如果已经加载完成,则不需走下面流程            if (img[i].offsetTop < seeHeight + scrollTop) {                console.log(img[i].offsetTop, seeHeight, scrollTop);                if (img[i].getAttribute('src') == Vue.$vuiLazyLoad.img) {                    img[i].src = img[i].getAttribute('data-src');                    img[i].setAttribute('data-image-show', 'true'); // 给个标识,表示已经加载完成                }            }        }    }    // vui中的默认配置,用户可通过修改 Vue.$vuiLazyLoad.img 进行修改占位图    Vue.$vuiLazyLoad = {        img:            'https://github.com/zxpsuper/Demo/blob/master/images/avatar.jpg?raw=true',        imgLength: 0, // 懒加载的图片数量,当数量为 0 的时候移除滚动监听    };        lazyload(); //页面载入完毕加载可是区域内的图片    window.removeEventListener('scroll', throttle(lazyload, 800));    window.addEventListener('scroll', throttle(lazyload, 800));    return {        name: 'lazy',        // 绑定        bind(el, binding) {            el.setAttribute('src', Vue.$vuiLazyLoad.img);            el.setAttribute('data-src', binding.value);            Vue.$vuiLazyLoad.imgLength++;        },        // 插入        inserted(el) {            // 暂时空        },        // 解绑        unbind() {            Vue.$vuiLazyLoad.imgLength--; // 每次解绑,自减            if (!Vue.$vuiLazyLoad.imgLength)                window.removeEventListener('scroll', throttle(lazyload, 800));        },    };}

使用新特性 IntersectionObserver

IntersectionObserver接口(从属于Intersection Observer API)为开发者提供了一种可以异步监听目标元素与其祖先或视窗(viewport)交叉状态的手段。

观察元素是否与视窗交叉,若是则修改 scrdata-src 值,并解除观察状态,当然这一切的前提是你在图片创建的时候观察图片本身,因此在图片插入时的钩子函数内

inserted(el) {    if (IntersectionObserver) lazyImageObserver.observe(el);},

具体使用方法:

let lazyImageObserverif (IntersectionObserver) {    lazyImageObserver = new IntersectionObserver((entries, observer) => {        entries.forEach((entry, index) => {            let lazyImage = entry.target;            // 如果元素可见            if (entry.intersectionRatio > 0) {                if (lazyImage.getAttribute('src') == Vue.$vuiLazyLoad.img) {                    lazyImage.src = lazyImage.getAtstribute('data-src');                }                lazyImageObserver.unobserve(lazyImage); // 解除观察            }        });    });}

当然我们优先使用 IntersectionObserver, 若不支持则使用传统方法

注册指令

import Vue from 'vue'Vue.directive('lazy', lazyLoad(Vue));

3. 完整代码

const lazyLoad = function(Vue) {    var img = document.getElementsByTagName('img');    function lazyload() {        //监听页面滚动事件        var seeHeight = window.innerHeight; //可见区域高度        var scrollTop =            document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度        for (var i = 0; i < img.length; i++) {            if (img[i].getAttribute('data-image-show')) continue;            console.log(i);            if (img[i].offsetTop < seeHeight + scrollTop) {                console.log(img[i].offsetTop, seeHeight, scrollTop);                if (img[i].getAttribute('src') == Vue.$vuiLazyLoad.img) {                    img[i].src = img[i].getAttribute('data-src');                    img[i].setAttribute('data-image-show', 'true');                }            }        }    }    Vue.$vuiLazyLoad = {        img:            'https://github.com/zxpsuper/Demo/blob/master/images/avatar.jpg?raw=true',        imgLength: 0,    };    var lazyImageObserver;    if (IntersectionObserver) {        lazyImageObserver = new IntersectionObserver((entries, observer) => {            entries.forEach((entry, index) => {                let lazyImage = entry.target;                // 如果元素可见                if (entry.intersectionRatio > 0) {                    if (lazyImage.getAttribute('src') == Vue.$vuiLazyLoad.img) {                        lazyImage.src = lazyImage.getAttribute('data-src');                    }                    lazyImageObserver.unobserve(lazyImage);                }            });        });    } else {        lazyload(); //页面载入完毕加载可是区域内的图片        window.removeEventListener('scroll', throttle(lazyload, 800));        window.addEventListener('scroll', throttle(lazyload, 800));    }    return {        name: 'lazy',        bind(el, binding) {            el.setAttribute('src', Vue.$vuiLazyLoad.img);            el.setAttribute('data-src', binding.value);            Vue.$vuiLazyLoad.imgLength++;        },        inserted(el) {            if (IntersectionObserver) lazyImageObserver.observe(el);        },        unbind() {            Vue.$vuiLazyLoad.imgLength--;            if (!Vue.$vuiLazyLoad.imgLength)                window.removeEventListener('scroll', throttle(lazyload, 800));        },    };};export default lazyLoad;function throttle(event, time) {    let timer = null;    return function(...args) {        if (!timer) {            timer = setTimeout(() => {                timer = null;                event.apply(this, args);            }, time);        }    };}

总结

本文是对vue自定义指令及懒加载原理的综合实现,若有错误,望指出共同进步。

更多推荐

前端进阶小书(advanced_front_end)

前端每日一题(daily-question)

webpack4 搭建 Vue 应用(createVue)

Canvas 进阶(一)二维码的生成与扫码识别

Canvas 进阶(二)写一个生成带logo的二维码npm插件

Canvas 进阶(三)ts + canvas 重写”辨色“小游戏

Canvas 进阶(四)实现一个“刮刮乐”游戏