Proper way to handle <picture> srcset attribute in Vue

Viewed 954

I have a Vue App with a Projects page, where I want to have many Project cards that will each have an attached picture.

This is what my Projects.vue route/component looks like currently. I omitted some irrelevant divs and classes for the sake of brevity.

<template>
    <div>
        <ProjectCard
            v-for="project in ProjectList"
            :key="project.title"
            :title="project.title"
            :description="project.description"
            :image="require(`../assets/images/projects/${project.name}.jpg`)"
        />
    </div>
</template>

<script>
    import ProjectCard from "@/components/ProjectCard";
    import ProjectList from "@/assets/images/projects/imageList.json";

    export default {
        name: "Projects",
        components: {
            ProjectCard
        },
        data() {
            return {
                ProjectList
            };
        }
    };
</script>

As you can see, it simply loops through a JSON list and passes some props down to the ProjectCard component.

This is the ProjectCard component

<template>
    <div>
        <img :src="image" />
        <h1>{{ title }}</h1>
        <h3>{{ description }}</h3>
    </div>
</template>

<script>
    export default {
        name: "ProjectCard",
        props: {
            image: String,
            title: String,
            description: String
        }
    };
</script>

This works fine, the pictures show up as they should, however I want to now replace my img tag with a picture tag that will have fallbacks for older browsers (as I want to use webp) as well as some responsive image rules.

Obviously, though, I can't just use the same require that I'm using just without a file extension, as then Webpack won't be able to recognize which asset it's supposed to be using.

As far as I see it, the only option I can think of is to have different props for the ProjectCard template that reference each different image format, but this seems like it's too manual of a process. Let's say the directory looks something like this:

projects /
    a-small.jpg
    a-medium.jpg
    a-large.jpg
    a-small.webp
    a-medium.webp
    a-large.webp

I'd have to make 5 additional props, just to get one image showing correctly, instead of just having the one prop that I can pass and just do something like this in the HTML

<picture>
    <source media="(max-width: 1400px)" :srcset="`${image}-large.webp`" >
    <source media="(max-width: 1000px)" :srcset="`${image}-medium.webp`" >
    <source media="(max-width: 600px)" :srcset="`${image}-small.webp`" >
    <source media="(max-width: 1400px)" :srcset="`${image}-large.jpg`" >
    <source media="(max-width: 1000px)" :srcset="`${image}-medium.jpg`" >
    <source media="(max-width: 600px)" :srcset="`${image}-small.jpg`" >
</picture>

Is there any way to do what I'd prefer to do in this situation?

EDIT: Solution

Tony offered a good solution, however I went a much simpler route and simply put my images in the public/ directory. No need to try wrestle with webpack imports this way!

1 Answers

Instead of having an image prop in your ProjectCard component, how about an images prop?

Projects.vue

<template>
    <div>
        <ProjectCard
            v-for="project in ProjectList"
            :key="project.title"
            :title="project.title"
            :description="project.description"
            :images="imagesAndMaxWidthMapped"
        />
    </div>
</template>

<script>
    import ProjectCard from "@/components/ProjectCard";
    import ProjectList from "@/assets/images/projects/imageList.json";

    export default {
        name: "Projects",
        components: {
            ProjectCard
        },
        data() {
            return {
                ProjectList: ....,
                imagesAndMaxWidthMapped: [
                  { maxWidth: '1400px', src: 'a-large.webp'},
                  { maxWidth: '1000px', src: 'a-medium.webp'},
                  { maxWidth: '600px',  src: 'a-small.webp'},
                  { maxWidth: '1400px', src: 'a-large.jpg'},
                  { maxWidth: '1000px', src: 'a-medium.webp'},
                  { maxWidth: '1000px', src: 'a-small.jpg'},
                ]
            };
        }
    };
</script>

ProjectCard.Vue

<template>
    <div>
      <picture>
        <source v-for="(image, index) in images" :key="index" media="(max-width: `${image.maxWidth}`)" :srcset="require(`../assets/images/projects/${image.src}`">
      </picture>
      <h1>{{ title }}</h1>
      <h3>{{ description }}</h3>
    </div>
</template>

<script>
    export default {
        name: "ProjectCard",
        props: {
            images: {
              type: Array,
              required: true,
            },
            title: String,
            description: String
        }
    };
</script>

Related