Bootstrap

图片懒加载(自定义指令)

图片懒加载

实现思路

使用自定义指令实现通用图片懒加载(在图片到达视口内时再进行加载)

自定义指令

可以理解为另一个生命周期,在各个钩子函数中处理相关内容,常用的钩子函数如下

// 自定义指令配置
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);
    }
}

在这里插入图片描述

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;