Gridsome: Serve webp with fallback to jpeg in g-image component

Created on 24 Dec 2019  路  3Comments  路  Source: gridsome/gridsome

Summary

At the moment the g-image component outputs an img tag but it might be worth considering serving an optimised webp source as well as a jpeg fallback source inside a picture element. This allows browsers that understand webp to download and display a smaller (or better quality) image than the fallback jpeg.

Basic example

Something like the following output presents the webp images in the first source and if the browser can display them will use that as a preference, if not the browser will fall back to the second source (jpeg).

<picture>
  <source
    data-srcset="/files/example_image.webp 800w, /files/example_image_large.webp 1040w"
    type="image/webp" sizes="503px"
    srcset="/files/example_image.webp 800w, /files/example_image_large.webp 1040w">
  <source
    data-srcset="/files/example_image.jpg 800w, /files/example_image_large.jpg 1040w"
    type="image/jpeg" sizes="503px"
    srcset="/files/example_image.jpg 800w, /files/example_image_large.jpg 1040w">
  <img src="/files/example_image.jpg"
    alt="Example alt text" sizes="503px"
    src="/files/example_image.jpg">
</picture>

Motivation

Faster loading images or higher quality images is the goal. Images in webp format are around 30% smaller for the same quality when compared to jpeg. An option could be to specify the jpeg quality and webp quality separately so that higher quality webp could be output for roughly the same file size as jpeg.

I wouldn't call this a high priority feature request but just a nice to have, or at least consider. Thanks!

feature request

Most helpful comment

383 already exists, but you did a further job by actually outputing how should <g-image /> render.

Note that, to have a WebP counterpart, WORKS with any popular images formats (PNG, JPEG, GIF).

Some technical precisions:

  • For browsers that support <picture>, all classes and styles applied to the <img /> tag will correctly render as if there was only this <img /> tag
  • For browsers that do not support <picture>, this tag, as well as <source />, will be replaced by <div>, so no problems
  • If the browser choose to render <img /> only, it is more likely an old device with low specs, better display a low res image

An example with GIF

<picture>
  <source type="image/webp" data-srcset="/img/mountain-480.webp 480w, /img/mountain-1024.webp 1024w", ... />
  <source type="image/gif" data-srcset="/img/mountain-480.gif 480w, /img/mountain-1024.gif 1024w, ..." />
  <img src="/img/mountain-480.gif" alt="Some mountains" />
</picture>

All these informations came from the knownledges I acquired during the time when I made a web app for a shop, and I needed to improve my performance score. Here is the current solution I used (not on Gridsome, on my previous web app for the shop I made with Gulp).

I used vanilla-lazyload for the lazy loading part.

I also checked whether the user wants to saveData, if yes, I only display the low image resolution no matter the viewport size.

<!-- src/component/MaterializePicture.vue -->
<template lang="pug">
  picture.lazy
    source(v-if="!saveData" v-bind:data-srcset="image | desktop | webp" v-bind:srcset="image | low | webp" type="image/webp" media="(min-width: 1201px)")
    source(v-if="!saveData" v-bind:data-srcset="image | laptop | webp" v-bind:srcset="image | low | webp" type="image/webp" media="(min-width: 981px)")
    source(v-if="!saveData" v-bind:data-srcset="image | tablet | webp" v-bind:srcset="image | low | webp" type="image/webp" media="(min-width: 601px)")
    source(v-bind:data-srcset="image | mobile | webp" v-bind:srcset="image | low | webp" type="image/webp" media="(min-width: 1px)")
    source(v-if="!saveData" v-bind:data-srcset="image | desktop" v-bind:srcset="image | low" v-bind:type="image | mime" media="(min-width: 1201px)")
    source(v-if="!saveData" v-bind:data-srcset="image | laptop" v-bind:srcset="image | low" v-bind:type="image | mime" media="(min-width: 981px)")
    source(v-if="!saveData" v-bind:data-srcset="image | tablet" v-bind:srcset="image | low" v-bind:type="image | mime" media="(min-width: 601px)")
    source(v-bind:data-srcset="image | mobile" v-bind:srcset="image | low" v-bind:type="image | mime" media="(min-width: 1px)")
    img(v-bind:class="classes + ' ' + 'lazy'" v-bind:data-src="image | mobile" v-bind:alt="description")
</template>
<script>
import desktop from "../filter/desktop";
import laptop from "../filter/laptop";
import low from "../filter/low";
import mime from "../filter/mime";
import mobile from "../filter/mobile";
import tablet from "../filter/tablet";
import webp from "../filter/webp";
import { mapGetters } from "vuex";

export default {
  filters: {
    desktop,
    laptop,
    low,
    mime,
    mobile,
    tablet,
    webp,
  },
  props: {
    image: {
      type: String,
      required: true,
    },
    description: {
      type: String,
      required: true,
    },
    classes: {
      type: String,
      default: "",
    },
    lazy: {
      type: Boolean,
      default: true,
    },
  },
  computed: {
    ...mapGetters("browser", ["saveData"]),
    ...mapGetters("app", ["lazyLoad"]),
  },
  mounted() {
    if (this.lazy) {
      this.lazyLoad.update();
    }
  },
};
</script>

Note that for SVG, you would not want to use a WebP counterpart as SVG will always be fastest to load/render than pixelated picture formats.

All 3 comments

383 already exists, but you did a further job by actually outputing how should <g-image /> render.

Note that, to have a WebP counterpart, WORKS with any popular images formats (PNG, JPEG, GIF).

Some technical precisions:

  • For browsers that support <picture>, all classes and styles applied to the <img /> tag will correctly render as if there was only this <img /> tag
  • For browsers that do not support <picture>, this tag, as well as <source />, will be replaced by <div>, so no problems
  • If the browser choose to render <img /> only, it is more likely an old device with low specs, better display a low res image

An example with GIF

<picture>
  <source type="image/webp" data-srcset="/img/mountain-480.webp 480w, /img/mountain-1024.webp 1024w", ... />
  <source type="image/gif" data-srcset="/img/mountain-480.gif 480w, /img/mountain-1024.gif 1024w, ..." />
  <img src="/img/mountain-480.gif" alt="Some mountains" />
</picture>

All these informations came from the knownledges I acquired during the time when I made a web app for a shop, and I needed to improve my performance score. Here is the current solution I used (not on Gridsome, on my previous web app for the shop I made with Gulp).

I used vanilla-lazyload for the lazy loading part.

I also checked whether the user wants to saveData, if yes, I only display the low image resolution no matter the viewport size.

<!-- src/component/MaterializePicture.vue -->
<template lang="pug">
  picture.lazy
    source(v-if="!saveData" v-bind:data-srcset="image | desktop | webp" v-bind:srcset="image | low | webp" type="image/webp" media="(min-width: 1201px)")
    source(v-if="!saveData" v-bind:data-srcset="image | laptop | webp" v-bind:srcset="image | low | webp" type="image/webp" media="(min-width: 981px)")
    source(v-if="!saveData" v-bind:data-srcset="image | tablet | webp" v-bind:srcset="image | low | webp" type="image/webp" media="(min-width: 601px)")
    source(v-bind:data-srcset="image | mobile | webp" v-bind:srcset="image | low | webp" type="image/webp" media="(min-width: 1px)")
    source(v-if="!saveData" v-bind:data-srcset="image | desktop" v-bind:srcset="image | low" v-bind:type="image | mime" media="(min-width: 1201px)")
    source(v-if="!saveData" v-bind:data-srcset="image | laptop" v-bind:srcset="image | low" v-bind:type="image | mime" media="(min-width: 981px)")
    source(v-if="!saveData" v-bind:data-srcset="image | tablet" v-bind:srcset="image | low" v-bind:type="image | mime" media="(min-width: 601px)")
    source(v-bind:data-srcset="image | mobile" v-bind:srcset="image | low" v-bind:type="image | mime" media="(min-width: 1px)")
    img(v-bind:class="classes + ' ' + 'lazy'" v-bind:data-src="image | mobile" v-bind:alt="description")
</template>
<script>
import desktop from "../filter/desktop";
import laptop from "../filter/laptop";
import low from "../filter/low";
import mime from "../filter/mime";
import mobile from "../filter/mobile";
import tablet from "../filter/tablet";
import webp from "../filter/webp";
import { mapGetters } from "vuex";

export default {
  filters: {
    desktop,
    laptop,
    low,
    mime,
    mobile,
    tablet,
    webp,
  },
  props: {
    image: {
      type: String,
      required: true,
    },
    description: {
      type: String,
      required: true,
    },
    classes: {
      type: String,
      default: "",
    },
    lazy: {
      type: Boolean,
      default: true,
    },
  },
  computed: {
    ...mapGetters("browser", ["saveData"]),
    ...mapGetters("app", ["lazyLoad"]),
  },
  mounted() {
    if (this.lazy) {
      this.lazyLoad.update();
    }
  },
};
</script>

Note that for SVG, you would not want to use a WebP counterpart as SVG will always be fastest to load/render than pixelated picture formats.

Nice addition with the check to see whether the user has asked to Save-Data.

A great alternative I'm currently implementing is Imagekit.io in combination with Vue Lazy Image. With this combination you can load webp and automatic compress and transform your images while still using lazy loading.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nolfranklin picture nolfranklin  路  3Comments

ShahrukhAhmed89 picture ShahrukhAhmed89  路  3Comments

tomtev picture tomtev  路  3Comments

nuriaheep picture nuriaheep  路  3Comments

tomtev picture tomtev  路  3Comments