懒加载图片

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

相关推荐
子兮曰4 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰4 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万4 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝4 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
A黄俊辉A4 天前
uniapp webview中实现 app和内嵌的H5双向通信
vue.js·json
三十而立洋4 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁4 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄4 天前
JavaScript 隐式全局变量解析
javascript
honkun64 天前
vue 表格组件 vxe-table 配置 ajax 请求自动加载数据与表单查询
vue.js·vxe-table
kybs19914 天前
全球灾害数据分析可视化 毕业设计-附源码66794
vue.js·spring boot·mysql·安全·django·c#·asp.net