Skip to content

Commit 264e5ef

Browse files
feat: update sizes attribute on image resize with new value (#15)
* Add automatic recalculation of "sizes" attribute when element-width changes on resize * Add debounce effect to resize call * refactor: update the debounce function according to johannschopplich suggestion * refactor: refactor resizeElementStore from array to set Co-authored-by: Johann Schopplich <mail@johannschopplich.com> * fix: add resize observer to clean-up functions, to avoid dangling observer instances * fix: only register resize observer on <img> elements, since <source> tags have no dimensions and cant be resized * feat: add global `updateSizesOnResize` option --------- Co-authored-by: Johann Schopplich <mail@johannschopplich.com>
1 parent 52899df commit 264e5ef

File tree

4 files changed

+65
-6
lines changed

4 files changed

+65
-6
lines changed

docs/api/lazy-load.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ interface UnLazyLoadOptions {
6262
*/
6363
placeholderSize?: number
6464

65+
/**
66+
* Whether to update the `sizes` attribute on resize events with the current image width.
67+
*
68+
* @default false
69+
*/
70+
updateSizesOnResize?: boolean
71+
6572
/**
6673
* A callback function to run when an image is loaded.
6774
*/

packages/core/src/lazyLoad.ts

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DEFAULT_PLACEHOLDER_SIZE } from './constants'
2-
import { isCrawler, isLazyLoadingSupported, toElementArray } from './utils'
2+
import { debounce, isCrawler, isLazyLoadingSupported, toElementArray } from './utils'
33
import { createPngDataUri as createPngDataUriFromThumbHash } from './thumbhash'
44
import { createPngDataUri as createPngDataUriFromBlurHash } from './blurhash'
55
import type { UnLazyLoadOptions } from './types'
@@ -18,18 +18,24 @@ export function lazyLoad<T extends HTMLImageElement>(
1818
hash = true,
1919
hashType = 'blurhash',
2020
placeholderSize = DEFAULT_PLACEHOLDER_SIZE,
21+
updateSizesOnResize = false,
2122
onImageLoad,
2223
}: UnLazyLoadOptions = {},
2324
) {
2425
const cleanupFns = new Set<() => void>()
2526

2627
for (const image of toElementArray<T>(selectorsOrElements)) {
2728
// Calculate the image's `sizes` attribute if `data-sizes="auto"` is set
28-
updateSizesAttribute(image)
29+
cleanupFns.add(
30+
updateSizesAttribute(image, updateSizesOnResize),
31+
)
2932

3033
// Calculate the `sizes` attribute for sources inside a `<picture>` element
31-
if (image.parentElement?.tagName.toLowerCase() === 'picture')
32-
[...image.parentElement.getElementsByTagName('source')].forEach(updateSizesAttribute)
34+
if (image.parentElement?.tagName.toLowerCase() === 'picture') {
35+
[...image.parentElement.getElementsByTagName('source')].forEach(
36+
sourceTag => updateSizesAttribute(sourceTag),
37+
)
38+
}
3339

3440
// Generate the blurry placeholder from a Blurhash or ThumbHash string if applicable
3541
if (__ENABLE_HASH_DECODING__ && hash) {
@@ -157,17 +163,39 @@ export function createPlaceholderFromHash(
157163
}
158164
}
159165

160-
function updateSizesAttribute(element: HTMLImageElement | HTMLSourceElement) {
166+
// Keep track of elements that have a `data-sizes="auto"` attribute
167+
// and need to be updated when their size changes
168+
const resizeElementStore = new WeakMap<HTMLImageElement | HTMLSourceElement, ResizeObserver>()
169+
170+
function updateSizesAttribute(element: HTMLImageElement | HTMLSourceElement, shouldUpdateOnResize = false) {
171+
const removeResizeObserver = (): void => {
172+
const observerInstance = resizeElementStore.get(element)
173+
if (!observerInstance)
174+
return
175+
176+
observerInstance.disconnect()
177+
resizeElementStore.delete(element)
178+
}
179+
161180
const { sizes } = element.dataset
162181
if (sizes !== 'auto')
163-
return
182+
return removeResizeObserver
164183

165184
const width = element instanceof HTMLSourceElement
166185
? element.parentElement?.getElementsByTagName('img')[0]?.offsetWidth
167186
: element.offsetWidth
168187

169188
if (width)
170189
element.sizes = `${width}px`
190+
191+
if (shouldUpdateOnResize && !resizeElementStore.has(element)) {
192+
const debounceResize = debounce(() => updateSizesAttribute(element), 500)
193+
const observerInstance = new ResizeObserver(debounceResize)
194+
resizeElementStore.set(element, observerInstance)
195+
observerInstance.observe(element)
196+
}
197+
198+
return removeResizeObserver
171199
}
172200

173201
function updateImageSrc(image: HTMLImageElement | HTMLSourceElement) {

packages/core/src/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ export interface UnLazyLoadOptions {
3535
*/
3636
placeholderSize?: number
3737

38+
/**
39+
* Whether to update the `sizes` attribute on resize events with the current image width.
40+
*
41+
* @default false
42+
*/
43+
updateSizesOnResize?: boolean
44+
3845
/**
3946
* A callback function to run when an image is loaded.
4047
*/

packages/core/src/utils/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,20 @@ export function base64ToBytes(value: string) {
4141

4242
return new Uint8Array(decodedData)
4343
}
44+
45+
export function debounce<T extends (...args: any[]) => void>(
46+
fn: T,
47+
delay: number,
48+
) {
49+
let timeout: ReturnType<typeof setTimeout> | undefined
50+
51+
return function (...args: Parameters<T>) {
52+
if (timeout)
53+
clearTimeout(timeout)
54+
55+
timeout = setTimeout(() => {
56+
timeout = undefined
57+
fn(...args)
58+
}, delay)
59+
}
60+
}

0 commit comments

Comments
 (0)