懒加载图片

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

相关推荐
weixin_4277716110 分钟前
前端调试隐藏元素
前端
threelab22 分钟前
Three.js 代码云效果 | 三维可视化 / AI 提示词
开发语言·javascript·人工智能
爱上好庆祝1 小时前
学习js的第五天
前端·css·学习·html·css3·js
C澒1 小时前
IntelliPro 产研协作平台:基于 AI Agent 的低代码智能化配置方案设计与实现
前端·低代码·ai编程
一袋米扛几楼982 小时前
【Git】规范化协作:详解 GitHub 工作流中的 Issue、Branch 与 Pull Request 最佳实践
前端·git·github·issue
网络点点滴2 小时前
前端与后端的区别与联系
前端
yqcoder2 小时前
JavaScript 柯里化:把“大餐”拆成“小炒”的艺术
开发语言·javascript·ecmascript
每天吃饭的羊2 小时前
JSZip的使用
开发语言·javascript
EnCi Zheng2 小时前
M5-markconv自定义CSS样式指南 [特殊字符]
前端·css·python
kyriewen2 小时前
你的网页慢,用户不说直接走——前端性能监控教你“读心术”
前端·性能优化·监控