Skip to content

Update Node.js examples to use AVIF #37

Open

Description

@lilouartz

Hey!

Thank you for the wonderful library.

I just posted a blog post that shows how using AVIF with thumbhash produces 50% smaller images.

https://pillser.com/engineering/2024-06-20-optimizing-image-loading-with-avif-placeholders-for-enhanced-performance

Perhaps it should be the default?

Activity

  1. Green-Sky commented on Jun 20, 2024

    @Green-Sky

    The idea is to transmit the hash, not the image of the decoded hash. Assuming you are in fact generating them on the server.

  2. lilouartz commented on Jun 20, 2024

    @lilouartz
    Author

    @Green-Sky Could you elaborate? I am not confident that I am following

  3. Green-Sky commented on Jun 20, 2024

    @Green-Sky

    It reads like you are rendering the thumbhash to png/avif on the server side and then send it as part of the html to the client.

    What you really want to do, if you are not doing it, is send the thumbhash to the client and then run some js that renders the image on the client.

    also this issue might be of interest to you: #33

  4. lilouartz commented on Jun 20, 2024

    @lilouartz
    Author

    oh that was not obvious at all. hah!

    Well, I need to update the article it seems.

  5. lilouartz commented on Jun 20, 2024

    @lilouartz
    Author

    so, on the client side I would do something like this?

    const base64ToUint8Array = (base64: string) => {
      const binaryString = atob(base64);
      const len = binaryString.length;
      const bytes = new Uint8Array(len);
      for (let i = 0; i < len; i++) {
        bytes[i] = binaryString.charCodeAt(i);
      }
    
      return bytes;
    };
    
    const dataUrl = thumbHashToDataURL(
      base64ToUint8Array('a/cNG4L1NUOKhahX+XncHfg='),
    );
  6. lilouartz commented on Jun 20, 2024

    @lilouartz
    Author

    This is what I have so far...

    const base64ToUint8Array = (base64: string): Uint8Array => {
      const binaryString = atob(base64);
      const len = binaryString.length;
      const bytes = new Uint8Array(len);
      for (let i = 0; i < len; i++) {
        // eslint-disable-next-line unicorn/prefer-code-point
        bytes[i] = binaryString.charCodeAt(i);
      }
    
      return bytes;
    };
    
    const dataURLtoBlob = (dataURI: string): Blog => {
      const mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
      const binary = atob(dataURI.split(',')[1]);
      const array = [];
    
      for (let i = 0; i < binary.length; i++) {
        // eslint-disable-next-line unicorn/prefer-code-point
        array.push(binary.charCodeAt(i));
      }
    
      return new Blob([new Uint8Array(array)], { type: mime });
    };
    
    const SupplementImage = ({
      image,
      loadingStrategy,
    }: {
      readonly image: Image;
      readonly loadingStrategy: LoadingStrategy;
    }) => {
      const [dataUrl, setDataUrl] = useState<string | null>(null);
    
      useEffect(() => {
        requestIdleCallback(() => {
          setDataUrl(
            URL.createObjectURL(
              dataURLtoBlob(
                thumbHashToDataURL(base64ToUint8Array('a/cNG4L1NUOKhahX+XncHfg=')),
              ),
            ),
          );
        });
      }, []);
  7. evanw commented on Jun 22, 2024

    @evanw
    Owner

    Data URLs are already URLs. You don't need to convert a data URL into an object URL to be able to use it. Doing that is slower, requires more code, and actually introduces a memory leak as URLs returned by URL.createObjectURL keep the blob alive until you call URL.revokeObjectURL. Instead, you can just use the data URL as the URL directly.

  8. lilouartz commented on Jun 23, 2024

    @lilouartz
    Author

    The reason I used object URLs is to workaround this issue https://stackoverflow.com/q/78645289/24982554

  9. lilouartz commented on Jun 23, 2024

    @lilouartz
    Author

    I am having a bit of a problem with this approach (of generating the image client-side).

    It looks like the amount of time it takes to generate image makes the placeholder appear as a blank space for a long-time:

    https://pillser.com/supplements/calcium-magnesium-zinc-with-vitamin-d3-2577

    I even removed the URL.createObjectURL logic. It's now just:

      useEffect(() => {
        requestIdleCallback(() => {
          setDataUrl(
            thumbHashToDataURL(base64ToUint8Array('a/cNG4L1NUOKhahX+XncHfg=')),
          );
        });
      }, []);
    
  10. lilouartz commented on Jun 23, 2024

    @lilouartz
    Author

    I ended up moving image generation logic back to server-side. The load experience is far better despite the larger payload size.

  11. Green-Sky commented on Jun 23, 2024

    @Green-Sky

    I ended up moving image generation logic back to server-side. The load experience is far better despite the larger payload size.

    That's kinda sad. You can still try to reduce the image size and transfer a low res version, since the blur is somewhat forgiving in zoom blur. (also check out the other issue i linked)

  12. andupotorac commented on Nov 30, 2024

    @andupotorac

    @lilouartz this looks like something you need: #46.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions