懒加载图片

  • 这个页面在 goodsInfo 插槽里通过 <LazyImage> 渲染商品缩略图,具体位于 index.vue ,只有 row.goods?.thumbUrl 有值时才会走 <LazyImage>,否则展示一个固定大小的 "预览" 占位块;为了维持尺寸,缩略图宽高都由 goodsThumbSize 计算后传入 width/height 属性。
  • LazyImage 组件位于 LazyImage.vue (line 1),内部用 wrapperRef 挂在到外层 <div> 上,并通过 IntersectionObserver(rootMargin: '120px 0px')监听元素是否进入视窗;一旦 entry.isIntersecting,调用 loadImage 把 shouldLoad 设为 true 并断开 observer,确保图片只加载一次。
  • 模板里 shouldLoad 为 true 时才渲染 ant-design-vue 的 <Image>,并通过 imageAttrs、alt、width、height、src 把 props/attrs 透传进去;未加载前会渲染带灰背景的 .lazy-image-placeholder 占位层,避免布局抖动。
  • resolvedPreview 计算属性在 shouldLoad 变 true 以后才起作用,用来把 previewSrc、preview 相关配置组装成 Ant Design Image 可识别的预览配置,若 props.preview 为 false 则禁用预览;wrapperStyle 用来将传入的宽高参数转成 CSS 字符串保证容器尺寸。
  • 组件还支持透传其它属性 (useAttrs 去除 class/style 后的属性会直接传给 <Image>),并在 onBeforeUnmount 里清理 IntersectionObserver,保证组件卸载时不会留下监听器。

<script lang="ts" setup>

import { Image } from 'ant-design-vue';

import {

computed,

onBeforeUnmount,

onMounted,

ref,

useAttrs,

} from 'vue';

defineOptions({

name: 'LazyImage',

inheritAttrs: false,

});

const props = defineProps({

src: { type: String, default: '' },

previewSrc: { type: String, default: '' },

alt: { type: String, default: '' },

width: { type: [Number, String], default: undefined },

height: { type: [Number, String], default: undefined },

preview: { type: [Boolean, Object], default: true },

});

const attrs = useAttrs();

const wrapperRef = ref<HTMLElement | null>(null);

const shouldLoad = ref(false);

let observer: null | IntersectionObserver = null;

const imageAttrs = computed(() => {

const { class: _class, style: _style, ...rest } = attrs;

return rest;

});

const wrapperStyle = computed(() => {

const style: Record<string, string> = {};

if (props.width !== undefined) {

style.width =

typeof props.width === 'number'

? `${props.width}px`

: String(props.width);

}

if (props.height !== undefined) {

style.height =

typeof props.height === 'number'

? `${props.height}px`

: String(props.height);

}

return style;

});

const resolvedPreview = computed(() => {

if (!shouldLoad.value) return false;

if (props.preview === false) return false;

const source = props.previewSrc || props.src;

if (props.preview === true) {

return source ? { src: source } : true;

}

const base =

props.preview && typeof props.preview === 'object' ? props.preview : {};

return { src: source, ...(base as Record<string, unknown>) };

});

function loadImage() {

if (shouldLoad.value) return;

shouldLoad.value = true;

if (observer) {

observer.disconnect();

observer = null;

}

}

onMounted(() => {

const el = wrapperRef.value;

if (!el) {

shouldLoad.value = true;

return;

}

if (typeof IntersectionObserver === 'undefined') {

shouldLoad.value = true;

return;

}

observer = new IntersectionObserver(

(entries) => {

if (entries.some((entry) => entry.isIntersecting)) {

loadImage();

}

},

{ rootMargin: '120px 0px' },

);

observer.observe(el);

});

onBeforeUnmount(() => {

if (observer) {

observer.disconnect();

observer = null;

}

});

</script>

<template>

<div

ref="wrapperRef"

:class="attrs.class"

:style="[wrapperStyle, attrs.style]"

>

<Image

v-if="shouldLoad"

v-bind="imageAttrs"

:src="src"

:preview="resolvedPreview"

:alt="alt"

:width="width"

:height="height"

/>

<div v-else class="lazy-image-placeholder" />

</div>

</template>

<style scoped>

.lazy-image-placeholder {

width: 100%;

height: 100%;

background: #f5f5f5;

}

</style>

相关推荐
Hyyy5 小时前
普通前端续命周报——第1周
前端·javascript
KaMeidebaby6 小时前
卡梅德生物技术快报|抗独特型抗体开发:半抗原检测技术瓶颈拆解,抗独特型抗体开发工程化实践
前端·数据库·人工智能·其他·百度·新浪微博
2501_940041746 小时前
纯前端创意交互:五款全新实用工具与视觉应用生成指南
前端·交互
刀法如飞6 小时前
《道德经》简单解说版-第 2 章:天下皆知美之为美
前端·后端·面试
GISer_Jing8 小时前
Three.JS渲染架构解读
java·javascript·架构
发现一只大呆瓜8 小时前
超全 Vite 性能优化指南:网络、资源、预渲染三维落地方案
前端·面试·vite
IT_陈寒8 小时前
Vue的computed属性怎么突然不更新了?
前端·人工智能·后端
时寒的笔记8 小时前
day13~14核心案例某采招网
开发语言·javascript·ecmascript
智商不够_熬夜来凑8 小时前
【Picker】单选多选
前端·javascript·vue.js