<img>

Никита Дубко, Яндекс.Поиск

<img>

Никита Дубко, разработчик интерфейсов

Кто я?

<a>, Никита Дубко

1995

Hypertext Markup Language - 2.0

HTML 2.0

<IMG SRC="triangle.xbm" ALIGN="TOP" ALT="Warning">
<a href="http://machine/htbin/imagemap/sample">
    <IMG SRC="sample.xbm" ISMAP>
</a>

2020

HTML Living Standard

<img>

<img>
<img src="cats.png">
GET /img/cats.png HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) ...
Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: image
Referer: http://127.0.0.1:8080/img/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,ru-RU;q=0.8,ru;q=0.7
GET /img/cats.png HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) ...
Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: image
Referer: http://127.0.0.1:8080/img/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,ru-RU;q=0.8,ru;q=0.7

Форматы графики

Показываем картинки пользователю: подробное руководство / Никита Дубко
const img = new Image();
img.onload = function() { /* ... */ };
img.onerror = function() { /* ... */ };
img.src = 'path/to/image.png';​

XHR не нужен 😁

onerror

<img src="cats.png"
     alt="Три котёнка.">
Три котёнка.

IMG — заменяемый

img {
    font-family: 'Helvetica';
    color: darkred;
    text-align: center;
    min-height: 3em;
    display: block;
    position: relative;
}

img::before {
    content: "Картинка поломалась :(";
    display: block;
}

img::after {
    content: "(url: " attr(src) ")";
    display: block;
}
Три котёнка. Styling Broken Images
img::after {
    content: "📷" " " attr(alt);
    z-index: 1;

    line-height: 3em;
    color: rgb(100, 100, 100);
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: #ccc;
}
Три котёнка. Styling Broken Images

a.singlediv.com

Сервис-воркер 💡

self.addEventListener('fetch', (e) => {
    e.respondWith(
        fetch(e.request)
            .then((response) => {
                if (response.ok) return response;

                // 🤔
            })
            .catch((err) => {
                // ❌
            })
    )
});
Handling broken images with the service worker
function isImage(fetchRequest) {
    return fetchRequest.method === "GET"
        && fetchRequest.destination === "image";
}
self.addEventListener('install', (e) => {
    self.skipWaiting();
    e.waitUntil(
        caches.open("precache").then((cache) => {
            cache.add("/broken.png");
        })
    );
});
Handling broken images with the service worker
self.addEventListener('fetch', (e) => {
    e.respondWith(
        fetch(e.request)
            .then((response) => {
                if (response.ok) return response;

                if (isImage(e.request)) {
                    return caches.match("/broken.png");
                }

            })
            .catch((err) => {
                if (isImage(e.request)) {
                    return caches.match("/broken.png");
                }
            })
    )
});
Handling broken images with the service worker
<img src="cats.png"
     alt="Три котёнка.">

alt=""
⬇︎
не показывать иконку

alt=""
⬇︎
не озвучивать

weblind.ru

Изображения
⬋            ⬊
ℹ️ информативные         🖼 декоративные

Декоративная картинка без смысловой нагрузки — alt остаётся пустым.

Картинка со смыслом, которая дополняет или иллюстрирует текст. В alt пишется уникальное и ёмкое описание изображения. Избегайте повторения предложений, которые уже есть на странице.

В конце описания в alt всегда ставится точка, чтобы помочь скринридеру сделать паузу перед следующим дальше контентом.
CONTRIBUTING.md сайта Веб-стандартов

Декоративные изображения

<figure>
    <img src="picture.png" alt="Ёмкое описание картинки.">
    <figcaption>
        Информация об изображении
        (например, фото: автор).
    </figcaption>
</figure>

Графики 📊

<svg role="img"
     aria-label="Описание графика"
     aria-described-by="chart-desc">
    <desc id="chart-desc">
        Подробное описание графика
    </desc>
</svg>
Сергей Кригер — Разработка доступных графиков
<img src="cats.png"
     title="Три котёнка.">

❌ Please, don't ❌

Using the HTML title attribute
<img src="cats.png"
     alt="Три котёнка."
     width="500">

height 🤔

PNG

od -t u1 picture.png | less


0000000   137  80  78  71  13  10  26  10   0   0   0  13  73  72  68  82
0000020     0   0   7 128   0   0   4  56   8   6   0   0   0 232 211 193
0000040    67   0   0  32   0  73  68  65  84 120  94 236 157   7 152  93
0000060    87 117 239 255 167 221  50 125  70 163 222 172  46 217 146 229
0000100   110 185  96  27  66  47 110  88 174 184   0  14 193   1  66 224
0000120    81 220   0  83  19  32  47   1  30  37 148 132  22  30 132  16
0000140   120   4 135  98 192 198 113 145 109 220 173  94  71 178 186  52
0000160   189 220 118 206 217 239  91 187 220 123 230 206 157 185 119 154
PNG (Portable Network Graphics) Specification, Version 1.2

GIF

od -t u2 picture.gif | less


0000000     18759   14406   24889     500     275     247   60672   44217
0000020     16992   28728   27499   54514   18639   18754   38345   43654
0000040     26485   29547    9371    5653   42714   36246   46224   25751
0000060     51543   30857    6178   21282   21574   12611   60471   39341
0000100     33962   55926   34454   35472   13456    8994   39309   42698
0000120     54955   29592   39528   25193   34169   47286   27000   42394
0000140     35030   21592   45223   42984   22889    2065   22032   26190
0000160     31400   47730   34708   13928   47153   34188   17492   65351
Graphics Interchange Format (GIF) Specification
Картинки как коробки — что внутри? Полина Гуртовая
<img src="cats.png"
     alt="Три котёнка."
     width="500" height="275">

❌ This proposal is no longer active ❌

<img src="cats.png"
     alt="Три котёнка."
     intrinsicsize="400x300">
intrinsicsize Attribute on Media Elements Explainer
<style>
img {
    width: 100%;
    height: auto;
    aspect-ratio: attr(width) / attr(height);
}
</style>

<img src="image.jpg" width="500" height="500">
Setting Height And Width On Images Is Important Again
caniuse: aspect-ratio
<img src="cats.png"
     alt="Три котёнка."
     width="500" height="275"
     referrerpolicy="origin">
Referer and Referrer-Policy best practices
res.cloudinary.com
:method: GET
:path: /.../picture.png
:scheme: https
accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9,ru-RU;q=0.8,ru;q=0.7
cache-control: no-cache
pragma: no-cache
referer: http://example.com/
sec-fetch-dest: image
sec-fetch-mode: no-cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) ...
<img src="cats.png"
     alt="Три котёнка."
     width="500" height="275"
     crossorigin>
HTML Living Standard
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const image = document.getElementById('source');

ctx.drawImage(image, 0, 0);

// DOMException: Failed to execute 'getImageData'
// on 'CanvasRenderingContext2D':
// The canvas has been tainted by cross-origin data.
<img src="cats.png"
     alt="Три котёнка."
     width="500" height="275"
     load="lazy">
Browser-level image lazy-loading for the web

load

Connection px
Unknown 3000
Offline 8000
Slow 2G 8000
2G 6000
3G 2500
4G 1250
chromium/third_party/blink/renderer/core/frame/settings.json5
<img src="hero.jpg" alt="…">
<img data-src="unicorn.jpg" alt="…" loading="lazy" class="lazyload">

<script>
    if ('loading' in HTMLImageElement.prototype) {
        const images = document.querySelectorAll('img[loading="lazy"]');
        images.forEach(img => {
            img.src = img.dataset.src;
        });
    } else {
        const script = document.createElement('script');
        script.src =
            'https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.1.2/lazysizes.min.js';
        document.body.appendChild(script);
    }
</script>
Browser-level image lazy-loading for the web

JS выключен
⬇︎
loading=auto

<img src="cats.png"
     alt="Три котёнка."
     width="500" height="275"
     decoding="async">
"decode" attribute on <img>

decoding

twitter.com/addyosmani/status/938078402430382080

Долгий JS
⬇︎
заблокированный рендеринг

<img srcset="cats-320.jpg 320w,
             cats-480.jpg 480w,
             cats-800.jpg 800w"
     sizes="(max-width: 320px) 280px,
            (max-width: 480px) 440px,
            100vw"
     src="cats-800.jpg">
Простой подход к работе с отзывчивыми изображениями
<img src="picture.png"
     srcset="picture@2x.png 2x">
<meta name="viewport"
      content="width=device-width">

npx imagingheap https://gdg-russia.com/


<img src="https://static.tildacdn.com/.../logo-rus-bel.png">
╔══════════╤══════════╤════════╤════════════╤════════╤════════════╤════════╤════════════╗
║          │ Image    │ @1x    │ @1x        │ @2x    │ @2x        │ @3x    │ @3x        ║
║          │ Width in │ Image  │ Percentage │ Image  │ Percentage │ Image  │ Percentage ║
║ Viewport │ Layout   │ Width  │ Match      │ Width  │ Match      │ Width  │ Match      ║
╟──────────┼──────────┼────────┼────────────┼────────┼────────────┼────────┼────────────╢
║ 320px    │ 220px    │ 1706px │ 775.5%     │ 1706px │ 387.7%     │ 1706px │ 258.5%     ║
║ 400px    │ 220px    │ 1706px │ 775.5%     │ 1706px │ 387.7%     │ 1706px │ 258.5%     ║
║ 480px    │ 220px    │ 1706px │ 775.5%     │ 1706px │ 387.7%     │ 1706px │ 258.5%     ║
║ 560px    │ 220px    │ 1706px │ 775.5%     │ 1706px │ 387.7%     │ 1706px │ 258.5%     ║
║ 640px    │ 220px    │ 1706px │ 775.5%     │ 1706px │ 387.7%     │ 1706px │ 258.5%     ║
║ 720px    │ 220px    │ 1706px │ 775.5%     │ 1706px │ 387.7%     │ 1706px │ 258.5%     ║
║ 800px    │ 220px    │ 1706px │ 775.5%     │ 1706px │ 387.7%     │ 1706px │ 258.5%     ║
║ 880px    │ 220px    │ 1706px │ 775.5%     │ 1706px │ 387.7%     │ 1706px │ 258.5%     ║
║ 960px    │ 220px    │ 1706px │ 775.5%     │ 1706px │ 387.7%     │ 1706px │ 258.5%     ║
║ 1040px   │ 220px    │ 1706px │ 775.5%     │ 1706px │ 387.7%     │ 1706px │ 258.5%     ║
║ 1120px   │ 220px    │ 1706px │ 775.5%     │ 1706px │ 387.7%     │ 1706px │ 258.5%     ║
║ 1200px   │ 220px    │ 1706px │ 775.5%     │ 1706px │ 387.7%     │ 1706px │ 258.5%     ║
║ 1280px   │ 220px    │ 1706px │ 775.5%     │ 1706px │ 387.7%     │ 1706px │ 258.5%     ║
╚══════════╧══════════╧════════╧════════════╧════════╧════════════╧════════╧════════════╝
Legend:  @1x <100% >150%    Above @1x <75% 75%–92%
filamentgroup/imaging-heap
<picture>
    <source type="image/avif"
            srcset="cats.avif">
    <source type="image/webp"
            srcset="cats.webp">
    <img src="cats.jpg"
         width="20" height="20">
</picture>
AVIF has landed
async function supportsImgType(type) {
    let img = document.createElement('img');
    document.createElement('picture').append(
        Object.assign(document.createElement('source'), {
            srcset: 'data:,x', // валидный урл, который не дёргает сеть
            type
        }),
        img
    );
    await 0; // даём примениться currentSrc
    return !!img.currentSrc; // если браузер умеет, заполнит значение currentSrc
}

for (let type of ['image/png', 'image/jpeg', 'image/webp', 'image/avif']) {
    supportsImgType(type).then(supported => console.log(`${type}: ${supported}`));
}
RReverser/detect-img-type-support.js
<picture>
    <source type="image/avif"
            srcset="cats@1x.avif 1x, cats@2x.avif 2x">
    <source type="image/webp"
            srcset="cats@1x.webp 1x, cats@2x.webp 2x">
    <img src="cats@1x.jpg"
         srcset="cats@2x.jpg 2x"
         width="20" height="20">
</picture>
<picture>
    <source type="image/avif"
            media="(min-width: 1150px)"
            srcset="cats-desktop@1x.avif 1x, cats-desktop@2x.avif 2x">
    <source type="image/avif"
            srcset="cats-mobile@1x.avif 1x, cats-mobile@2x.avif 2x">
    <source type="image/webp"
            media="(min-width: 1150px)"
            srcset="cats-desktop@1x.webp 1x, cats-desktop@2x.webp 2x">
    <source type="image/webp"
            srcset="cats-mobile@1x.webp 1x, cats-mobile@2x.webp 2x">
    <source media="(min-width: 1150px)"
            srcset="cats-desktop@1x.jpg 1x, cats-desktop@2x.jpg 2x">
    <img src="cats-mobile@1x.jpg"
         srcset="cats-mobile@2x.jpg 2x"
         width="20" height="20">
</picture>

😱

squoosh.app
ImageOptim

mefody/image-processor

Пакеты

gulp.task('convert', gulp.series(
    'clean',
    'convert:retina',
    'convert:webp',
    'convert:avif',
    gulp.parallel(
        'optimize:guetzli',
        'optimize:png',
        'optimize:svg'
    ),
));
google/guetzli

AVIF

const gulp = require('gulp');
const exec = require('gulp-exec');

const config = require('./config');

gulp.task('convert:avif', () => {
    let src = `${config.base.dist}/**/*.{jpg,png}`;
    return gulp.src(src)
        .pipe(exec((file) =>
            `avifenc -c aom --min 20 --max 50 \
            ${file.path} \
            ${file.path.replace(/(png|jpg)$/ig, 'avif')}`
        ));
});

brew install joedrago/repo/avifenc

CDN 🌐

https://imgproxy.evilmartians.com/PzslbQaEHzr9AEfdvP0UCF49tg0S1PoQiGsHrNyf11s/rs:fill:960:540/dpr:2/g:ce/wm:0.5:soea:0:0:0.2/wmu:aHR0cHM6Ly9pbWdwcm94eS5uZXQvd2F0ZXJtYXJrLnN2Zw/plain/https:%2F%2Fwww.nasa.gov%2Fsites%2Fdefault%2Ffiles%2Fthumbnails%2Fimage%2Fpia22228.jpg

imgproxy

Тёмные темы 🌚

<picture>
    <source srcset="picture-dark.png"
            media="(prefers-color-scheme: dark)">
    <img src="picture-light.png">
</picture>

Движение 🐌

<picture>
    <source srcset="no-motion.jpg"
            media="(prefers-reduced-motion: reduce)">
    <img srcset="animated.gif">
</picture>
Reducing motion with the picture element

Пятиминутка ностальгии 🎩

<p>
    Please select a shape:
    <img src="shapes.png" usemap="#shapes">
    <map name="shapes">
        <area shape=rect coords="50,50,100,100"> <!-- the hole in the red box -->
        <area shape=rect coords="25,25,125,125" href="red.html" alt="Red box.">
        <area shape=circle coords="200,75,50" href="green.html" alt="Green circle.">
        <area shape=poly coords="325,25,262,125,388,125" href="blue.html" alt="Blue triangle.">
        <area shape=poly coords="450,25,435,60,400,75,435,90,450,125,465,90,500,75,465,60"
            href="yellow.html" alt="Yellow star.">
    </map>
</p>
Image maps
<a href="/imagemapper">
    <img src="image.png" ismap />
</a>

/imagemapper?3,9,
где
3 — смещение по горизонтали,
9 — смещение по вертикали

Deprecated

Рамки 🖼

По ширине

img {
    max-width: 100%;
    height: auto;
}

С заданными пропорциями

.img-container {
    position: relative;
    padding-bottom: 56.25%; /* 16:9 */
    height: 0;
    overflow: hidden;
}

.img-container img {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}
Setting Height And Width On Images Is Important Again

object-fit

.img-container img {
    object-fit: cover;
}
Погружение в object-fit: cover с полифилом и без

Кадрирование ✂️

ML 💡

<div class="img-container">
    <img src="picture.png"
         alt=""
         class="img-blur"
         aria-hidden="true">
    <img src="picture.png" alt="Picture">
</div>
.img-container > * {
    position: absolute;
    top: var(--offset, 0);
    left: var(--offset, 0);
    width: calc(100% - 2 * var(--offset, 0px));
    height: calc(100% - 2 * var(--offset, 0px));
    object-fit: contain;
}

.img-blur {
    --blur: 20px;
    --offset: calc(-1 * var(--blur));
    object-fit: cover;
    filter: blur(var(--blur));
}
Create blurred fills for images with aspect ratio containers in CSS
.pixelated {
    width: 512px;
    height: 512px;
    image-rendering: -moz-crisp-edges;
    image-rendering: -webkit-crisp-edges;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
}

Быстрее 🐎

<link rel="preload"
      as="image"
      href="important-image.jpg">
Preload late-discovered Hero images faster
Preload late-discovered Hero images faster

Итого 📝

Выбирайте правильный формат 🥋

Выбирайте правильный размер 📏

Автоматизируйте 🤖

Материалы ✨

Спасибо за внимание!

mefody.github.io/talks/img/
@dark_mefody
mefody@yandex-team.ru QR-код со ссылкой на доклад