vercel / next.js

The React Framework
https://nextjs.org
MIT License
126.71k stars 26.94k forks source link

Blur placeholder of `next/image` is still visible after the image is loaded #53329

Open mmazzarolo opened 1 year ago

mmazzarolo commented 1 year ago

Verify canary release

Provide environment information

Operating System:
      Platform: darwin
      Arch: arm64
      Version: Darwin Kernel Version 22.5.0: Thu Jun  8 22:22:20 PDT 2023; root:xnu-8796.121.3~7/RELEASE_ARM64_T6000
    Binaries:
      Node: 18.6.0
      npm: 8.13.2
      Yarn: 1.22.18
      pnpm: 8.6.7
    Relevant Packages:
      next: 13.4.13-canary.6
      eslint-config-next: 13.4.9
      react: 18.2.0
      react-dom: 18.2.0
      typescript: 5.1.6
    Next.js Config:
      output: N/A

Which area(s) of Next.js are affected? (leave empty if unsure)

Image optimization (next/image, next/legacy/image)

Link to the code that reproduces this issue or a replay of the bug

https://codesandbox.io/p/sandbox/pensive-mccarthy-yfz9lg

To Reproduce

Describe the Bug

The image/style generated by setting placeholder="blur" on a next/image can still be seen (for some time) after the image has been loaded.

Example from the attached Codesandbox:

ScreenShot 2023-07-29 at 11 12 30@2x

You can see other people reporting and discussing this bug at https://github.com/vercel/next.js/issues/42140#issuecomment-1342533513 .

For reference, here's how a Gatsby image with blur setup looks like out-of-the box:

https://github.com/vercel/next.js/assets/9536354/3484be15-066e-4ad0-9eef-4fd7c0c238a0

Expected Behavior

The placeholder image/style should immediately disappear when the image is loaded. Or, the generated image placeholder should respect the transparency pixels of the used image.

Which browser are you using? (if relevant)

Chromium Engine Version 115.0.5790.114, Safari Version 16.5.2 (18615.2.9.11.10)

How are you deploying your application? (if relevant)

No response

NEXT-1679

Jordaneisenburger commented 1 year ago

Yep, I'm experiencing the same issue when using placeholder="blur" and plaiceholder for for base64Data

focux commented 1 year ago

Same, I'm having this same issue. Have any of you found a work-around?

Jorundur commented 1 year ago

@focux A workaround is using the onLoad function on the Image component.

You can have a state variable to keep track of whether or not the image has loaded and only add the blur props when the image hasn't finished loading, i.e.

  const [imageLoaded, setImageLoaded] = React.useState<boolean>(false);

  return (
    <Image
      src={image.url}
      {...(blurDataURL && !imageLoaded && { placeholder: 'blur', blurDataURL })}
      onLoad={() => {
        setImageLoaded(true);
      }}
    />
  );
styfle commented 1 year ago

This bug only impacts next dev with App Router.

When I try with next build && next start, then the blur placeholder is removed as soon as the image is loaded.

The issue appears to be related to main-app.js taking 350ms to load which is blocking all client side js like react state (not just next/image).

ackvf commented 1 year ago

Nope, this happens when served from Vercel too and this doesn't work either, the onLoad will not fire until the image 100% finishes loading.

gif-placeholder-bug-fast

See here, it's as if the gif was still loading frame by frame (hence why the first animation is so slow), so technically the image hasn't finished loading. But it is already being displayed (albeit frame by frame) at which point I don't want to see the placeholder. Second animation goes on full speed and the placeholder is no longer visible.

These don't work either, only onload and onLoad is printed to the console.

<Image
  // onLoad={hidePlaceholder}
  // onLoadStart={hidePlaceholder}
  onLoad={() => console.debug('onLoad')}
  onLoadStart={() => console.debug('onLoadStart')}
  onLoadedData={() => console.debug('onLoadedData')}
  onLoadedMetadata={() => console.debug('onLoadedMetadata')}
  // ref={ref => ref && (ref.onload = hidePlaceholder)}
  ref={ref => {
    if (!ref) return
    ref.onload =  () => console.debug('onload')
    ref.onloadstart = () => console.debug('onloadstart')
    ref.onloadeddata = () => console.debug('onloadeddata')
    ref.onloadedmetadata = () => console.debug('onloadedmetadata')
  }}
/>
ackvf commented 1 year ago

Alright, this works like a charm! Just use it in place of Next/Image. It has the same signature.

Before:

          <Image
            src={getStaticAssetURL('core.gif')}
            placeholder={coreStaticDataURL}
          />

After:

          <LoadingImage
            src={getStaticAssetURL('core.gif')}
            placeholder={coreStaticDataURL}
          />

/* eslint-disable jsx-a11y/alt-text */

import Image from 'next/image'
import { useReducer } from 'react'

/**
 * This component is a drop-in replacement for next/image that shows a placeholder image while the image is loading.
 * 
 * *GitHub Issue: Placeholder of next/image is still visible after the image is loaded*\
 * https://github.com/vercel/next.js/issues/53329
 */

//@ts-ignore
export const LoadingImage: React.FC<LoadingImageProps> = ({ placeholder, ...props }) => {
  //@ts-ignore
  const [isLoading, stopLoading] = useReducer((_, ev) => (props.onLoad?.(ev), false), true)

  // prettier-ignore
  return (
    <>
      {isLoading && placeholder &&
        <Image
          {...props}
          placeholder={undefined}
          src={placeholder}
        />
      }
      <Image
        {...props}
        className={`${props.className} ${isLoading && 'absolute invisible'}`}
        placeholder={undefined}
        onLoad={stopLoading}
      />
    </>
  )
}

export type NextImageProps = Parameters<typeof Image>[0]

export interface LoadingImageProps extends NextImageProps {
  placeholder: NextImageProps['placeholder']
}

You'll need to define also css (uses tailwind)

.invisible {
  visibility: hidden;
}
.absolute{
  position: absolute;
}
artemshchirov commented 9 months ago

@ackvf Thank you!

thanhtutzaw commented 9 months ago

I also get this issue in nextjs 14 and blur data did not have border radius from original photo .

sehrish30 commented 7 months ago

the issue also exists in onLoad in iPhone it gets triggered too fast before image is 100% loaded, and my loading image does not appear

works fine in web and android