Skip to content

Commit 4e03c62

Browse files
feat(nuxt): lazyLoad prop for data saving mode etc.
1 parent 566faa1 commit 4e03c62

File tree

6 files changed

+155
-113
lines changed

6 files changed

+155
-113
lines changed

packages/core/src/lazyLoad.ts

Lines changed: 64 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ export function lazyLoad<T extends HTMLImageElement>(
1818
hash = true,
1919
hashType = 'blurhash',
2020
placeholderSize = DEFAULT_PLACEHOLDER_SIZE,
21-
immediate = false,
2221
onImageLoad,
2322
}: UnLazyLoadOptions = {},
2423
) {
@@ -28,27 +27,16 @@ export function lazyLoad<T extends HTMLImageElement>(
2827
// Calculate the image's `sizes` attribute if `data-sizes="auto"` is set
2928
updateSizesAttribute(image)
3029

31-
// Load the image right away if `immediate` is set to `true`
32-
if (immediate) {
33-
updatePictureSources(image)
34-
updateImageSrcset(image)
35-
updateImageSrc(image)
36-
continue
37-
}
38-
3930
// Generate the blurry placeholder from a Blurhash or ThumbHash string if applicable
4031
if (__ENABLE_HASH_DECODING__ && hash) {
41-
const { blurhash, thumbhash } = image.dataset
42-
const hasOptsHash = typeof hash === 'string'
43-
const _hash = hasOptsHash ? hash : (thumbhash || blurhash)
44-
45-
if (_hash) {
46-
applyPlaceholder(image, {
47-
hash: _hash,
48-
type: hasOptsHash ? hashType : thumbhash ? 'thumbhash' : 'blurhash',
49-
size: placeholderSize,
50-
})
51-
}
32+
const placeholoder = createPlaceholderFromHash({
33+
image,
34+
hash: typeof hash === 'string' ? hash : undefined,
35+
hashType,
36+
placeholderSize,
37+
})
38+
if (placeholoder)
39+
image.src = placeholoder
5240
}
5341

5442
// Bail if the image doesn't provide a `data-src` or `data-srcset` attribute
@@ -101,9 +89,13 @@ export function loadImage(
10189
onImageLoad?: (image: HTMLImageElement) => void,
10290
) {
10391
const imageLoader = new Image()
104-
imageLoader.srcset = image.dataset.srcset!
105-
imageLoader.src = image.dataset.src!
106-
imageLoader.sizes = image.sizes
92+
const { srcset, src, sizes } = image.dataset
93+
if (srcset)
94+
imageLoader.srcset = srcset
95+
if (src)
96+
imageLoader.src = src
97+
if (sizes)
98+
imageLoader.sizes = sizes
10799

108100
imageLoader.addEventListener('load', () => {
109101
updatePictureSources(image)
@@ -113,6 +105,55 @@ export function loadImage(
113105
})
114106
}
115107

108+
export function createPlaceholderFromHash(
109+
{
110+
/** If given, the hash will be extracted from the image's `data-blurhash` or `data-thumbhash` attribute and ratio will be calculated from the image's actual dimensions */
111+
image,
112+
hash,
113+
hashType = 'blurhash',
114+
/** @default 32 */
115+
placeholderSize = DEFAULT_PLACEHOLDER_SIZE,
116+
/** Will be calculated from the image's actual dimensions if not provided */
117+
placeholderRatio,
118+
}: {
119+
image?: HTMLImageElement
120+
hash?: string
121+
hashType?: 'blurhash' | 'thumbhash'
122+
placeholderSize?: number
123+
placeholderRatio?: number
124+
} = {},
125+
) {
126+
if (!hash && image) {
127+
const { blurhash, thumbhash } = image.dataset
128+
hash = thumbhash || blurhash
129+
hashType = thumbhash ? 'thumbhash' : 'blurhash'
130+
}
131+
132+
if (!hash)
133+
return
134+
135+
try {
136+
if (hashType === 'thumbhash') {
137+
return createPngDataUriFromThumbHash(hash)
138+
}
139+
else {
140+
// Preserve the original image's aspect ratio
141+
if (!placeholderRatio && image) {
142+
const actualWidth = image.width || image.offsetWidth || placeholderSize
143+
const actualHeight = image.height || image.offsetHeight || placeholderSize
144+
placeholderRatio = actualWidth / actualHeight
145+
}
146+
return createPngDataUriFromBlurHash(hash, {
147+
ratio: placeholderRatio,
148+
size: placeholderSize,
149+
})
150+
}
151+
}
152+
catch (error) {
153+
console.error(`Error generating ${hashType} placeholder:`, error)
154+
}
155+
}
156+
116157
function updateSizesAttribute(element: HTMLImageElement | HTMLSourceElement) {
117158
const { sizes } = element.dataset
118159
if (sizes !== 'auto')
@@ -148,33 +189,3 @@ function updatePictureSources(image: HTMLImageElement) {
148189
[...picture.querySelectorAll<HTMLSourceElement>('source[data-src]')].forEach(updateImageSrc)
149190
}
150191
}
151-
152-
function applyPlaceholder(
153-
image: HTMLImageElement,
154-
{ hash, type, size }: {
155-
hash: string
156-
type: 'blurhash' | 'thumbhash'
157-
size: number
158-
},
159-
) {
160-
try {
161-
// Generate the blurry placeholder for either a ThumbHash or a BlurHash string
162-
let placeholder: string
163-
164-
if (type === 'thumbhash') {
165-
placeholder = createPngDataUriFromThumbHash(hash)
166-
}
167-
else {
168-
// Preserve the original image's aspect ratio
169-
const actualWidth = image.width || image.offsetWidth || size
170-
const actualHeight = image.height || image.offsetHeight || size
171-
const ratio = actualWidth / actualHeight
172-
placeholder = createPngDataUriFromBlurHash(hash, { ratio, size })
173-
}
174-
175-
image.src = placeholder
176-
}
177-
catch (error) {
178-
console.error(`Error generating ${type} placeholder:`, error)
179-
}
180-
}

packages/nuxt/playground/app.vue

Lines changed: 78 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
<script setup lang="ts">
22
import { PlaygroundDivider, UnLazyImage } from '#components'
3+
import { ref } from '#imports'
34
import '@unocss/reset/tailwind.css'
45
56
const blurhash = 'LKO2:N%2Tw=w]~RBVZRi};RPxuwH'
67
const thumbhash = '1QcSHQRnh493V4dIh4eXh1h4kJUI'
78
const logoUrl = new URL('../../../docs/public/logo.svg', import.meta.url).href
9+
10+
const shouldLoadImage = ref(false)
11+
12+
function loadImage() {
13+
console.log('load image')
14+
shouldLoadImage.value = true
15+
}
816
</script>
917

1018
<template>
@@ -13,59 +21,81 @@ const logoUrl = new URL('../../../docs/public/logo.svg', import.meta.url).href
1321
<Link rel="icon" :href="logoUrl" type="image/svg+xml" />
1422
</Head>
1523

16-
<main class="mx-auto max-w-xl px-4 py-12 sm:px-6 lg:px-8">
17-
<div class="space-y-6">
18-
<div class="space-y-2">
19-
<PlaygroundDivider>SSR-decoded BlurHash as <strong>PNG</strong> data URI</PlaygroundDivider>
20-
<p class="text-sm text-gray-500">
21-
The image below is inlined as a PNG data URI.
22-
</p>
23-
<UnLazyImage
24-
:blurhash="blurhash"
25-
:blurhash-ratio="2"
26-
data-srcset="image-320w.jpg 320w, image-640w.jpg 640w"
27-
width="640"
28-
height="320"
29-
/>
24+
<main class="mx-auto max-w-prose px-4 py-12 sm:px-6 lg:px-8">
25+
<div class="space-y-12">
26+
<div class="grid grid-cols-2 gap-6">
27+
<div class="space-y-2">
28+
<PlaygroundDivider><strong>SSR</strong>-decoded BlurHash</PlaygroundDivider>
29+
<UnLazyImage
30+
:blurhash="blurhash"
31+
:blurhash-ratio="2"
32+
data-srcset="image-320w.jpg 320w, image-640w.jpg 640w"
33+
width="640"
34+
height="320"
35+
/>
36+
<p class="text-sm text-gray-500">
37+
The image above is inlined as a PNG data URI.
38+
</p>
39+
</div>
40+
<div class="space-y-2">
41+
<PlaygroundDivider><strong>Client-side</strong> decoded BlurHash</PlaygroundDivider>
42+
<UnLazyImage
43+
:ssr="false"
44+
:blurhash="blurhash"
45+
:blurhash-ratio="2"
46+
data-srcset="image-320w.jpg 320w, image-640w.jpg 640w"
47+
width="640"
48+
height="320"
49+
/>
50+
<p class="text-sm text-gray-500">
51+
The client-side decoded BlurHash will infer the image dimensions from the <code>width</code> and <code>height</code> attributes.
52+
</p>
53+
</div>
3054
</div>
3155

32-
<div class="space-y-2">
33-
<PlaygroundDivider>Client-side decoded BlurHash</PlaygroundDivider>
34-
<p class="text-sm text-gray-500">
35-
The client-side decoded BlurHash will infer the image dimensions from the <code>width</code> and <code>height</code> attributes.
36-
</p>
37-
<UnLazyImage
38-
:ssr="false"
39-
:blurhash="blurhash"
40-
:blurhash-ratio="2"
41-
data-srcset="image-320w.jpg 320w, image-640w.jpg 640w"
42-
width="640"
43-
height="320"
44-
/>
45-
</div>
56+
<div class="grid grid-cols-2 gap-6">
57+
<div class="space-y-2">
58+
<PlaygroundDivider><strong>SSR</strong>-decoded ThumbHash</PlaygroundDivider>
59+
<UnLazyImage
60+
:thumbhash="thumbhash"
61+
data-src="/images/sunrise-evan-wallace.jpg"
62+
width="480"
63+
height="640"
64+
style="aspect-ratio: 3/4;"
65+
/>
66+
<p class="text-sm text-gray-500">
67+
The image above is inlined as a PNG data URI.
68+
</p>
69+
</div>
4670

47-
<div class="space-y-2">
48-
<PlaygroundDivider>SSR-decoded ThumbHash as <strong>PNG</strong> data URI</PlaygroundDivider>
49-
<p class="text-sm text-gray-500">
50-
The image below is inlined as a PNG data URI.
51-
</p>
52-
<UnLazyImage
53-
:thumbhash="thumbhash"
54-
data-srcset="image-320w.jpg 320w, image-640w.jpg 640w"
55-
width="480"
56-
height="640"
57-
/>
71+
<div class="space-y-2">
72+
<PlaygroundDivider><strong>Client-side</strong> decoded ThumbHash</PlaygroundDivider>
73+
<UnLazyImage
74+
:ssr="false"
75+
:thumbhash="thumbhash"
76+
data-src="/images/sunrise-evan-wallace.jpg"
77+
width="480"
78+
height="640"
79+
style="aspect-ratio: 3/4;"
80+
/>
81+
</div>
5882
</div>
5983

60-
<div class="space-y-2">
61-
<PlaygroundDivider>Client-side decoded ThumbHash</PlaygroundDivider>
62-
<UnLazyImage
63-
:ssr="false"
64-
:thumbhash="thumbhash"
65-
data-srcset="image-320w.jpg 320w, image-640w.jpg 640w"
66-
width="480"
67-
height="640"
68-
/>
84+
<div class="grid grid-cols-2 gap-6">
85+
<div class="space-y-2">
86+
<PlaygroundDivider>Lazy load on click</PlaygroundDivider>
87+
<UnLazyImage
88+
thumbhash="HBkSHYSIeHiPiHh8eJd4eTN0EEQG"
89+
:lazy-load="shouldLoadImage"
90+
data-src="/images/fall-evan-wallace.jpg"
91+
width="480"
92+
height="640"
93+
@click="loadImage"
94+
/>
95+
<p class="text-sm text-gray-500">
96+
Lazy loading will only be triggered when the image is clicked.
97+
</p>
98+
</div>
6999
</div>
70100
</div>
71101
</main>
4.7 KB
Loading
2.48 KB
Loading

packages/nuxt/src/runtime/components/UnLazyImage.vue

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ const props = withDefaults(
2222
/** Aspect ratio (width / height) of the decoded BlurHash image. Only applies to SSR-decoded placeholder images from a BlurHash string. */
2323
placeholderRatio?: number
2424
/**
25-
* A flag to indicate whether the image should be loaded immediately.
26-
* @default false
25+
* A flag to indicate whether the image should be lazy-loaded (default) or deferred until this prop is set to `true`. Note: Placeholder images from hashes will still be decoded.
26+
* @default true
2727
*/
28-
immediate?: boolean
28+
lazyLoad?: boolean
2929
/** Whether the ThumbHash or BlurHash should be decoded on the server. Overrides the global module configuration if set. */
3030
ssr?: boolean
3131
}>(),
@@ -36,20 +36,19 @@ const props = withDefaults(
3636
thumbhash: undefined,
3737
placeholderSize: undefined,
3838
placeholderRatio: undefined,
39-
immediate: false,
39+
lazyLoad: true,
4040
ssr: undefined,
4141
},
4242
)
4343
4444
const { unlazy } = useRuntimeConfig().public
4545
const target = ref<HTMLImageElement | undefined>()
4646
let cleanup: () => void | undefined
47+
// const now = performance.now()
4748
4849
// SSR-decoded BlurHash as PNG data URI placeholder image
49-
const isSSR = process.server && (props.ssr ?? unlazy.ssr)
50-
51-
// const now = performance.now()
52-
const pngPlaceholder = (isSSR && (props.thumbhash || props.blurhash))
50+
const loadSSR = process.server && (props.ssr ?? unlazy.ssr)
51+
const pngPlaceholder = (loadSSR && (props.thumbhash || props.blurhash))
5352
? props.blurhash
5453
? createPngDataUriFromBlurHash(props.blurhash, {
5554
size: props.placeholderSize || unlazy.placeholderSize,
@@ -58,21 +57,23 @@ const pngPlaceholder = (isSSR && (props.thumbhash || props.blurhash))
5857
: createPngDataUriFromThumbHash(props.thumbhash!)
5958
: undefined
6059
61-
// if (isSSR && process.dev)
60+
// if (loadSSR && process.dev)
6261
// console.log(`[unlazy] BlurHash decoded in ${performance.now() - now}ms`)
6362
6463
onMounted(() => {
64+
if (!target.value)
65+
return
66+
6567
watchEffect(() => {
6668
cleanup?.()
6769
68-
if (!target.value)
70+
if (!props.lazyLoad)
6971
return
7072
7173
cleanup = lazyLoad(target.value, {
7274
hash: props.thumbhash || props.blurhash,
7375
hashType: props.thumbhash ? 'thumbhash' : 'blurhash',
7476
placeholderSize: props.placeholderSize || unlazy.placeholderSize,
75-
immediate: props.immediate,
7677
})
7778
})
7879
})

packages/unlazy/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { autoSizes, isCrawler, isLazyLoadingSupported, lazyLoad, loadImage } from '@unlazy/core'
1+
export { autoSizes, createPlaceholderFromHash, isCrawler, isLazyLoadingSupported, lazyLoad, loadImage } from '@unlazy/core'
22
export type { UnLazyLoadOptions } from '@unlazy/core'

0 commit comments

Comments
 (0)