-----------------------------------------------------------
图片懒加载
实现思路
使用自定义指令实现通用图片懒加载(在图片到达视口内时再进行加载)
自定义指令
可以理解为另一个生命周期,在各个钩子函数中处理相关内容,常用的钩子函数如下
// 自定义指令配置
export default {
// 只调用一次,指令第一次绑定到元素时调用
bind(el,boundings){
// 处理
},
// 被绑定元素插入父节点时调用
inserted(el,boundings){
// 处理
},
// 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前
update(el,boundings){
// 处理
}
// 只调用一次,指令与元素解绑时调用
unbind(el){
// 处理
}
}
通常是使用对象来配置,当bind钩子与update钩子内容一致,而不关心其它的钩子时可以简写为函数配置:
// 自定义指令配置
export default function(el,boundings){
// 处理
}
使用mock模拟随机图片
Mock.mock(/^\/api\/blog(\?.+)?$/,"get",function(options){
// 使用动态模拟数据
const query = qs.parse(options.url);
return Mock.mock({
"code": 0,
"msg": "string",
"data": {
"total|200-800": 0,
[`rows|${query.limit||10}`]: [
{
"id": "@guid",
"title": "@ctitle",
"description": "@cparagraph(1,10)",
"createDate": "@date('T')",
"scanNumber|1000-10000": 0,
"commentNumber|0-400": 0,
"category": {
"id|0-10": 0,
"name": "分类@id"
},
"thumb|1": ["@image(300x250,@color,#fff,@natural)",null]
}
]
}
})
})
列表组件如下(主要内容):
<template>
<div class="bloglist-container" v-loading="isLoading" ref="bloglistRef">
<ul class="list">
<li class="item" v-for="item in data.rows" :key="item.id">
<router-link :to="{
name: '/blogDetail',
params: {
id: item.id
}
}">
<div class="img" v-if="item.thumb">
<!-- 绑定自定义指令 -->
<img :src="item.thumb" class="image" v-lazy="item.thumb">
</div>
</router-link>
<div :class="item.thumb?'txt':'ptxt'">
<router-link :to="{
name: '/blogDetail',
params: {
id: item.id
}
}">
<h2 class="title">{{ item.title }}</h2>
</router-link>
<div class="info">
<span>日期:{{ formatDate(item.createDate) }}</span>
<span>浏览:{{ item.scanNumber }}</span>
<span>评论:{{ item.commentNumber }}</span>
<router-link :to="{
name:'/categoryBlog',
params: {categoryId: item.category.id}
}">
<span class="cate">分类:{{ item.category.id }}</span>
</router-link>
</div>
<p class="desc">{{ item.description }}</p>
</div>
</li>
</ul>
<Pager :current="this.page" :total="data.total" :limit="this.limit"
@pageChange="handlePageChange"/>
</div>
</template>
<script>
import * as blogApi from '@/api/blog.js'
import fetchData from '@/mixins/fetchData';
import { formatDate } from '@/utils/formatDate';
import Pager from '@/components/Pager';
import scrollEvent from '@/mixins/scrollEvent';
export default {
mixins: [fetchData({}),scrollEvent("bloglistRef")],
components: {
Pager,
BackTop
},
computed:{
page(){
return +this.$route.query.page || 1;
},
limit(){
return +this.$route.query.limit || 10;
},
categoryId(){
return +this.$route.params.categoryId || -1;
}
},
methods:{
// 获取文章列表
async getFetchData(){
return await blogApi.getBlog(this.page,this.limit,'',this.categoryId);
},
formatDate,
},
}
</script>
配置自定义指令
使用一个数组保存ajax获取的所有列表元素相关内容,然后在元素插入父组件时先进行一次处理,将初始就在视口的图片加载,已经加载完毕的元素相关内容会被数组删除,然后通过滚动事件来进行图片处理,最后进行事件清除,具体如下:
import defaultImg from '@/assets/default.gif';
import eventBus from '@/eventBus';
import {deBounce} from '@/utils'
// 刚开始ajax获取的所有图片相关节点、src都在内,处理后会被此数组剔除
let imgs = []; // 需要处理的图片数组
/**
* 处理图片,先赋值默认图片,如在视口内则加载源图片
* @param {*} i 数组内容,包含该元素dom,以及图片源src
*/
function setImage(i){
// 先使用默认图片
i.dom.src = defaultImg;
// 判断是否位于视口内
const clientHeight = document.documentElement.clientHeight;
const domTop = i.dom.getBoundingClientRect().top;
const domHeight = i.dom.getBoundingClientRect().height || 250;
// console.log(clientHeight,domTop,domHeight);
if(domTop >= -domHeight && domTop <= clientHeight){
// 在视口内
i.dom.src = i.src;
// 处理完成后清除数组中相关内容
imgs.filter((img)=> img!==i);
}
}
/**
* 批量处理所有图片
*/
function setImages(){
for (const i of imgs) {
setImage(i);
}
}
/**
* 用于触发setImages
*/
function handleScroll() {
setImages();
}
// 事件总线监听滚动
eventBus.$on('blogMainScroll',deBounce(handleScroll,50));
// 自定义指令配置
export default {
// 被绑定元素插入父节点时调用
inserted(el,boundings){
const img = {
dom: el,
src: boundings.value
};
imgs.push(img);
setImage(img) // 处理最开始的数组内容
},
// 只调用一次,指令与元素解绑时调用
unbind(el){
// 处理结束后,清除数组内相关内容,避免重复处理
imgs = imgs.filter((img) => img.dom !== el);
}
}