fix(next/image): empty blur image when animated (#54028)

Partial fix for #54012: do not generate a blur image in the image loader when the image is detected to be animated, rather than returning the *entire* animated image as the blur image.
This commit is contained in:
Matt Cowley 2023-08-15 03:17:40 +01:00 committed by GitHub
parent c5fb04663a
commit c1fa78bf6c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 47 additions and 12 deletions

View file

@ -243,7 +243,7 @@ placeholder = 'empty' // "empty" | "blur" | "data:image/..."
A placeholder to use while the image is loading. Possible values are `blur`, `empty`, or `data:image/...`. Defaults to `empty`.
When `blur`, the [`blurDataURL`](#blurdataurl) property will be used as the placeholder. If `src` is an object from a [static import](/docs/app/building-your-application/optimizing/images#local-images) and the imported image is `.jpg`, `.png`, `.webp`, or `.avif`, then `blurDataURL` will be automatically populated.
When `blur`, the [`blurDataURL`](#blurdataurl) property will be used as the placeholder. If `src` is an object from a [static import](/docs/app/building-your-application/optimizing/images#local-images) and the imported image is `.jpg`, `.png`, `.webp`, or `.avif`, then `blurDataURL` will be automatically populated, except when the image is detected to be animated.
For dynamic images, you must provide the [`blurDataURL`](#blurdataurl) property. Solutions such as [Plaiceholder](https://github.com/joe-bell/plaiceholder) can help with `base64` generation.

View file

@ -8,10 +8,10 @@ You are attempting use the `next/image` component with `placeholder=blur` proper
The `blurDataURL` might be missing because you're using a string for `src` instead of a static import.
Or `blurDataURL` might be missing because the static import is an unsupported image format. Only jpg, png, webp, and avif are supported at this time.
Or `blurDataURL` might be missing because the static import is an unsupported image format. Only jpg, png, webp, and avif are supported at this time, though animated images are not supported.
## Possible Ways to Fix It
- Add a [`blurDataURL`](/docs/pages/api-reference/components/image#blurdataurl) property, the contents should be a small Data URL to represent the image
- Change the [`src`](/docs/pages/api-reference/components/image#src) property to a static import with one of the supported file types: jpg, png, or webp
- Change the [`src`](/docs/pages/api-reference/components/image#src) property to a static import with one of the supported file types: jpg, png, webp, or avif (animated images not supported)
- Remove the [`placeholder`](/docs/pages/api-reference/components/image#placeholder) property, effectively no blur effect

View file

@ -37,7 +37,7 @@ export async function getBlurImage(
let blurWidth: number = 0
let blurHeight: number = 0
if (VALID_BLUR_EXT.includes(extension)) {
if (VALID_BLUR_EXT.includes(extension) && !isAnimated(content)) {
// Shrink the image's largest dimension
if (imageSize.width >= imageSize.height) {
blurWidth = BLUR_IMG_SIZE
@ -65,18 +65,15 @@ export async function getBlurImage(
blurDataURL = url.href.slice(prefix.length)
} else {
const resizeImageSpan = tracing('image-resize')
const resizedImage = await resizeImageSpan.traceAsyncFn(() => {
if (isAnimated(content)) {
return content
}
return optimizeImage({
const resizedImage = await resizeImageSpan.traceAsyncFn(() =>
optimizeImage({
buffer: content,
width: blurWidth,
height: blurHeight,
contentType: `image/${extension}`,
quality: BLUR_QUALITY,
})
})
)
const blurDataURLSpan = tracing('image-base64-tostring')
blurDataURL = blurDataURLSpan.traceFn(
() =>

View file

@ -807,7 +807,7 @@ export default function Image({
- Add a "blurDataURL" property, the contents should be a small Data URL to represent the image
- Change the "src" property to a static import with one of the supported file types: ${VALID_BLUR_EXT.join(
','
)}
)} (animated images not supported)
- Remove the "placeholder" property, effectively no blur effect
Read more: https://nextjs.org/docs/messages/placeholder-blur-data-url`
)

View file

@ -493,7 +493,7 @@ export function getImgProps(
- Add a "blurDataURL" property, the contents should be a small Data URL to represent the image
- Change the "src" property to a static import with one of the supported file types: ${VALID_BLUR_EXT.join(
','
)}
)} (animated images not supported)
- Remove the "placeholder" property, effectively no blur effect
Read more: https://nextjs.org/docs/messages/placeholder-blur-data-url`
)

View file

@ -0,0 +1,38 @@
/* eslint-env jest */
import { getBlurImage } from 'next/dist/build/webpack/loaders/next-image-loader/blur'
import { readFile } from 'fs-extra'
import { join } from 'path'
const getImage = (filepath) => readFile(join(__dirname, filepath))
const tracing = () => ({
traceFn: (fn, ...args) => fn(...args),
traceAsyncFn: (fn, ...args) => fn(...args),
})
const context = { basePath: '', outputPath: '', isDev: false, tracing }
describe('getBlurImage', () => {
it('should return image for jpg', async () => {
const buffer = await getImage('./images/test.jpg')
const result = await getBlurImage(
buffer,
'jpeg',
{ width: 400, height: 400 },
context
)
expect(result).toBeObject()
expect(result.dataURL).toBeString()
})
it('should return undefined for animated webp', async () => {
const buffer = await getImage('./images/animated.webp')
const result = await getBlurImage(
buffer,
'webp',
{ width: 400, height: 400 },
context
)
expect(result).toBeObject()
expect(result.dataURL).toBeUndefined()
})
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB