vue.jsnuxt.jsv-forsrcset

srcset with a v-for


I might miss some basic vue.js knowledge, but I have the following question related to <picture> and specifically srcset

I have different formats of the same image coming from my api, like this:

"posts": [
  {
    "image": {
      "formats": {
        "thumbnail": {
          "hash": "thumbnail_image",
          "ext": ".png",
          "mime": "image/png",
          "width": 245,
          "height": 138,
          "size": 76.89,
          "url": "/uploads/thumbnail_image.png"
        },
        "large": {
          "hash": "large_image",
          "ext": ".png",
          "mime": "image/png",
          "width": 1000,
          "height": 563,
          "size": 916.23,
          "url": "/uploads/large_image.png"
        },
        "medium": {
          "hash": "medium_image",
          "ext": ".png",
          "mime": "image/png",
          "width": 750,
          "height": 422,
          "size": 562.28,
          "url": "/uploads/medium_image.png"
        },
        "small": {
          "hash": "small_image",
          "ext": ".png",
          "mime": "image/png",
          "width": 500,
          "height": 281,
          "size": 275.16,
          "url": "/uploads/small_image.png"
        }
      }
    }
  },

The final results I want to obtain is something like:

<picture>
  <source srcset="https://apiurl.com/large_image.png 1000w, https://apiurl.com/medium_image.png  750w, https://apiurl.com/small_image.png 500w" />
</picture>

I am already passing all of the formats as a prop into the component, so they are all stored as "images"

basically what I would like is to have a single source that can loop through the formats (not necessarily there's always large or small) and add the URL inside :srcset.

Of course I am able to do

<source v-for="format in images" :srcset="apiUrl + format.url">

which works but generates multiple source

What's the best approach or solution for this situation? Thanks


Solution

  • Simple Solution:

    If you don't want to use computed or a component, you could just use a method where you pass a post and it returns the srcset urls.

    methods: {
        postSrcSetUrls(post) {
            if (!post || !post.image || !post.image.formats)
                return null;
    
            let sizes = Object.keys(post.image.formats);
            return sizes.map(size => `${post.image.formats[size].url} ${post.image.formats[size].width}w`).join(", ");
        }
    }
    

    Then, in your template

    <picture v-for="post in posts">
        <source :srcset="postSrcSetUrls(post)" />
    </picture>
    

    Computed / Component Solution:

    I would make a component and use a computed property for what you're trying to do. You can then use Object.keys to get the names of the format sizes, which can then be used to loop through your formats.

    I've included a snippet below, which you'll probably want to change a little, for example to include your api url.

    This uses the Props, Computed Properties, Template Syntax, List Rendering and Component Registration sections of the documentation, that might be useful.

    Vue.component("post-image-formats", {
      template: "<div><strong>Src Set Url</strong><br /> {{srcsetUrl}}</div>",
      props: ["formats"],
      computed: {
        srcsetUrl() {
          if (!this.formats)
            return null;
    
          let sizes = Object.keys(this.formats);
          return sizes.map(size => `${this.formats[size].url} ${this.formats[size].width}w`).join(", ");
        }
      }
    });
    
    let posts = [{
        "image": {
          "formats": {
            "thumbnail": {
              "hash": "thumbnail_image",
              "ext": ".png",
              "mime": "image/png",
              "width": 245,
              "height": 138,
              "size": 76.89,
              "url": "/uploads/thumbnail_image.png"
            },
            "large": {
              "hash": "large_image",
              "ext": ".png",
              "mime": "image/png",
              "width": 1000,
              "height": 563,
              "size": 916.23,
              "url": "/uploads/large_image.png"
            },
            "medium": {
              "hash": "medium_image",
              "ext": ".png",
              "mime": "image/png",
              "width": 750,
              "height": 422,
              "size": 562.28,
              "url": "/uploads/medium_image.png"
            },
            "small": {
              "hash": "small_image",
              "ext": ".png",
              "mime": "image/png",
              "width": 500,
              "height": 281,
              "size": 275.16,
              "url": "/uploads/small_image.png"
            }
          }
        }
      },
      {
        "image": {
          "formats": {
            "thumbnail": {
              "hash": "thumbnail_image",
              "ext": ".png",
              "mime": "image/png",
              "width": 245,
              "height": 138,
              "size": 76.89,
              "url": "/uploads/thumbnail_image.png"
            },
            "large": {
              "hash": "large_image",
              "ext": ".png",
              "mime": "image/png",
              "width": 1000,
              "height": 563,
              "size": 916.23,
              "url": "/uploads/large_image.png"
            },
            "medium": {
              "hash": "medium_image",
              "ext": ".png",
              "mime": "image/png",
              "width": 750,
              "height": 422,
              "size": 562.28,
              "url": "/uploads/medium_image.png"
            },
            "small": {
              "hash": "small_image",
              "ext": ".png",
              "mime": "image/png",
              "width": 500,
              "height": 281,
              "size": 275.16,
              "url": "/uploads/small_image.png"
            }
          }
        }
      }
    ];
    
    new Vue({
      el: "#app",
      data: () => {
        return {
          posts: posts
        };
      }
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
    <div id="app">
      <div>
        <post-image-formats v-for="post in posts" :formats="post.image.formats"></post-image-formats>
      </div>
    </div>