diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 064392c0ab2..8a666e79177 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -12,6 +12,7 @@ ### Feats +- 🌟 `n-image` adds `lazy` prop, closes [#3055](https://github.com/TuSimple/naive-ui/issues/3055). - Exports `NTooltipInst` type. - `n-data-table` adds `render-cell` prop, closes [#3095](https://github.com/TuSimple/naive-ui/issues/3095). - `n-space` adds `wrap-item` prop. diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index 09f4e7dc08b..b8c81c51eea 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -12,6 +12,7 @@ ### Feats +- 🌟 `n-image` 新增 `lazy` 属性,关闭 [#3055](https://github.com/TuSimple/naive-ui/issues/3055) - 导出 `NTooltipInst` 类型 - `n-data-table` 新增 `render-cell` 属性,关闭 [#3095](https://github.com/TuSimple/naive-ui/issues/3095) - `n-space` 新增 `wrap-item` 属性 diff --git a/src/image/demos/enUS/index.demo-entry.md b/src/image/demos/enUS/index.demo-entry.md index 41695c8bfaf..ef5b969cfc7 100644 --- a/src/image/demos/enUS/index.demo-entry.md +++ b/src/image/demos/enUS/index.demo-entry.md @@ -11,6 +11,7 @@ error.vue preview-disabled.vue custom.vue tooltip.vue +lazy.vue ``` ## API diff --git a/src/image/demos/enUS/lazy.demo.vue b/src/image/demos/enUS/lazy.demo.vue new file mode 100644 index 00000000000..09a19d24989 --- /dev/null +++ b/src/image/demos/enUS/lazy.demo.vue @@ -0,0 +1,16 @@ + +# lazyLoad + + + diff --git a/src/image/demos/zhCN/index.demo-entry.md b/src/image/demos/zhCN/index.demo-entry.md index 71595904037..ded951aa4c1 100644 --- a/src/image/demos/zhCN/index.demo-entry.md +++ b/src/image/demos/zhCN/index.demo-entry.md @@ -12,6 +12,7 @@ preview-disabled.vue custom.vue tooltip.vue full-debug.vue +lazy.vue ``` ## API diff --git a/src/image/demos/zhCN/lazy.demo.vue b/src/image/demos/zhCN/lazy.demo.vue new file mode 100644 index 00000000000..58fda923f30 --- /dev/null +++ b/src/image/demos/zhCN/lazy.demo.vue @@ -0,0 +1,16 @@ + +# 懒加载 + + + diff --git a/src/image/src/Image.tsx b/src/image/src/Image.tsx index 8e41129b7cf..eb7927dbcbf 100644 --- a/src/image/src/Image.tsx +++ b/src/image/src/Image.tsx @@ -7,7 +7,9 @@ import { toRef, watchEffect, ImgHTMLAttributes, - onMounted + onMounted, + onBeforeUnmount, + watchPostEffect } from 'vue' import NImagePreview from './ImagePreview' import type { ImagePreviewInst } from './ImagePreview' @@ -15,6 +17,7 @@ import { imageGroupInjectionKey } from './ImageGroup' import type { ExtractPublicPropTypes } from '../../_utils' import { useConfig } from '../../_mixins' import { imagePreviewSharedProps } from './interface' +import { imgObserverHandler, imgUnobserverHandler } from './utils' export interface ImageInst { click: () => void @@ -24,6 +27,10 @@ const imageProps = { alt: String, height: [String, Number] as PropType, imgProps: Object as PropType, + lazy: Boolean, + lazyOptions: Object as PropType<{ + root: string + }>, objectFit: { type: String as PropType< 'fill' | 'contain' | 'cover' | 'none' | 'scale-down' @@ -77,6 +84,21 @@ export default defineComponent({ imageGroupHandle?.groupId || '' ) }) + + watchPostEffect(() => { + if (props.lazy) { + imgObserverHandler(imageRef.value, props.lazyOptions?.root) + } else { + imgUnobserverHandler(imageRef.value) + } + }) + + onBeforeUnmount(() => { + if (props.lazy) { + imgUnobserverHandler(imageRef.value) + } + }) + watchEffect(() => { void props.src void props.imgProps?.src @@ -112,7 +134,13 @@ export default defineComponent({ ref="imageRef" width={this.width || imgProps.width} height={this.height || imgProps.height} - src={this.showError ? this.fallbackSrc : this.src || imgProps.src} + src={ + this.showError + ? this.fallbackSrc + : this.lazy + ? undefined + : this.src || imgProps.src + } alt={this.alt || imgProps.alt} aria-label={this.alt || imgProps.alt} onClick={this.click} @@ -121,6 +149,7 @@ export default defineComponent({ style={[imgProps.style || '', { objectFit: this.objectFit }]} data-error={this.showError} data-preview-src={this.previewSrc || this.src} + data-src={this.src || imgProps.src} /> ) diff --git a/src/image/src/utils.ts b/src/image/src/utils.ts new file mode 100644 index 00000000000..de7c35c88b7 --- /dev/null +++ b/src/image/src/utils.ts @@ -0,0 +1,42 @@ +let imgObserver: IntersectionObserver | null = null + +let imgObserverOptions: { + root: HTMLElement | null +} | null + +const imgObserverCallback: IntersectionObserverCallback = (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + const img = entry.target as HTMLImageElement + if (!img.src) { + img.src = img.dataset.src || '' + } + } + }) +} + +export const imgObserverHandler: ( + el: HTMLImageElement | null, + root?: string +) => void = (el, root = 'body') => { + if (el === null) return + if (imgObserver === null) { + imgObserverOptions = { + root: document.querySelector(root) + } + imgObserver = new IntersectionObserver( + imgObserverCallback, + imgObserverOptions + ) + } + imgObserver.observe(el) +} + +export const imgUnobserverHandler: (el: HTMLImageElement | null) => void = ( + el +) => { + if (el === null) return + if (imgObserver) { + imgObserver.unobserve(el) + } +}