Vue使用<Suspense/>实现图片加载组件
什么是Suspense?
Suspense是Vue的内置组件,能让你管理组件加载时的等待、出错和最终渲染逻辑。
主要配合异步组件、async/await 版的 setup() 使用。
它提供两个插槽:
-
default(默认插槽):要渲染的异步内容 -
fallback:等待异步内容加载时显示的兜底内容
话不多说,上代码:
第一步,实现图片异步组件
<template>
<img
:src="props.src"
:alt="props.alt"
:style="{
width: setWidth,
height: setHeight,
borderRadius: rounded,
objectFit: 'cover'
}"
/>
</template>
// =================================== 此处为计算属性逻辑 ================================
<script setup lang="ts">
import { computed, ref } from "vue";
// 图片形状可选项
type ShapeOption = "circle" | "square" | "roundRect";
interface ILazyImage {
src: string;
width:string|number;
height:string|number;
delay?:number;//延迟执行?
timeout?:number;//超时时间?
shape?:ShapeOption;//图片形状
alt?:string;
}
const props = defineProps<ILazyImage>();
//计算超时
const timeoutSet = computed(() => {
let time = Number(props.timeout) || 5;
return time * 1000;
});
//计算延迟
const delayTime = computed(() => {
let time = Number(props.delay) || 0.1;
return time * 1000;
});
//计算宽度
const setWidth = computed(() => {
const val = parseFloat(props.width)
if(!val) return '100px'
return val + "px";
});
//计算高度
const setHeight = computed(() => {
const val = parseFloat(props.Height)
if(!val) return '100px'
return val + "px";
});
// 计算圆角
const rounded = computed(() => {
switch (props.shape) {
case "circle":
return "50%";
case "square":
return "0";
case "roundRect":
return "10px";
default:
return "10px";
}
});
//========================实现异步加载图片==================================
const loadImage = (src: string) => {
return new Promise((resolve, reject) => {
// 创建一个AbortController,用于取消请求
const controller = new AbortController();
const signal = controller.signal;
const timeoutId = setTimeout(() => {
// 超时时取消请求
controller.abort();
reject(new Error("图片加载超时"));
}, timeoutSet.value);
setTimeout(() => {
const image = new Image();
image.src = src;
image.onload = () => {
clearTimeout(timeoutId);
resolve(src);
};
image.onerror = () => {
clearTimeout(timeoutId);
reject(new Error("图片加载失败"));
};
}, delayTime.value);
});
};
// 执行图片加载(async/await 触发 Suspense 等待)
await loadImage(props.src);
</script>
第二步:实现懒加载组件
-
default(默认插槽):要渲染的异步内容 -
fallback:等待异步内容加载时显示的兜底内容 - 使用
v-bind()绑定属性
在 <Suspense></Suspense>中直接使用异步组件即可,<template #fallback></template>则是加载时显示
<template>
<div>
<Suspense>
<template #default>
<div class="image">
<Image v-bind="{ ...props }"></Image>
</div>
</template>
<template #fallback>
<div class="skeleton"></div>
</template>
</Suspense>
</div>
</template>
<script setup lang="ts">
import { computed } from "vue";
import Image from "./Image.vue";
// 图片形状可选项
type ShapeOption = "circle" | "square" | "roundRect";
interface ILazyImage {
src: string;
width:string|number;
height:string|number;
delay?:number;//延迟执行?
timeout?:number;//超时时间?
shape?:ShapeOption;//图片形状
alt?:string;
}
const props = defineProps<ILazyImage>();
//计算超时
const timeoutSet = computed(() => {
let time = Number(props.timeout) || 5;
return time * 1000;
});
//计算延迟
const delayTime = computed(() => {
let time = Number(props.delay) || 0.1;
return time * 1000;
});
//计算宽度
const setWidth = computed(() => {
const val = parseFloat(props.width)
if(!val) return '100px'
return val + "px";
});
//计算高度
const setHeight = computed(() => {
const val = parseFloat(props.Height)
if(!val) return '100px'
return val + "px";
});
// 计算骨架屏动画:遮罩宽度(取容器宽度的 40%)
const setSeletonWidth = computed(() => {
// 提取宽度数值(兼容 px 单位)
const widthNum = parseFloat(setWidth.value) || 100;
return `${widthNum * 0.4}px`;
});
// 骨架屏动画起始位置(从容器左侧外 30% 开始)
const setSeletonStartRight = computed(() => {
const widthNum = parseFloat(setWidth.value) || 100;
return `-${widthNum * 1.3}px`; // 起始在容器左侧外
});
// 骨架屏动画结束位置(到容器右侧外 30%)
const setSeletonEndRight = computed(() => {
const widthNum = parseFloat(setWidth.value) || 100; return `${widthNum * 1.3}px`;
// 结束在容器右侧外
});
// 计算圆角
const rounded = computed(() => {
switch (props.shape) {
case "circle":
return "50%";
case "square":
return "0";
case "roundRect":
return "10px";
default:
return "10px";
}
});
</script>
<style scoped>
.image {
overflow: hidden;
}
.skeleton {
width: v-bind(setWidth);
height: v-bind(setHeight);
background-color: #dfe4ea;
overflow: hidden;
position: relative;
border-radius: v-bind(rounded);
}
.skeleton::before {
content: "";
display: block;
background-color: #ced6e0;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
position: absolute;
top: 0;
right: v-bind(setSeletonStartRight);
width: v-bind(setSeletonWidth);
height: v-bind(setHeight);
transform: skewX(-30deg);
animation: ping 1s infinite ease-out;
filter: blur(10px);
}
@keyframes ping {
from {
right: v-bind(setSeletonStartRight);
}
to {
right: v-bind(setSeletonEndRight);
}
}
</style>
补充
-
代码内有些重复的内容,可使用另外的
ts来声明或实现。 -
其中可能有些变量或实现过程有误,此文章仅供参考。