vercel / next.js

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

next/image blur placeholder with conditional rendering is taken down too early in Firefox #30128

Open yfhui opened 2 years ago

yfhui commented 2 years ago

What version of Next.js are you using?

11.1.2

What version of Node.js are you using?

16.9.1

What browser are you using?

Firefox

What operating system are you using?

Windows

How are you deploying your application?

Vercel

Describe the Bug

Observation:

conditional rendering example:

import React from 'react'
import Image from 'next/image';
export default function MyImage() {
    console.log('image is rendered');
    let loadTime = Date.now();

    return (
        <Image
            width={1395}
            height={947}
            blurDataURL={
                "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
            }
            placeholder="blur"
            src={"/mountains.jpg"}
            onLoadingComplete={() => {
                console.log("test", Date.now() - loadTime);
            }}
        />
    )
}
import MyImage from "../components/image";
import styles from "../styles/Home.module.css";
import { useState } from "react";

export default function Home() {
    const [mountImage, setMountImage] = useState(false);

    console.log('Home is rendered')
    return (
        <div className={styles.container}>
            <button
                onClick={() => {
                    setMountImage(true);
                }}
            >
                click to load image
            </button>
            {mountImage && (
                <MyImage/>
            )}
        </div>
    );
}

log in Firefox, we could see the first time to execute onLoad function is shorter than the image loading time: image log in Chrome, we could see the time to execute onLoad function is longer than the image loading time(and it only run once): image

Expected Behavior

onLoadingComplete/onload should be run once only. And placeholder exists until the image is fully loaded. (Chrome will execute onLoad once only and gives the expected behavior.)

To Reproduce

A demo is deployed to vercel. Click the button to update the conditional rendering variable. The base code is from npx create-next-app@latest. (complete repo)

https://test-nextjs-peach.vercel.app/

styfle commented 2 years ago

This might be a difference with the way Firefox resolved img.decode()

https://github.com/vercel/next.js/blob/c4e26ab248a9c505bf9aac425f4ff0821cbb641b/packages/next/client/image.tsx#L260-L268

Would you like to submit a PR to fix it?

yfhui commented 2 years ago

Thanks for suggesting the direction. after some investigation I think the reason for the early firing is because of the line if(!img.src.startsWith('data:'))

my discovery:

Edit: Noticed that !img.src.startsWith('data:') is changed to if (img.src !== emptyDataURL) in 11.1.3 but tested with 11.1.3-canary.100, the problem still exists

styfle commented 2 years ago

Theres a similar bug reported in #34697. It seems that Firefox implements img.decode() differently so there is a Firefox bug with some discussion here https://bugzilla.mozilla.org/show_bug.cgi?id=1758035

yfhui commented 2 years ago

That's what I suspected too, thx for you update!

bennettdams commented 2 years ago

I hope this is related, but there's also a problem of a blank image while the blur placeholder transitions to the real image.

I couldn't see the bug for the blurred placeholder in your repro, @yfhui, so here is another one that uses a remote image (from Imgur.com). You can see the bug in action:

blurred image -> blank -> final image

Animation

Deployed at: https://nextjs-image-bug.vercel.app/

Here is a reproduction repo, it just uses an image from Imgur.com and a static blur URL.

  1. npx create-next-app@latest --ts --use-npm .
  2. Add a remote image
  3. Open the browser and disable cache an throttle the network speed
  4. npm run build && npm run start

This is only an issue in Firefox - Chrome doesn't show this blank space between the placeholder and the final image.

yfhui commented 2 years ago

@bennettdams I noticed this issue too! but I am not sure if it's related to the Firefox bug mentioned above

bennettdams commented 2 years ago

@bennettdams I noticed this issue too! but I am not sure if it's related to the Firefox bug mentioned above

It doesn't happen in Chrome, that's why I posted it in this issue - but maybe it's worth to keep this as a separate issue, maybe someone from the team could let me know?

styfle commented 2 years ago

This should be fixed in PR #35889

styfle commented 2 years ago

One issue https://github.com/vercel/next.js/issues/30128#issuecomment-1086754374 is fixed can you can try it on canary with npm install next@canary.

The original issue https://github.com/vercel/next.js/issues/30128#issue-1032077189 still fails and seems to be related to be either decode() resolving immediately mentioned in https://github.com/vercel/next.js/issues/30128#issuecomment-1063249762 or it could be img.complete is true. It requires a deeper dive.

That being said, the blur placeholder is only acting up on Firefox when its conditionally rendered. Images that are rendered during page load seem to work fine with the blur placeholder.

Etsi0 commented 8 months ago

This is still happening on version 14.0.4

What i have installed on my project:

├── autoprefixer@10.4.16
├── eslint-config-next@14.0.4
├── eslint-config-prettier@9.1.0
├── eslint@8.56.0
├── next@14.0.4
├── postcss@8.4.32
├── react-dom@18.2.0
├── react@18.2.0
└── tailwindcss@3.4.0

Browser:

Firefox: 121.0 (64-bit)

Bug

https://github.com/vercel/next.js/assets/43541844/ca344549-94b8-4070-824f-80263ac4852a