懒加载图片

  • 这个页面在 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>

相关推荐
计算机魔术师1 小时前
美国司法部正式站队 OpenAI,AI 训练的版权账要这么算了
前端
IT_陈寒1 小时前
React Hooks闭包陷阱差点让我加班到凌晨
前端·人工智能·后端
风骏时光牛马1 小时前
疑难Bug根因定位与问题复盘分析
前端
创新技术阁2 小时前
FastapiAdmin 系统日志体系与核心配置参数详解
前端·后端·fastapi
掘金酱2 小时前
【社区公告】签到与矿石奖励解耦说明
前端
promiseThen2 小时前
LWC Workflow:用 7 个 Cursor Skill 搭一条 AI 协作开发流水线
前端·ai编程
一个游离的指针3 小时前
函数管道:消除深度嵌套调用
前端·javascript
浅诺3 小时前
Nginx sub_filter 的“幽灵陷阱”:为什么页面能打开,懒加载的 JS 却全是 404?
前端
PBitW3 小时前
为什么vite中TS报错,可以继续运行?Webpack不行?
前端·webpack·typescript·vite
光影少年3 小时前
react navite手写 FlatList 优化配置
前端·react native·react.js