How to use next.js Image component with HTML <picture> element?
Asked Answered
H

5

13

Next.js has an Image component that lazy loads images and also provides a srcset for a given image.

However, sometimes we want to deliver different images for different devices (art redirection).

Mozilla says we should use <picture> element for this purpose, providing different images for different media queries.

I can't find an article (even in next.js official docs) to tell us how can we do that using <Image> component.

Is it possible? How can I use next.js <Image> component and HTML <picture> element together?

Huddersfield answered 26/2, 2022 at 10:37 Comment(1)
github.com/vercel/next.js/discussions/19880Jaymie
T
11

I have searched for hundreds of websites, this is the only solution I found which workable on Next.js ( with tailwindcss ).

import Image from 'next/image'

    <div>
      <div className="md:hidden">
        <Image src="Banner-767x500.webp" height={500} width={767} />
      </div>
      <div className="hidden md:inline-flex lg:hidden">
        <Image src="Banner-1023x500.webp" height={500} width={1023} />
      </div>
      <div className="hidden lg:inline-flex xl:hidden">
        <Image src="Banner-1400x500.webp" height={500} width={1400} />
      </div>
      <div className="hidden xl:inline-flex">
        <Image height={500} width={2000} src="Banner-2000x500.webp" />
      </div>
    </div>
Trumaine answered 28/2, 2022 at 13:21 Comment(4)
Hello, i am a new to nextjs and web dev, can you please share class .css files? i copy/pasted your code, thinking it is some built in CSS classes, but it's not working, it displays all the images one on top of the other instead of 1.Pantry
Wouldn't hiding the images like this with css actually still load them in the background?Radiate
This works, but only if you don't use priority attribute. Otherwise all the images will be preloaded and will negatively effect your page load speed.Decretory
The Picture Element for responsive images, and the img.srcset for onload optimization images. This answer makes me sad.Questionable
A
8

UPDATE: As of Nextjs 13.5 a new experimental function unstable_getImgProps() was added to support advanced use cases without using the component directly, including the picture Tag:

import { unstable_getImgProps as getImgProps } from 'next/image';
 
export default function Page() {
  const common = { alt: 'Hero', width: 800, height: 400 };

  const {
    props: { srcSet: dark },
  } = getImgProps({ ...common, src: '/dark.png' });
  const {
    props: { srcSet: light, ...rest },
  } = getImgProps({ ...common, src: '/light.png' });
 
  return (
    <picture>
      <source media="(prefers-color-scheme: dark)" srcSet={dark} />
      <source media="(prefers-color-scheme: light)" srcSet={light} />
      <img {...rest} />
    </picture>
  );
}

https://nextjs.org/blog/next-13-5#nextimage-improvements


The new version of Next Image component removes a lot of the extraneous HTML and styles and allows full flexibility of the img tag. With that new improvement in the component we can use the picture tag and the image component as normal.

Plus, Web.dev explains the way the picture element works really well: https://web.dev/learn/design/picture-element/

In the same way that srcset builds upon the src attribute, the picture element builds upon the img element. The picture element wraps around an img element. If there is no img element nested inside the picture element, the picture element won't work. Like the srcset attribute, the picture element will update the value of the src attribute in that img element. The difference is that where the srcset attribute gives suggestions to the browser, the picture element gives commands. This gives you control.

So, understanding that the picture element is just a wrapper of the img element which the browser still requires, and using the new version of next/image (Nextjs v. 13), you can write it as:

import Image from "next/image";

<picture>
    <source srcset=".." media="..."/>
    <Image src="..." height="..." width="..." alt="..." />
</picture>
Anglicist answered 11/1, 2023 at 5:46 Comment(2)
This way the images in <source> tags won't be optimized. Do you have a solution for that?Decretory
@Decretory - so, after doing more testing it does work well in all browsers and uses the source tags except for macOS Safari. I think it's related to when Safari executes the JS and when it renders the picture tag, but I have to do more research to find out what is going on, it maybe a matter of adding priority to <Image />Anglicist
T
1

In my case. NextJs version 13. I successfully use loader parameter of Image component

UPDATED: I didn’t like the Loader solution, because The URL is not cached Next. Here is the updated solution (images cached by Next)

           <picture>
                <source media={`(max-width: 567px)`} srcSet={urlMobile} />
                <source media={`(min-width: 568px)`} srcSet={urlDesktop} />
                <Image src={urlDesktop} alt={text} fill />
           </picture>

Decition with loader (good for CDN caching with absolute URL images):

           <Image
                  src={urlDesktop}
                  alt=""
                  fill
                  sizes="(min-width: 568px) 568px, (min-width: 1024px) 1024px, 128px"
                  loader={({ width }) => (width >= 568 ? urlDesktop : urlMobile)}
           />

From official documentation. A loader is a function that generates the URLs for your image. It modifies the provided src, and generates multiple URLs to request the image at different sizes. These multiple URLs are used in the automatic srcset generation, so that visitors to your site will be served an image that is the right size for their viewport: https://nextjs.org/docs/pages/building-your-application/optimizing/images

Tome answered 2/6, 2023 at 8:33 Comment(1)
Yes, works very well, as you say. getImageProps is now a stable part of the Next.js Image API, listed here with Theme Detection and Art Detection code examples: nextjs.org/docs/app/api-reference/components/…Comely
B
0

A better approach is to use vanilla HTML as the following example, tested in Next.js 13.

Pros:

  • Full control of what you're showing and when.

Cons:

  • Manual optimization of assets.
<picture>
  <source srcSet="/portrait/my-dog.webp" media="(orientation: portrait)" />
  <img src="/my-dog.webp" alt="A beautiful labrador" loading="lazy" />
</picture>
Born answered 26/12, 2022 at 19:35 Comment(0)
L
0

It's been a while since the question was asked, but I finally found this: https://nextjs.org/docs/app/api-reference/components/image#getimageprops

Next.js <Image> element supports now getImageProps(), which allows for the same result as using <picture> and srcSet from HTML5.

Copied from their docs:

import { getImageProps } from 'next/image'
 
export default function Home() {
  const common = { alt: 'Art Direction Example', sizes: '100vw' }
  const {
    props: { srcSet: desktop },
  } = getImageProps({
    ...common,
    width: 1440,
    height: 875,
    quality: 80,
    src: '/desktop.jpg',
  })
  const {
    props: { srcSet: mobile, ...rest },
  } = getImageProps({
    ...common,
    width: 750,
    height: 1334,
    quality: 70,
    src: '/mobile.jpg',
  })
 
  return (
    <picture>
      <source media="(min-width: 1000px)" srcSet={desktop} />
      <source media="(min-width: 500px)" srcSet={mobile} />
      <img {...rest} style={{ width: '100%', height: 'auto' }} />
    </picture>
  )
}
Laconia answered 7/9, 2024 at 9:47 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.