Optimización

de imágenes

para la web

The sum of transfer size kilobytes of all external images requested by the page. An external image is identified as a resource with the png, gif, jpg, jpeg, webp, ico, or svg file extensions or a MIME type containing image.

DISCLAIMER

Después de la charla de Joan León ... sortearemos el libro "Image Optimization" de Addy Osmani entre todos los/as asistentes.

DISCLAIMER

49 images

283 MB

=

Other example

one more...

44 images

1.4 MB

32 images

7.4 MB

https://www.adevinta.es/

1

2

3

?

🙈

"Original"

"Original"

ImageOptim

+2

ImageOptim

+2

WebP

const sharp = require("sharp");
const fs = require("fs");

const ORIGINALS = "./home/original-images/";
const IMAGES = "./home/images/";
const FORMAT = "webp";

function convertToimg({ file }) {
  const filename = file.split(".").slice(0, -1).join(".");

  return sharp(`${ORIGINALS}/${file}`).toFile(
    `${IMAGES}${filename}.${FORMAT}`,
    () => console.log(`${IMAGES}${filename}.${FORMAT}`)
  );
}

WebP

fs.existsSync(IMAGES)
  ? fs.rmdirSync(IMAGES, { recursive: true })
  : fs.mkdirSync(IMAGES);


fs.readdirSync(ORIGINALS).forEach((file) => {
  const isImageFile = /\.(jpe?g|png|tif?f|avif)$/i.test(file);

  isImageFile && convertToimg({ file });
});

WebP

4,5 MB

15,5 MB

home

2,1 MB

17,5 MB

speakers

WebP

+1

WebP

+2

WebP

AVIF

const sharp = require("sharp");
const fs = require("fs");

const ORIGINALS = "./home/original-images/";
const IMAGES = "./home/images/";
const FORMAT = "avif";

function convertToimg({ file }) {
  const filename = file.split(".").slice(0, -1).join(".");

  return sharp(`${ORIGINALS}/${file}`).toFile(
    `${IMAGES}${filename}.${FORMAT}`,
    () => console.log(`${IMAGES}${filename}.${FORMAT}`)
  );
}

AVIF

2,3 MB

15,5 MB

home

1 MB

17,5 MB

speakers

AVIF

+1

AVIF

+2

AVIF

AVIF

const sharp = require("sharp");
const fs = require("fs");

const ORIGINALS = "./home/original-images/";
const IMAGES = "./home/images/";
const FORMAT = "avif";
const WIDTH = 680

function convertToimg({ file }) {
  const filename = file.split(".").slice(0, -1).join(".");

  return sharp(`${ORIGINALS}/${file}`)
  	.resize(WIDTH)
  	.toFile(
          `${IMAGES}${filename}.${FORMAT}`,
          () => console.log(`${IMAGES}${filename}.${FORMAT}`)
        );
}

AVIF

644 KB

17,5 MB

speakers

JPEG-XL

<picture>
  <source type="image/jxl" srcset="image.jxl">
  <source type="image/avif" srcset="image.avif">
  <source type="image/webp" srcset="image.webp">
  <img src="image.jpg" height="300" width="200"
       alt="Awesome image">
</picture>
<picture>
  <source
    sizes="(min-width: 600px) 100vw, 600px,
           (min-width: 1200px) 100vw, 1200px"
    srcset="image-wide.avif 600w,
            image-ultrawide.avif 1200w"
    type="image/avif">
  ...
  <img src="image.jpg" height="300" width="200"
       alt="Awesome image">
</picture>
<picture>
  <source
    sizes="(min-width: 600px) 100vw, 600px,
           (min-width: 1200px) 100vw, 1200px"
    srcset="image-wide.jpg 600w,
            image-ultrawide.jpg 1200w"
    type="image/jxl">
  <source
    sizes="(min-width: 600px) 100vw, 600px,
           (min-width: 1200px) 100vw, 1200px"
    srcset="image-wide.avif 600w,
            image-ultrawide.avif 1200w"
    type="image/avif">
  <source
    sizes="(min-width: 600px) 100vw, 600px,
           (min-width: 1200px) 100vw, 1200px"
    srcset="image-wide.webp 600w,
            image-ultrawide.webp 1200w"
    type="image/webp">
  <source
    sizes="(min-width: 600px) 100vw, 600px,
           (min-width: 1200px) 100vw, 1200px"
    srcset="image-wide.jpg 600w,
            image-ultrawide.jpg 1200w"
    type="image/jpeg">
  <img 
    loading="lazy"
    decoding="async"
    src="image.jpg"
    height="300" width="200"
    alt="Awesome image">
</picture>
<picture>
  <img 
    sizes="(min-width: 600px) 100vw, 600px,
           (min-width: 1200px) 100vw, 1200px"
    srcset="
      https://res.cloudinary.com/nucliweb/.../image.png 600px,
      https://res.cloudinary.com/nucliweb/.../image.png 1200px"
    src="https://res.cloudinary.com/nucliweb/.../image.png"
    loading="lazy"
    decoding="async"
    height="300"
    width="200"
    alt="Awesome image">
</picture>

.../upload/dpr_auto,f_auto,q_auto/v1683697498/...

We use...

image-set()

/* Select image based on resolution */
image-set(
  "image1.jpg" 1x,
  "image2.jpg" 2x
);

image-set(
  url("image1.jpg") 1x,
  url("image2.jpg") 2x
);

/* Select gradient based on resolution */
image-set(
  linear-gradient(blue, white) 1x,
  linear-gradient(blue, green) 2x
);

/* Select image based on supported formats */
image-set(
  url("image1.avif") type("image/avif"),
  url("image2.jpg") type("image/jpeg")
);

Takeaways

Use WebP, AVIF, JPEG-XL image format

Use native loading="lazy"

Preload the LCP image element

Use Responsive Images

Define the image size in the <img> tag

Use CDN image service

Resources