使用场景和指令用法
使用场景
如电商网站,首页通常会很长,用户不一定能访问到页面靠下的图片。
比如用户可能在上面点击某个图片后就完成了购物,那么下面图片加载反而耗费资源。
这类图片可以通过懒加载的手段做到只有进入视口区域才发送图片请求
指令用法:
<img v-img-lazy="url">
// v-img-lazy为自定义指令
在img身上绑定指令,该图片只有在正式进入到视口区域时才会发送图片网络请求
实现思路和步骤
核心原理:图片进入视口才发送资源请求
vue3自定义指令
官方示例
当一个 input 元素被 Vue 插入到 DOM 中后,它会被自动聚焦:
const focus = {
mounted: (el) => el.focus()
}
export default {
directives: {
// 在模板中启用 v-focus
focus
}
}
<input v-focus />
指令钩子:
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode) {
// 下面会介绍各个参数的细节
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode) {}
}
指令的钩子会传递以下几种参数:
-
el
:指令绑定到的元素。这可以用于直接操作 DOM。 -
binding
:一个对象,包含以下属性。value
:传递给指令的值。例如在v-my-directive="1 + 1"
中,值是2
。oldValue
:之前的值,仅在beforeUpdate
和updated
中可用。无论值是否更改,它都可用。arg
:传递给指令的参数 (如果有的话)。例如在v-my-directive:foo
中,参数是"foo"
。modifiers
:一个包含修饰符的对象 (如果有的话)。例如在v-my-directive.foo.bar
中,修饰符对象是{ foo: true, bar: true }
。instance
:使用该指令的组件实例。dir
:指令的定义对象。
-
vnode
:代表绑定元素的底层 VNode。 -
prevVnode
:代表之前的渲染中指令所绑定元素的 VNode。仅在beforeUpdate
和updated
钩子中可用。
以我们要实现的图片懒加载为例
我们定义一个自定义指令,叫img-lazy
其中我们只要使用两个参数,el和binding
el就是你所绑定的那个dom元素,我们的指令绑定在img标签上,那么el就获得这个标签上的dom元素。
binding.value,获取该对象的值,也就是我们要获取的img标签的src
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
// 自定义全局指令
app.directive('img-lazy', {
mounted(el, binding) {
// el: 指令绑定的那个dom元素,这里也就是img
// binding: binding.value 指令等于后面绑定的表达式的值, 图片的url
console.log(el, binding.value)
}
})
我们绑定在一个img元素上实验一下。
<img v-img-lazy="item.picture" :src="item.picture" alt="" />
查看控制台输出
可以看到输出了el:整个img标签,和binding.value:src属性
接下来我们只需要判断视图是否已经加载即可
我们使用之前用到过的一个库来实现:通过vueuse实现头部导航栏固定
我们当时使用的是useScroll来获取滚动的距离
我们这次使用另一个很好用的组件useIntersectionObserver
这个组件可以捕捉目标dom元素进入到视口的时候
我们分析一下官方文档提供的代码示例
<script setup>
import { useIntersectionObserver } from '@vueuse/core'
import { ref } from 'vue'
// target是你要监视的组件
const target = ref(null)
const targetIsVisible = ref(false)
const { stop } = useIntersectionObserver(
target,
// 如果进入视口,则isIntersecting 为true
([{ isIntersecting }], observerElement) => {
targetIsVisible.value = isIntersecting
},
)
</script>
<template>
<div ref="target">
<h1>Hello world</h1>
</div>
</template>
根据官方文档提供示例,我们改造一个自己的函数
app.directive('img-lazy', {
mounted(el, binding) {
// el: 指令绑定的那个dom元素,这里也就是img
// binding: binding.value 指令等于后面绑定的表达式的值, 图片的url
const { stop } = useIntersectionObserver(el, ([{ isIntersecting }]) => {
console.log(isIntersecting)
if (isIntersecting) {
el.src = binding.value
stop()
}
})
}
})
当进入视口的时候,我们将图片的src设置,发送请求实现图片的懒加载
测试:
当我们没有滚动到指定位置时,图片未加载,输出false
滚动到我们监听的四张图片后,输出true,并且请求四张图片
懒加载实现成功
代码改进
现在我们把懒加载的逻辑都写到main.js里了,这显然是不合理的
所以我们最好将自定义指令封装为一个插件,然后在main.js中引入
创建一个index.js
将刚才写的逻辑转移到这里
import { useIntersectionObserver } from '@vueuse/core'
// 定义懒加载插件
export const LazyPlugin = {
install(app) {
// 自定义全局指令
app.directive('img-lazy', {
mounted(el, binding) {
// el: 指令绑定的那个dom元素,这里也就是img
// binding: binding.value 指令等于后面绑定的表达式的值, 图片的url
const { stop } = useIntersectionObserver(el, ([{ isIntersecting }]) => {
console.log(isIntersecting)
if (isIntersecting) {
el.src = binding.value
stop()
}
})
}
})
}
}
在main.js中调用
import { LazyPlugin} from '@/directives/index'
app.use(LazyPlugin)
这样就完美啦
一个人可以走的很快,一群人才能走的更远, 编程这条路有苦有乐,可唯有坚持才是学好编程的必由之路。让我们一起出发,互相帮助,互相监督。如果你不够自律,总是坚持两天却又松懈。没有体会过披荆斩棘和实现目标的快乐和满足。加入我们吧,我们一同走得更远!
主要活动:学习交流群早起打卡,算法题练习打卡,项目讨论交流,精彩文章书籍分享,问题分享和解决,生活乐趣畅谈。
加入方式:添加微信加入学习群微信号了解更多:linlinzongzhong