Open sggmico opened 3 years ago
图片懒加载大家都很熟悉了,优点如下:
本文将使用vue自定义指令的方式,实现图片懒加载的功能。要点如下:
先来围观下,不使用图片懒加载功能,图片列表交互效果,这里。
嗯,因为页面会同时请求全部的图片。如果不处理, 一是页面会比较卡,二是滚动到后边会出现白屏,用户体验非常差。接下来,主角登场 —— 图片懒加载指令他来了。
首先使用 mock.js,准备一些图片数据。
目录地址: src/mock/index.js
import Mock from 'mockjs' Mock.mock('/images', 'get', { code: 200, msg: 'ok', 'list|20': [ { id: '@id', 'cover|1': [ "@image('300x200', @color, #fff, @title)" ] } ] })
当请求数据时,返回的数据格式大概是下面的样子:
此时图片数据他来了。
图片交互中,我们需要监听图片容器的滚动,这里利用Vue内置的事件通知机制,封装了一个eventBus。该模块保证 eventBus既可以在所有组件内使用,也可以在普通的JS模块里使用。代码如下:
目录地址:src/eventBus.js
import Vue from 'vue' const bus = new Vue() Vue.prototype.$bus = bus export default bus
列表组件目录地址: src/components/List.vue
<template> <div class="container" ref="container"> <ul> <li v-for="(image, idx) in images" :key="image.id"> <img :src="image.cover" alt="" :data-id="idx"/> </li> </ul> </div> </template> <script> import { getImages } from "@/api/images"; export default { data() { return { images: [] }; }, async created() { const { data } = await getImages(); this.images = Object.freeze(data.list); }, mounted() { this.$refs.container.addEventListener("scroll", () => { this.$bus.$emit("iscroll", this.$refs.container); }); }, }; </script>
上面,我们在created阶段,请求到了mock的图片数据,并且在模板里循环遍历了图片列表。同时,在mounted阶段,我们通过监听图片容器的滚动,当滚动触发时,通过封装好的事件总线(eventBus)发布 “iscroll”的事件通知。滚动监听他来了。
“iscroll”
eventBus的更多了解,这里。
图片懒加载的机制是:当图片列表滚动时,动态的将可视区内的图片加载并渲染出来。但是,当滚动发生时,对图片进行展示的逻辑并非实时执行,而是等到滚动停止后的某个时间点执行,这样就可以避免在滑动过程中触发非必要的图片加载和渲染。所以,debounce防抖处理他来了。
/** * 防抖函数 * * @export * @param {*} fn * @param {*} delay * @returns */ export function debounce(fn, delay) { let timer = null return function (...arg) { clearTimeout(timer) timer = setTimeout(() => { fn.apply(this, arg) }, delay); } }
关于防抖和节流,感兴趣的朋友,这里。
对Vue自定义指令不熟悉的朋友,这里。
到目前位置,List组件内的渲染时,会将所有的图片原地址添加到src属性上 —— :src="image.cover" 。这样,在组件渲染时,全部的图片都会加载和渲染。为了实现图片懒加载的效果,我们需要对src的赋值通过自定义指令做一层代理,并且指令的逻辑单独维护。
:src="image.cover"
最新List组件实现,如下:
<template> <div class="container" ref="container"> <ul> <li v-for="(image, idx) in images" :key="image.id"> <img v-lazy:[container]="image.cover" alt="" :data-id="idx"/> </li> </ul> </div> </template> <script> //... export default { data() { return { images: [], container: 'container' //图片容器的className }; }, //... }; </script>
上面,我们通过 v-lazy:[container] = ”image.cover“ 来接管每个图片的原始地址,其中 container 为父容器的类名,在指令里判断图片是否在可视区会用到父容器,并且可以在data中动态指定。接下来,我们只需要关注 v-lazy的实现即可。
v-lazy:[container] = ”image.cover“
container
v-lazy
另外,需要提前准备一张占位图片,每个图片默认开始时使用的是占位图。
目录地址:src/directives/lazy/img/cover-def.gif
v-lazy指令实现细节如下:
首先,我们需要声明一个变量 imgs,用来存放当前待被懒加载的图片信息集合。当图片滚动发生时,遍历imgs,并找出在可视区停留的图片进行加载和渲染。
// 用来收集需要懒加载的图片信息 let imgs = []
根据指令的写法,我们需要用到两个钩子函数:
el
binding
binding.arg
import defImage from './img/cover-def.gif' // 用来收集需要懒加载的图片信息 let imgs = [] // 指令配置对象 const lazyDirective = { inserted(el, binding) { const { value, arg } = binding let container = el.parentNode // 设置默认图片 el.src = defImage; // 找到容器 while (container && container.className.indexOf(arg) == -1) { container = container.parentNode } const img = { el: el, src: value, container: { top: container && container.getBoundingClientRect().top || 0, clientHeight: container && container.clientHeight || 0 }, height: el.height || 0 } // 添加到当前需要被懒加载的图片集合内,当滚动时,通过加载真实图片 imgs.push(img) // 立即处理:是否加载真实图片 loadImg(img) }, unbind(el) { // 当前图片的指令解除绑定时,需要从imgs中删除(因为此时当前图片已经销毁) deleteImg(el) } }
通过事件总线,注册 ”iscroll“事件,当 List 组件中的图片容器发生滚动时,会触发图片列表查看及加载判断。
import { debounce } from '@/utils' import eventBus from '@/eventBus' /** * 查看所有待加载的图片 * */ function loadImages() { for (const img of imgs) { loadImg(img) } } // 监听滚动 eventBus.$on('iscroll', debounce(loadImages, 300))
通过 loadImg 函数处理每个带加载图片,是否加载的条件:图片是否在图片容器可视区内。
是否在可视区,分两种情况判断:
来围观下示意图:
上图中的,disTop 大于 负的img.height 或 disTop 小于 container.clientHeight时,当前图片就在container的可视区域内。反之,就不在可视区。细品~ 。实现如下:
/** * 加载真实图片 * * @param {*} img */ function loadImg(img) { const { el, src, height, container } = img img.top = el.getBoundingClientRect().top || 0 const disTop = img.top - container.top // 不在可视区 if (disTop < -height || disTop > container.clientHeight) return // 在可视区 const image = new Image() image.onload = () => { el.src = src } image.src = src console.log('加载了', el.dataset.id) // 加载过的图片,需要将其从imgs中清除 deleteImg(el) }
另外,观察上面代码最后行,需要注意的是:加载过的图片,需要将其从imgs中清除。
从imgs中清除已经加载完成的img,实现如下:
/** * 删除imgs中已加载的真实图片 * * @param {*} el */ function deleteImg(el) { imgs = imgs.filter(img => img.el != el) }
为了保证改指令可以在全局内使用,需要将其注册为Vue的全局指令。实现如下:
import Vue from 'vue' // 注册全局指令 Vue.directive('lazy', lazyDirective) // 导出指令配置 export default lazyDirective
目录地址:src/directives/lazy/index.js
import Vue from 'vue' import { debounce } from '@/utils' import defImage from './img/cover-def.gif' import eventBus from '@/eventBus' // 用来收集需要懒加载的图片信息 let imgs = [] // 指令配置对象 const lazyDirective = { inserted(el, binding) { const { value, arg } = binding let container = el.parentNode // 设置默认图片 el.src = defImage; // 找到容器 while (container && container.className.indexOf(arg) == -1) { container = container.parentNode } const img = { el: el, src: value, container: { top: container && container.getBoundingClientRect().top || 0, clientHeight: container && container.clientHeight || 0 }, height: el.height || 0 } // 添加到当前需要被懒加载的图片集合内,当滚动时,通过加载真实图片 imgs.push(img) // 立即处理:是否加载真实图片 loadImg(img) }, unbind(el) { // 当前图片的指令解除绑定时,需要从imgs中删除(因为此时当前图片已经销毁) deleteImg(el) } } /** * 查看所有待加载的图片 * */ function loadImages() { for (const img of imgs) { loadImg(img) } } /** * 加载真实图片 * * @param {*} img */ function loadImg(img) { const { el, src, height, container } = img img.top = el.getBoundingClientRect().top || 0 const disTop = img.top - container.top // 不在可视区 if (disTop < -height || disTop > container.clientHeight) return // 在可视区 const image = new Image() image.onload = () => { el.src = src } image.src = src console.log('加载了', el.dataset.id) // 加载过的图片,需要将其从imgs中清除 deleteImg(el) } /** * 删除imgs中已加载的真实图片 * * @param {*} el */ function deleteImg(el) { imgs = imgs.filter(img => img.el != el) } // 监听滚动 eventBus.$on('iscroll', debounce(loadImages, 300)) // 注册全局指令 Vue.directive('lazy', lazyDirective) export default lazyDirective
这样,我们就完成了图片懒加载Vue自定义指令的封装。下面来看下效果吧, 这里
根据控制台输出,滚动结束后,只加载可视区内的图片。效果是不是好些了。
本文比较详细讲述了如何一步步封装一个图片懒加载的Vue自定义指令。内容涉及比较多,防抖、事件总线、如果判断图片在可视区等。希望感兴趣的小伙伴可以自己试着操作一遍,相信一定会有更深入的体会和理解。
如果觉得本文对你有帮助,点赞、关注不失联,你的支持是对笔者最大的鼓励!
微信关注 「 乘风破浪大前端 」公众号,发现更多有趣好玩的前端知识和实战。
干货系列文章汇总如下,欢迎 start 、follow 交流学习👏🏻。
https://github.com/szjxxy/fe-happy-interview
关于本文如有任何意见或建议,欢迎评论区讨论、指正。
也许你还想看:
前言
图片懒加载大家都很熟悉了,优点如下:
本文将使用vue自定义指令的方式,实现图片懒加载的功能。要点如下:
先来围观下,不使用图片懒加载功能,图片列表交互效果,这里。
嗯,因为页面会同时请求全部的图片。如果不处理, 一是页面会比较卡,二是滚动到后边会出现白屏,用户体验非常差。接下来,主角登场 —— 图片懒加载指令他来了。
mock
首先使用 mock.js,准备一些图片数据。
目录地址: src/mock/index.js
当请求数据时,返回的数据格式大概是下面的样子:
此时图片数据他来了。
eventBus
图片交互中,我们需要监听图片容器的滚动,这里利用Vue内置的事件通知机制,封装了一个eventBus。该模块保证 eventBus既可以在所有组件内使用,也可以在普通的JS模块里使用。代码如下:
目录地址:src/eventBus.js
列表组件目录地址: src/components/List.vue
上面,我们在created阶段,请求到了mock的图片数据,并且在模板里循环遍历了图片列表。同时,在mounted阶段,我们通过监听图片容器的滚动,当滚动触发时,通过封装好的事件总线(eventBus)发布
“iscroll”
的事件通知。滚动监听他来了。eventBus的更多了解,这里。
debounce
图片懒加载的机制是:当图片列表滚动时,动态的将可视区内的图片加载并渲染出来。但是,当滚动发生时,对图片进行展示的逻辑并非实时执行,而是等到滚动停止后的某个时间点执行,这样就可以避免在滑动过程中触发非必要的图片加载和渲染。所以,debounce防抖处理他来了。
关于防抖和节流,感兴趣的朋友,这里。
v-lazy
对Vue自定义指令不熟悉的朋友,这里。
到目前位置,List组件内的渲染时,会将所有的图片原地址添加到src属性上 ——
:src="image.cover"
。这样,在组件渲染时,全部的图片都会加载和渲染。为了实现图片懒加载的效果,我们需要对src的赋值通过自定义指令做一层代理,并且指令的逻辑单独维护。最新List组件实现,如下:
上面,我们通过
v-lazy:[container] = ”image.cover“
来接管每个图片的原始地址,其中container
为父容器的类名,在指令里判断图片是否在可视区会用到父容器,并且可以在data中动态指定。接下来,我们只需要关注v-lazy
的实现即可。另外,需要提前准备一张占位图片,每个图片默认开始时使用的是占位图。
目录地址:src/directives/lazy/img/cover-def.gif
v-lazy
指令实现细节如下:imgs
首先,我们需要声明一个变量 imgs,用来存放当前待被懒加载的图片信息集合。当图片滚动发生时,遍历imgs,并找出在可视区停留的图片进行加载和渲染。
指令配置
根据指令的写法,我们需要用到两个钩子函数:
el
和 绑定的信息 ——binding
。需要处理逻辑包括:binding.arg
获取到指令参数 —— 图片容器的className,然后找到父容器iscroll 事件订阅
通过事件总线,注册 ”iscroll“事件,当 List 组件中的图片容器发生滚动时,会触发图片列表查看及加载判断。
loadImg
通过 loadImg 函数处理每个带加载图片,是否加载的条件:图片是否在图片容器可视区内。
是否在可视区,分两种情况判断:
来围观下示意图:
上图中的,disTop 大于 负的img.height 或 disTop 小于 container.clientHeight时,当前图片就在container的可视区域内。反之,就不在可视区。细品~ 。实现如下:
另外,观察上面代码最后行,需要注意的是:加载过的图片,需要将其从imgs中清除。
deleteImg
从imgs中清除已经加载完成的img,实现如下:
全局指令注册
为了保证改指令可以在全局内使用,需要将其注册为Vue的全局指令。实现如下:
完整实现
目录地址:src/directives/lazy/index.js
这样,我们就完成了图片懒加载Vue自定义指令的封装。下面来看下效果吧, 这里
根据控制台输出,滚动结束后,只加载可视区内的图片。效果是不是好些了。
小结
本文比较详细讲述了如何一步步封装一个图片懒加载的Vue自定义指令。内容涉及比较多,防抖、事件总线、如果判断图片在可视区等。希望感兴趣的小伙伴可以自己试着操作一遍,相信一定会有更深入的体会和理解。
交流
如果觉得本文对你有帮助,点赞、关注不失联,你的支持是对笔者最大的鼓励!
微信关注 「 乘风破浪大前端 」公众号,发现更多有趣好玩的前端知识和实战。
干货系列文章汇总如下,欢迎 start 、follow 交流学习👏🏻。
关于本文如有任何意见或建议,欢迎评论区讨论、指正。
也许你还想看: