Skip to content

Commit

Permalink
refactor(image): lazy load
Browse files Browse the repository at this point in the history
  • Loading branch information
07akioni committed Jun 19, 2022
1 parent 60a2d7a commit 226a8d9
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 55 deletions.
4 changes: 2 additions & 2 deletions src/image/demos/enUS/lazy.demo.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<markdown>
# lazyLoad
# Lazy load
</markdown>

<template>
<n-image
v-for="(item, index) in Array(50)"
v-for="(item, index) in Array(10)"
:key="index"
width="100"
src="https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg"
Expand Down
2 changes: 1 addition & 1 deletion src/image/demos/zhCN/lazy.demo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<template>
<n-image
v-for="(item, index) in Array(50)"
v-for="(item, index) in Array(10)"
:key="index"
width="100"
src="https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg"
Expand Down
38 changes: 20 additions & 18 deletions src/image/src/Image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ import {
watchEffect,
ImgHTMLAttributes,
onMounted,
onBeforeUnmount,
watchPostEffect
onBeforeUnmount
} from 'vue'
import NImagePreview from './ImagePreview'
import type { ImagePreviewInst } from './ImagePreview'
import { imageGroupInjectionKey } from './ImageGroup'
import type { ExtractPublicPropTypes } from '../../_utils'
import { useConfig } from '../../_mixins'
import { imagePreviewSharedProps } from './interface'
import { imgObserverHandler, imgUnobserverHandler } from './utils'
import { observeIntersection } from './utils'
import type { IntersectionObserverOptions } from './utils'

export interface ImageInst {
click: () => void
Expand All @@ -28,9 +28,7 @@ const imageProps = {
height: [String, Number] as PropType<string | number>,
imgProps: Object as PropType<ImgHTMLAttributes>,
lazy: Boolean,
lazyOptions: Object as PropType<{
root: string
}>,
intersectionObserverOptions: Object as PropType<IntersectionObserverOptions>,
objectFit: {
type: String as PropType<
'fill' | 'contain' | 'cover' | 'none' | 'scale-down'
Expand Down Expand Up @@ -85,18 +83,22 @@ export default defineComponent({
)
})

watchPostEffect(() => {
if (props.lazy) {
imgObserverHandler(imageRef.value, props.lazyOptions?.root)
} else {
imgUnobserverHandler(imageRef.value)
}
})

onBeforeUnmount(() => {
if (props.lazy) {
imgUnobserverHandler(imageRef.value)
}
onMounted(() => {
let unobserve: (() => void) | undefined
const stopWatchHandle = watchEffect(() => {
unobserve?.()
unobserve = undefined
if (props.lazy) {
unobserve = observeIntersection(
imageRef.value,
props.intersectionObserverOptions
)
}
})
onBeforeUnmount(() => {
stopWatchHandle()
unobserve?.()
})
})

watchEffect(() => {
Expand Down
111 changes: 77 additions & 34 deletions src/image/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,85 @@
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 type IntersectionObserverOptions = Omit<
IntersectionObserverInit,
'root'
> & {
root?: Element | Document | null | string
}

export const imgObserverHandler: (
el: HTMLImageElement | null,
root?: string
) => void = (el, root = 'body') => {
if (el === null) return
if (imgObserver === null) {
imgObserverOptions = {
root: document.querySelector(root)
export const resolveOptionsAndHash = (
options: IntersectionObserverOptions | undefined = {}
): {
hash: string
options: Omit<IntersectionObserverInit, 'root'> & { root: Element | Document }
} => {
const { root = null } = options
return {
hash: `${options.rootMargin || '0px 0px 0px 0px'}-${
Array.isArray(options.threshold)
? options.threshold.join(',')
: options.threshold ?? '0'
}`,
options: {
...options,
root:
(typeof root === 'string' ? document.querySelector(root) : root) ||
document.documentElement
}
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)
const observers = new WeakMap<
Document | Element,
Map<string, [IntersectionObserver, number]>
>()

export const observeIntersection: (
el: HTMLImageElement | null,
options: IntersectionObserverOptions | undefined
) => () => void = (el, options) => {
if (!el) return () => {}
const resolvedOptionsAndHash = resolveOptionsAndHash(options)
let rootObservers: Map<string, [IntersectionObserver, number]>
const _rootObservers = observers.get(resolvedOptionsAndHash.options.root)
if (_rootObservers) {
rootObservers = _rootObservers
} else {
rootObservers = new Map()
observers.set(resolvedOptionsAndHash.options.root, rootObservers)
}
let observer: IntersectionObserver
if (rootObservers.has(resolvedOptionsAndHash.hash)) {
const observerAndObservedElementCount =
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
rootObservers.get(resolvedOptionsAndHash.hash)!
observer = observerAndObservedElementCount[0]
observerAndObservedElementCount[1]++
} else {
observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {

This comment has been minimized.

Copy link
@07akioni

07akioni Jun 19, 2022

Author Collaborator

另外就是这里只要发现了一次就不需要再 observe 了,也许要处理

const img = entry.target as HTMLImageElement
if (!img.src) {
img.src = img.dataset.src || ''
}
}
})
})
rootObservers.set(resolvedOptionsAndHash.hash, [observer, 1])
}
observer.observe(el)
return () => {
const observerAndObservedElementCount = rootObservers.get(
resolvedOptionsAndHash.hash
)
if (observerAndObservedElementCount) {
observerAndObservedElementCount[0].unobserve(el)
observerAndObservedElementCount[1]--
if (observerAndObservedElementCount[1] <= 0) {
rootObservers.delete(resolvedOptionsAndHash.hash)
}
if (!rootObservers.size) {
observers.delete(resolvedOptionsAndHash.options.root)
}
}
}
}
2 changes: 2 additions & 0 deletions src/tree/demos/zhCN/basic.demo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
block-line
:data="data"
:default-expanded-keys="defaultExpandedKeys"
checkable
expand-on-click
selectable
/>
</template>
Expand Down

0 comments on commit 226a8d9

Please sign in to comment.