Skip to content

Commit 4cfdcab

Browse files
author
李磊
committed
feat: v-lazy
1 parent 5e60e20 commit 4cfdcab

File tree

8 files changed

+452
-9
lines changed

8 files changed

+452
-9
lines changed

apps/docs/.vitepress/config/zh.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ export const zh = defineConfig({
4747
},
4848
{
4949
text: '指令(@ssuperlilei/directives)',
50-
items: [{ text: 'vFocus 聚焦', link: '/guide/directives/vFocus' }],
50+
items: [
51+
{ text: 'vFocus 聚焦', link: '/guide/directives/vFocus' },
52+
{ text: 'vLazy 懒加载', link: '/guide/directives/vLazy' },
53+
],
5154
},
5255
{
5356
text: '工具函数(@ssuperlilei/utils)',
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<template>
2+
<div class="v-lazy-demo">
3+
<h1>v-lazy 图片懒加载指令演示</h1>
4+
5+
<div class="description">
6+
<p>v-lazy 指令用于图片的懒加载,只有当图片进入视口或即将进入视口时才会加载图片。</p>
7+
<p>滚动页面查看效果,图片将在进入视口时才开始加载。</p>
8+
</div>
9+
10+
<div v-for="(image, index) in images" :key="index" class="image-container">
11+
<div class="image-card">
12+
<div class="image-wrapper">
13+
<img
14+
v-lazy="image.url"
15+
:alt="'示例图片 ' + (index + 1)"
16+
class="lazy-image"
17+
data-loaded="false"
18+
@load="onImageLoad"
19+
/>
20+
<div class="loading-placeholder">加载中...</div>
21+
</div>
22+
<div class="image-info">
23+
<h3>图片 {{ index + 1 }}</h3>
24+
<p>{{ image.description }}</p>
25+
</div>
26+
</div>
27+
</div>
28+
</div>
29+
</template>
30+
31+
<script setup lang="ts">
32+
import { ref } from 'vue';
33+
import { vLazy } from '@ssuperlilei/directives';
34+
35+
// 图片数组,包含真实的图片URL
36+
const images = ref([
37+
{
38+
url: 'https://picsum.photos/id/1/800/400',
39+
description: '这是一张使用 v-lazy 指令懒加载的图片。它只会在滚动到视图中时才会加载。',
40+
},
41+
{
42+
url: 'https://picsum.photos/id/10/800/400',
43+
description: '懒加载可以减少初始页面加载时间,提高用户体验。',
44+
},
45+
{
46+
url: 'https://picsum.photos/id/100/800/400',
47+
description: 'v-lazy 指令使用 IntersectionObserver API 来检测元素可见性。',
48+
},
49+
{
50+
url: 'https://picsum.photos/id/1000/800/400',
51+
description: '当图片元素至少有 10% 可见或在视口 100px 范围内时,图片将开始加载。',
52+
},
53+
{
54+
url: 'https://picsum.photos/id/1001/800/400',
55+
description: '图片加载后,IntersectionObserver 将停止对该元素的观察。',
56+
},
57+
{
58+
url: 'https://picsum.photos/id/1002/800/400',
59+
description: '这个指令非常适合长页面和图片密集型应用程序。',
60+
},
61+
{
62+
url: 'https://picsum.photos/id/1003/800/400',
63+
description: '通过减少不必要的图片加载,可以节省带宽并提高页面性能。',
64+
},
65+
{
66+
url: 'https://picsum.photos/id/1004/800/400',
67+
description: '如果你已经滚动到了这里,前面的所有图片已经被懒加载了。',
68+
},
69+
]);
70+
71+
// 图片加载完成时的处理函数
72+
const onImageLoad = (event: Event) => {
73+
const img = event.target as HTMLImageElement;
74+
img.dataset.loaded = 'true';
75+
};
76+
</script>
77+
78+
<style scoped>
79+
.v-lazy-demo {
80+
max-width: 800px;
81+
padding: 20px;
82+
margin: 0 auto;
83+
}
84+
85+
h1 {
86+
margin-bottom: 30px;
87+
text-align: center;
88+
}
89+
90+
.description {
91+
padding: 15px;
92+
margin-bottom: 30px;
93+
background-color: #f5f5f5;
94+
border-radius: 8px;
95+
}
96+
97+
.image-container {
98+
margin-bottom: 50px;
99+
}
100+
101+
.image-card {
102+
overflow: hidden;
103+
border-radius: 8px;
104+
box-shadow: 0 4px 12px rgb(0 0 0 / 10%);
105+
}
106+
107+
.image-wrapper {
108+
position: relative;
109+
display: flex;
110+
align-items: center;
111+
justify-content: center;
112+
aspect-ratio: 2/1;
113+
overflow: hidden;
114+
background-color: #f0f0f0;
115+
}
116+
117+
.lazy-image {
118+
position: absolute;
119+
top: 0;
120+
left: 0;
121+
width: 100%;
122+
height: 100%;
123+
object-fit: cover;
124+
opacity: 0;
125+
transition: opacity 0.3s ease;
126+
}
127+
128+
.lazy-image[data-loaded='true'] {
129+
opacity: 1;
130+
}
131+
132+
.loading-placeholder {
133+
display: flex;
134+
align-items: center;
135+
justify-content: center;
136+
width: 100%;
137+
height: 100%;
138+
font-size: 18px;
139+
color: #666;
140+
}
141+
142+
.image-info {
143+
padding: 15px;
144+
}
145+
146+
h3 {
147+
margin-top: 0;
148+
margin-bottom: 10px;
149+
}
150+
</style>
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# v-lazy 懒加载指令
2+
3+
## 介绍
4+
5+
`v-lazy` 指令用于实现图片的懒加载功能,只有当图片进入视口或即将进入视口时才会加载图片。这种延迟加载图片的方式可以显著提高页面性能,减少不必要的网络请求,尤其适合图片密集型应用。
6+
7+
## 特性
8+
9+
- 基于 IntersectionObserver API,性能高效
10+
- 只有当图片至少 10% 可见或在视口 100px 范围内时,才会加载图片
11+
- 自动停止对已加载图片的观察
12+
- 简单易用,只需添加一个指令
13+
14+
## 安装
15+
16+
```bash
17+
# npm
18+
npm install @ssuperlilei/directives
19+
20+
# yarn
21+
yarn add @ssuperlilei/directives
22+
23+
# pnpm
24+
pnpm add @ssuperlilei/directives
25+
```
26+
27+
## 注册
28+
29+
### 全局注册
30+
31+
```typescript
32+
import { createApp } from 'vue';
33+
import { vLazy } from '@ssuperlilei/directives';
34+
import App from './App.vue';
35+
36+
const app = createApp(App);
37+
app.directive('lazy', vLazy);
38+
app.mount('#app');
39+
```
40+
41+
### 局部注册
42+
43+
```vue
44+
<script setup>
45+
import { vLazy } from '@ssuperlilei/directives';
46+
</script>
47+
48+
<template>
49+
<img v-lazy="imageUrl" alt="Lazy loaded image" />
50+
</template>
51+
```
52+
53+
## 基本用法
54+
55+
只需将 `v-lazy` 指令添加到 `<img>` 标签上,并传入图片 URL 即可:
56+
57+
```vue
58+
<img v-lazy="'https://example.com/image.jpg'" alt="懒加载图片" />
59+
```
60+
61+
当图片进入视口或接近视口时,指令会自动设置 `src` 属性,加载图片。
62+
63+
## 示例
64+
65+
以下是一个使用 `v-lazy` 指令实现图片懒加载的示例:
66+
67+
::: raw
68+
<demo class="vp-raw" vue="directives/vLazy/basic.vue" />
69+
:::
70+
71+
## API 参考
72+
73+
### 指令参数
74+
75+
`v-lazy` 指令接受一个字符串参数,表示图片的 URL:
76+
77+
```vue
78+
<img v-lazy="imageUrl" />
79+
```
80+
81+
### 配置选项
82+
83+
目前 `v-lazy` 指令使用以下固定配置:
84+
85+
- `rootMargin`: 100px - 图片距离视口 100px 内时开始加载
86+
- `threshold`: 0.1 - 图片 10% 可见时开始加载
87+
88+
## 注意事项
89+
90+
- `v-lazy` 指令只能用于 `<img>` 元素,用于其他元素会在控制台发出警告
91+
- 该指令使用 IntersectionObserver API,在不支持该 API 的旧浏览器上需要提供 polyfill
92+
- 为了更好的用户体验,建议为懒加载的图片提供加载状态的视觉反馈
93+
94+
## 浏览器兼容性
95+
96+
`v-lazy` 指令依赖于 IntersectionObserver API,该 API 在现代浏览器中得到广泛支持。如需在旧浏览器中使用,请考虑添加相应的 polyfill。

packages/directives/src/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import type { App, Directive } from 'vue';
1+
import type { App } from 'vue';
22
import vFocus from './v-focus';
3+
import vLazy from './v-lazy';
34

45
const directives = {
56
vFocus,
7+
vLazy,
68
};
79
export { version } from './version';
810

@@ -25,6 +27,6 @@ const globalRegister = (app: App, directiveKeys?: DirectiveKeys[]): void => {
2527
});
2628
}
2729
};
28-
export { vFocus, globalRegister };
30+
export { vFocus, vLazy, globalRegister };
2931

3032
export default directives;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { DirectiveBinding } from 'vue';
2+
3+
const lazy = {
4+
mounted(el: HTMLImageElement, binding: DirectiveBinding<string>) {
5+
// 确保 el 是 <img> 标签
6+
if (el.tagName !== 'IMG') {
7+
console.warn('v-lazy 只能用于 <img> 元素');
8+
return;
9+
}
10+
11+
const loadImage = (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => {
12+
entries.forEach((entry) => {
13+
if (entry.isIntersecting) {
14+
el.src = binding.value; // 设置真实图片地址
15+
observer.unobserve(el); // 加载后停止观察
16+
}
17+
});
18+
};
19+
20+
const observer = new IntersectionObserver(loadImage, {
21+
rootMargin: '100px', // 提前 100px 触发加载
22+
threshold: 0.1, // 当图片 10% 可见时加载
23+
});
24+
25+
observer.observe(el);
26+
},
27+
};
28+
29+
export default lazy;

playground/src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import 'ant-design-vue/dist/reset.css';
88
// 引入@ssuperlilei/ui
99
import ll_libUI from '~@ssuperlilei/ui';
1010
import '@ssuperlilei/ui/style.css';
11-
// import { globalRegister } from '@ssuperlilei/directives';
11+
// import { globalRegister } from '~@ssuperlilei/directives';
1212

1313
const app = createApp(App);
1414
app.use(Antd); // 全局引入antdv组件

0 commit comments

Comments
 (0)