nuxt / image

Plug-and-play image optimization for Nuxt applications.
https://image.nuxt.com
MIT License
1.31k stars 267 forks source link

Support `<picture>` with multiple sources #309

Open timbenniks opened 3 years ago

timbenniks commented 3 years ago

It seems the picture tag implementation in the nuxt/image package is lacking basic picture tag features.

The picture tag and its sources (who act like regular images - with srcset and sizes, but with the addition of the "media" attribute) is meant to be able to show a different image for different matches in that media attribute.

Currently one source is added in the code which acts as a normal image tag with srcset and sizes. However, there is no mention of the media attribute in the implementation. see: https://github.com/nuxt/image/blob/main/src/runtime/components/nuxt-picture.vue

A picture tag is mainly used in a more "Art Direction" way and not “Resolution Switching”.

<picture> is capable of handling resolution switching, however you shouldn’t use it if that’s all you need. In such cases stick with a regular element plus srcset and sizes.

Example of average picture tag usage:

  1. On landscape devices I show an image in a 22:9 aspect ratio and a specific crop.
  2. On portrait devices I show an image in 1:1 ratio and with a crop on the face.

These two images are different. Not just their width/height, they are actually different files with different aspects ratios and compositions. Each one is a source in the picture tag. Each of these <source /> tags also has a srcset and a sizes attribute so they behave like a normal image nuxt image would. They have an additional media attribute which defines when they are shown. This field has a media query in it that tells the browser in which context to show the source.

An HTML example:

<picture>
    <source
        media="(orientation: landscape)"
        srcset="image-small.png 320w,
                image-medium.png 800w,
                image-large.png 1200w"
        sizes="(min-width: 60rem) 80vw,
               (min-width: 40rem) 90vw,
               100vw">
    <source
        media="(orientation: portrait)"
        srcset="image-small-portrait.png 160w,
                image-medium-portrait.png 400w,
                image-large-portrait.png 600w"
        sizes="(min-width: 60rem) 80vw,
               (min-width: 40rem) 90vw,
               100vw">
    <img src="image-small.png" alt="Image description">
</picture>

You can of course also use the picture tag for file type support etc

<picture>
  <source type="image/webp" srcset="illustration.webp">
  <img src="illustration.png" alt="A hand-made illustration">
</picture>

With the above in mind we should be able to add multiple images into the nuxt picture tag so sources can be generated with media attributes.

See image for differences: img-picture

atinux commented 3 years ago

Thanks for the detailed issue @timbenniks

How would you see such feature usage using <nuxt-picture> component without being overkill?

It is possible though to use the standard <picture> element and use Nuxt Image programmatically: https://image.nuxtjs.org/api/$img

pi0 commented 3 years ago

With the current nuxt-picture, one cannot use different versions of image/src with media selectors but is simply an img replacement with the ability for serving modern formats alongside legacy support.

As @Atinux mentioned, It is possible to directly use $img and $img.getSizes API creating any structure like media variable sources. We can also create a shortcut like <nuxt-source> to make it sexier:

      <picture>
        <nuxt-source
          media="(orientation: landscape)"
          src="image.jpg"
          sizes="xs:200px md:500px lg:1024"
        />
        <nuxt-source
          media="(orientation: portrait)"
          src="image-portrait.jpg"
          sizes="xs:200px md:500px lg:1024"
        />
        <nuxt-img
          src="image.jpg"
          width="200"
        />
      </picture>

POC: https://codesandbox.io/s/intelligent-water-nbikw?file=/pages/index.vue

In addition, we probably support scoped slots for nuxt-picuture, this way we can make it more minimalistic. (subject to discuss when added)

One alternative would be supporting more complex syntax for sizes prop. Combining source and sizes. We had it in previous versions but removed it due to complexities for simpler usecases and implementation.

timbenniks commented 3 years ago

Different approaches possible.

  1. nuxt-img has media attribute and due to this outputs
  2. slot in nuxt-picture
<nuxt-picture>
  <nuxt-img sizes="sm:100vw md:50vw lg:400px" src="option-1.png" media="(orientation: landscape)">
  <nuxt-img sizes="sm:100vw md:50vw lg:400px" src="option-2.png" media="(orientation: portrait)">
</nuxt-picture>
  1. nuxt-source = higher level component wrappign nuxt-img
  2. slot in nuxt-picture
<nuxt-picture>
  <nuxt-source sizes="sm:100vw md:50vw lg:400px" src="option-1.png" media="(orientation: landscape)">
  <nuxt-source sizes="sm:100vw md:50vw lg:400px" src="option-2.png" media="(orientation: portrait)">
</nuxt-picture>
<nuxt-picture :sources="[{
  src: 'image-1.png',
  media: '(orientation: landscape)'
  sizes: 'sm:100vw md:50vw lg:400px'
}, {
  src: 'image-2.png',
  media: '(orientation: portriat)'
  sizes: 'sm:100vw md:50vw lg:400px'
}]"/>
pi0 commented 3 years ago

I guess best would be going with 2 (nuxt-source and slot) 🙈

timbenniks commented 3 years ago

I think this: https://codesandbox.io/s/intelligent-water-nbikw?file=/pages/index.vue is the way to go.

johncastorina commented 3 years ago

I would very much like to have this feature.

My team is migrating to webp format for all images, yet we still have almost 5% of our userbase on browsers that are on older browsers that don't support webp, so a real, automatic fallback to .png would be extremely helpful.

mattatcubix commented 3 years ago

@johncastorina webp with fallback is already supported using nuxt-picture

There is a legacyFormat prop to set the fallback https://image.nuxtjs.org/components/nuxt-picture#legacyformat

luksak commented 2 years ago

I guess best would be going with 2 (nuxt-source and slot) :see_no_evil:

@pi0 I agree. The other two approaches both have obvious issues.

While working on my bolierplate repository (which is not ready for open source yet), i ended up writing a quite complex image component trying to abstract away missing features like this one.

Some other issues that that are related to this and make proper responsive image handling hard:

469

462

422

toddpadwick commented 1 year ago

Yes, I am confused about the current setup – what would be the use case if it doesn't support multiple sources? - isn't the only purpose for the picture tag to have different images altogether, such as a landscape image and a portrait image for different orientations?

AndrewTannerNA commented 1 year ago

Is there anything further planned for this request? Just stumbled into this issue myself having assumed that nuxt-picture would allow me to use all the features of native picture 😅

toddpadwick commented 1 year ago

I guess this library is no longer maintained as much as it was - does anyone know any alternative solutions which do support the picture tag?

atinux commented 1 year ago

Actually we are back at it and we were waiting some pending features on the Nitro side, very sorry about this 😬

toddpadwick commented 1 year ago

Ah okay, fantastic. It's open source so I completely understand these things take time. Just wanted to know whether I should be looking elsewhere or not but I will hold out for the update :). Thanks!

Michaela-McKanna commented 1 year ago

Hey is there a PR that is being reviewed for this update? My team is open to working on a PR since we are waiting for this feature for our project. Thanks!!

the-lensky commented 1 year ago

@toddpadwick > I guess this library is no longer maintained as much as it was - does anyone know any alternative solutions which do support the picture tag?

Have you found a replacement for the picture tag for vue until guys add this support for nuxt-picture?

AndrewTannerNA commented 1 year ago

@the-lensky Just write your own <picture>. That's what we do, just by leveraging the $img helper function to get the URLs. We prefer it, it gives us full control over the output.

toddpadwick commented 1 year ago

@the-lensky I am also doing what @AndrewTannerNA does when we need the picture tag, but for the most part, I'm just trying to make do with the nuxt-image as much as possible. Because when we've used the $img helper function to create our own picture tag, its much slower on page speed. But luckily, most of our projects don't require the picture tag too often.

notmods commented 10 months ago

Does anyone have a clean example of a manual implementation they may be able to share? I'm trying to display different sources based on display size and having a hard time getting this to work correctly.

riddla commented 10 months ago

I followed @timbenniks great example over at https://codesandbox.io/s/intelligent-water-nbikw?file=/pages/index.vue without problems.


<!-- components/Something.vue --->
<picture
  v-for="(image, index) in images"
  :key="index"
>
  <nuxt-source media="(orientation: landscape)" :src="image.src" />
  <nuxt-source
    media="(orientation: portrait)"
    :src="image.portraitSrc"
  />
  <nuxt-img
    srcset=""
    :src="image.src"
    :alt="image.alt"
    width="1920"
    height="1080"
    format="webp"
  />
</picture>

<!-- components/NuxtSource.vue -->
<template>
  <source :media="media" :srcset="cSizes.srcset" :sizes="cSizes.sizes" />
</template>

<script>
export default {
  props: {
    src: { type: String },
    media: { type: String },
    sizes: { type: String },
  },
  computed: {
    cSizes() {
      const img = useImage()
      return img.getSizes(this.src, {
        sizes:
          this.sizes || 
          'xs:320px sm:640px md:768px lg:1024px xl:1280px xxl: 1536px 2xl:1536px',
      })
    },
  },
}
</script>
Lehoczky commented 4 months ago

Is this still planned? Without this, the NuxtPicture component is missing the most essential feature of the native picture element. Given the component's name, people expect NuxtPicure to work just like its native counterpart, with the added benefit of image optimization.