Как стать автором
Обновить
387.01
Рейтинг
Яндекс
Как мы делаем Яндекс

<img>. Доклад Яндекса

Блог компании ЯндексРазработка веб-сайтовJavaScriptИнтерфейсыHTML
«Просто добавь картинку на сайт», — говорили они. А оказалось, что «просто» не значит «правильно». В докладе я постарался разобраться, как эффективно добавлять изображения на страницу, какие форматы графики для каких случаев полезны и как автоматизировать автоматизируемое.

— Всем привет. У меня доклад с интригующим названием в виде одного тега.

Коротко представлюсь. Возможно, вы слышали о подкасте «Веб-стандарты» — иногда там можно услышать мой голос. И если новости в пабликах «Веб-стандартов» выходят с опечатками — скорее всего, это я. Работаю я в Яндекс.Поиске, разработчиком интерфейсов.

Сегодняшний доклад — по мотивам другого доклада. В 2019 году я успел вскочить в последний вагон и на последнем Web Standards Days прочитал доклад про ссылку.

Смотреть доклад про <a>

Интригующее название было. Тогда из зала прозвучал вопрос: «Когда будет про следующие теги?» Сегодня вы смотрите доклад именно про следующий тег, о котором мне хотелось рассказать.

1995


Начнем с истории. Шёл 1995 год. Тогда впервые появился тег img в стандарте HTML 2.0. Вы можете найти спецификацию — в то время они писались гораздо более сухо, чем сейчас. Но там есть интересные моменты.

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

В стандарте HTML 2.0 можно найти, что атрибутов у img тогда было не то чтобы много.

Был атрибут SRC, который дошёл до нас. Вы, кстати, можете увидеть тут у файла расширение xbm — X BitMap. Вы такой формат, возможно, и не застали. Я не застал. Был атрибут ALIGN, который позволял добавлять выравнивание этой картинке. Был ALT, он уже тогда был важен. Были всякие прикольные штуки вроде карт ISMAP, я про них ещё расскажу.

Кстати, интересный факт: в стандарте 1995 года есть пометка, что можно, а что не надо передавать в атрибут SRC, и там не рекомендуется указывать HTML-файлы. Видимо, кто-то пытался.

До стандарта HTML 2.0 были альтернативы того, каким образом выводить картинку, но победил именно тег IMG. И мы сейчас с ним живём.

2020


В 2020 году стандарт немножко поразноцветнее, и в нём гораздо больше подробностей.



<img>


Давайте поговорим про тег <img>, и начнём с простой конструкции, которую вы, скорее всего, когда-нибудь писали:

<img>

Этого достаточно, чтобы начать работать с картинкой. JavaScript, например, может взять этот код и дополнить. Возможностей много.

Но по-хорошему нужно добавить хотя бы атрибут src. Он про то, что нужно куда-то сходить, что-то скачать, а то, что скачается, каким-то образом отобразить.

<img src="cats.png">

Когда вы так пишете, то даёте инструкцию браузеру сходить за ресурсом по относительному адресу cats.png. Браузер отправляет запрос с HTTP-заголовками, эти заголовки можно на сервере обрабатывать и принимать какие-то решения.

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

Например, мы получили заголовок Accept. Он про то, с какими форматами изображений браузер умеет работать. Вы можете увидеть: браузер отправил информацию, что он умеет работать с WebP. Даже если запрошен был файл в формате PNG, можно всё-таки в качестве ответа подсунуть WebP, сэкономив трафик. Раз браузер говорит, что умеет, — пусть работает.

Есть и другие сущности, которые можно получить из HTTP-заголовков. Их удобно использовать, чтобы экономить трафик, принимать другие решения.

Атрибут src — мощная и сложная штука, потому что это один из немногих способов внутри HTML дернуть какой-нибудь URL. Ссылка — первый способ, но ссылка — это когда ты кликаешь сам, осознанно. А здесь пользователь ничего не нажимает, но куда-то идет запрос. Здесь речь и про безопасность тоже. Та ещё задача.

Форматы графики могут быть разные: gif, jpeg, ico, png, apng, tiff, svg, webp, avif и так далее. Это самые популярные из них. TIFF, кстати, до сих поддерживается в Safari. Другие браузеры я не проверял. Но официально поддержки вроде как нет.

Мы уже доросли до того, что AVIF — это поддерживаемый браузерами формат графики.

Про форматы изображений я в 2018 году читал отдельный доклад.

Смотреть доклад

Он был про то, как любым способом доставлять картинки (в том числе через background), как и что сжимать.

Вот еще один способ дёрнуть какой-нибудь URL без HTML:

const img = new Image();
img.onload = function() { /* ... */ };
img.onerror = function() { /* ... */ };
img.src = 'path/to/image.png';

Вы можете создать в JavaScript объект Image. Как только вы ему зададите src, браузер попытается этот src скачать. И по-хорошему вы должны добавить обработчик ошибок и обработчик загрузки. Получается, XHR не нужен! :)

Картинка — отличный способ дёрнуть URL, если вам не нужно обрабатывать нечто сложное. Всякие fetch, разные счётчики, метрики или что там у вас на страницах — всё это вы можете отправлять, дёргая картинку. Возможно, кто-то из вас разбирался, как работают Facebook-пиксели и прочие счётчики. Там применяется как раз такой способ: если скрипт выключен — дёрни картинку. По этой картинке можно из HTTP-заголовков получить много полезной информации.

Нужно понимать, когда у картинки при загрузке вызывается обработчик onerrorв коде выше. Это произойдет в нескольких случаях:

  • Когда src вы задали пустой: <img src="">. Тогда дёрнется onerror, потому что браузер не знает, что показывать.
  • Если вы укажете путь к текущей странице. Получается рекурсивный вызов: <img src="path/to/current-page">. В браузерах есть механизм защиты: не будет никакой рекурсии, он просто сразу бросит ошибку и не станет издеваться над собой.
  • Если формат не поддерживается. Например, если вы из Internet Explorer решили загрузить формат WebP.
  • Если вообще нет никаких данных о размерах картинки, браузер тоже не знает, как ему эту картинку нарисовать. Размеры могут прийти либо в метаданных, про них расскажу чуть позже, либо вы их задаёте атрибутами. Если в метаданных нет размеров и в атрибутах вы их тоже не задали, то браузер не знает, как и сколько резервировать места на странице, и бросает ошибку.
  • Последнее — поломанное изображение, что-то с сетью или с самой картинкой. В целом, если что-то с сетью, скорее всего, какие-нибудь пакеты не дойдут и будет беда.

Окей, допустим, что картинка загрузилась. Это такой счастливый пример для разработчика — всё хорошо выглядит, нормально загрузилось.


А вот такой пример мы же тоже в жизни видели, да?


Почему-то ресурсы не доходят, и браузер рисует какую-то странную иконку. Что нужно делать?

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

Атрибут ALT был ещё в первой версии спецификации IMG. Это замещение картинки для невизуальных браузеров или на случай, если картинка сломалась.



Если вы просто зададите alt, это уже не будет какая-то иконка в вакууме. Это будет текст. И если вы копируете фрагмент страницы и внутри находится картинка с alt, этот текст попадает в буфер обмена. Делать так даже в какой-то мере удобно, если там что-то информативное. Когда картинку нельзя скопировать, то можно скопировать хотя бы текст, описывающий эту картинку.

Есть важный нюанс: IMG — заменяемый элемент. Это значит, если картинка загрузилась, то всякие псевдоэлементы вы ему задать не можете — потому что IMG так себя ведет. Но если картинка не загрузилась, там появляется целый shadow-root, который вы можете посмотреть в Chrome DevTools, если поставите галочку «Show user agent shadow DOM» в настройках. Так можно увидеть, что вместо картинки показывается полноценный новый HTML. Вы можете добавлять туда псевдоэлементы before и after. И это можно использовать.

Например, Ире Адеринокун предлагает интересный способ.

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;
}



Когда вы указываете картинке, например, стили про текст, шрифты, цвета, то вы на самом деле можете таким образом, во-первых, стилизовать этот текст в alt. Во-вторых, добавить сюда ::before и ::after. И дать пользователю или разработчику понять, что картинка не загрузилась.

Например, для разработчика — возможно, для дебага — будет полезно добавить, как здесь в примере, img∷after. И вы можете при помощи CSS-функции attr() достать src из картинки и показать сообщение: вот эта картинка сломалась, почини, пожалуйста.

Кажется, это даже можно автоматизировать. Например, парсить страницу через Puppeteer или еще чем-нибудь. Если какие-то картинки сломались, находите, какие именно, даже таким визуальным способом, и где-то их собираете. Хотя, конечно, профилировать запросы в сеть в этом смысле гораздо удобнее. Но есть и такой способ.

Но всё равно такой плейсхолдер выглядит не очень, да? А что, если мы его стилизуем?

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;
}



Если у нас есть доступ к before и after, там же можно много чего натворить. Например, добавить after, приподнять его z-индексом над картинкой, чтобы не было видно стандартной иконки. И стилизуйте как хотите. Я эмодзи вставил — работает.

Главное — фантазия. У Лин Фишер, например, фантазии много, и есть сайт a.singlediv.com, где она на одном div делает целые анимированные произведения искусства. У вас есть before и after, целых два псевдоэлемента, которые можно стилизовать. Задумайтесь.



Но, допустим, мы вообще не хотим сломанных картинок. Можно ведь использовать сервис-воркер! Как с ними быть?

Еще один пример от Ире Адеринокун.

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

                // обработка ответа
            })
            .catch((err) => {
                // обработка ошибки
            })
    )
});

В сервис-воркере есть возможность, если мы вешаем обработчик на событие fetch и видим, что запрос сработал хорошо, просто вернуть его результат браузеру. Сходили за ресурсом — он есть — вернули.

Но есть два случая: либо мы сходили за ресурсом и ответ — не ок, либо на каком-то из этапов выбросилась ошибка. Это, скорее всего, значит, что пользователь в этот момент находится офлайн. Что можно сделать?

Можно определить, был ли это вообще поход за картинкой или не за картинкой, сделать вспомогательную функцию для этого.

function isImage(fetchRequest) {
    return fetchRequest.method === "GET"
        && fetchRequest.destination === "image";
}

Можно добавить на установку сервис-воркера поход за картинкой broken.png, которая, например, будет плейсхолдером для всех сломанных картинок. Положить её в кэш, когда интернет ещё есть.

self.addEventListener('install', (e) => {
    self.skipWaiting();
    e.waitUntil(
        caches.open("precache").then((cache) => {
            cache.add("/broken.png");
        })
    );
});

Затем мы просто берем непонятные нам случаи — когда либо что-то с запросом, либо выбрасывается ошибка — и возвращаем из кэша уже то, что лежит на локальном устройстве пользователя.

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");
                }
            })
    )
});

Вместо непонятной дефолтной иконки мы можем вернуть стильный плейсхолдер. Не стесняйтесь призвать дизайнера в помощь, чтобы сделать это красиво, а не своими силами рисовать плейсхолдеры при помощи CSS.

Конечно, можно пойти дальше и вообще все картинки закэшировать, чтобы они всегда возвращались. Но это не так интересно.

Хорошо. У нас есть атрибут alt. Мы его заполнили, подписали картинку.

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

Что будет, если мы атрибуту alt зададим пустое значение?



Во-первых, браузер не будет показывать эту сломанную иконку, когда картинка не загрузится. Вы даже не увидите, что у вас что-то не так. Не загрузилось, размеров нет, будет пусто. Во-вторых, для невизуальных браузеров это ещё и призыв не озвучивать картинку.



Таким образом, если у вас декоративное изображение, вы можете его спрятать.

Доступность


Картинки — визуальная штука. Людям с хорошим зрением, конечно, хорошо в интернетах сидеть и всё видеть, но я рекомендую вам сходить на weblind.ru, — отличный ресурс, где собраны рекомендации, как минимальными усилиями сделать ваши сайты чуть более доступными.

Короткая выдержка оттуда. Есть изображения информативные и декоративные. Информативные — это которые про контент, когда пишется текст, следом идёт картинка, которая дополняет этот текст, и вы в alt можете описать, что на этой картинке. Описывайте так, что если бы вы читали этот текст-описание, то как будто картинки даже не и было, можно её себе представить.

Декоративные — не несут особого смысла и, скорее всего, сделаны для красоты. Их можно спокойно выбросить, и ничего с текстом не случится.

У сайта «Веб-стандартов» есть CONTRIBUTING.md для тех, кто пишет статьи, переводы и так далее. У нас есть рекомендации, как всё это делать лучше. Совместно с Татьяной Фокиной сделали классные советы, которыми я хочу с вами поделиться.

  • Если картинка декоративная — ставится пустой alt, но ставится обязательно.
  • Если картинка со смыслом, то в alt вы пишете ёмкое описание изображения. Не надо коротко — «картинка», «город». Опишите, что там находится. Избегайте повторения предложений, которые уже есть на странице, потому что скринридеры читают текст, а потом вы точно такой же вставляете в alt, зачем? Он прочитается два раза.
  • Совет, который иногда даже вызывает холивары, хотя я не понимаю почему. В конце описания в alt ставьте точку. Когда скринридер читает и видит точку, он делает паузу перед тем, как читать что-то дальше. Так вот, alt — это предложение. Не стесняйтесь ставить там точку, если это реально что-то контентное.

Про декоративные изображения. Как их ещё можно скрывать от скринридеров?

Можно унести их в background-image. Это тот случай, когда можно сделать какой-нибудь div вместе img, тогда он спрячется, и всё.

Про пустой alt мы уже поговорили.

Можно добавить атрибут role="presentation", всё это присыпать aria-hidden="true", чтобы наверняка.

Это хорошие практики. Если действительно что-то мешает чтению картинки на слух, используйте их.

Можно ещё вашу картинку обернуть в figure.

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

Так вы можете ёмкое описание картинки вставить в alt. А в figcaption описать сам файл. Например, указать фотографа или источник фотографии. figcaption — он скорее не про описание картинки, а про описание файла.

Если вы хотите запихнуть в картинку нечто сложное, например график, то я рекомендую посмотреть в сторону SVG и выставить для него правильную роль.

<svg role="img"
     aria-label="Описание графика"
     aria-described-by="chart-desc">
    <desc id="chart-desc">
        Подробное описание графика
    </desc>
</svg>

Не забывайте про role="img, это важно.

Можно добавить aria-label, aria-described-by и полностью описать всё, что у вас на графике есть, полезно и подробно, при помощи desc, который есть у SVG.

Здесь я рекомендую посмотреть замечательный доклад Сергея Кригера о том, как делать доступные графики, — на случай, если вам действительно нужно сделать график и описать его, чтобы все могли им пользоваться.

Смотреть доклад

Вернёмся к котятам.

Я иногда видел советы — а зачем alt, давайте писать всё в атрибут title. Что такое title? Это когда вы наводите на картинку, проходит сколько-то секунд в зависимости от браузера, и появляется маленький тултипчик.

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

Рекомендация: не делайте так. Это не только моя рекомендация. Во-первых, как часто вы ловили себя на том, что специально наводите курсор на картинку, чтобы прочитать, что на ней? Я себя только во время подготовки к докладу попросил так сделать. Во-вторых, title не участвует в построении дерева доступности. Многие скринридеры его банально не читают. Да, есть нюансы, но в целом эта штука кажется немного бесполезной, если есть alt. Есть атрибут, который помогает, используйте его.

Размеры


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

Мы уже немного говорили о размерах. В браузере мы каким-то образом задаём ширину: width=500. Обратите внимание, что мы указываем не «500px». Не нужно задавать единицы измерения.

Итак, вы указываете это значение ширины. Тогда возникает вопрос: откуда браузер берёт второй размер, высоту?



А если задать только высоту, как браузер рассчитывает ширину? Откуда он её берет?

Здесь можно копнуть глубже. Например, вы можете расковырять ваши картинки как бинарные файлы и посмотреть в спецификации.



В спецификации PNG первые восемь байт вообще прибиты гвоздями, эти байты и говорят, что это PNG-файл. Дальше всякая служебная информация, и есть кусочек, я его подсветил выше, который говорит: ширина и высота вот такие. Прямо в первых, буквально заголовочных кусках файла есть размеры.

С GIF всё ещё проще, потому что сам формат проще.



Тут можно сходу сказать, что у этой гифки размеры 500x275. Можно прямо в текстовом редакторе посмотреть.

С JPEG — сложнее. Cходу такой же красивый и наглядный пример вам собрать не смог, потому что там много интересного, но не очень очевидного. С WebP даже не пытался.

Но для любых графических форматов есть правило: в самом верху файла, буквально с первыми пакетами при скачивании приходит размер. Как только браузер делает запрос за картинкой, и картинка начинает приходить, браузер тут же выцепливает эти размеры и резервирует с их учётом под картинку место.

Очень рекомендую доклад Полины Гуртовой «Картинки как коробки. Что же там внутри?». Полина классно рассказала, как вообще всё внутри устроено, про разные форматы, в том числе про PNG, о котором я только что говорил. Есть видео и расшифровка на Хабре, обязательно почитайте.

Если в метаданных картинки нет этих самых размеров, то мы можем их задать сами: width, height выставили, и счастье.

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

Тут важно задавать правильные размеры. Я думаю, все вы видели эти ужасные случаи, когда прямоугольная картинка из-за криво заданных размеров зачем-то вписывается в квадрат, и получается нечто непропорциональное. Поэтому здесь хорошо бы, конечно, прикрутить какую-то автоматику. Но если вы делаете это руками, скорее всего в бизнес-процессах есть этап добавления картинки где-нибудь в вашей админке, если контентом занимается контент-менеджер. Там вы можете получить размеры, сохранить их в базу и потом подставлять эти размеры в клиентский код. Например, подставлять физические размеры картинки, как есть. А уже дальше в CSS вычислить, что с ними делать.

Есть ещё интересный атрибут intrinsicsize, он про пропорции. Если мы не хотим задавать четкие значения width, height, а хотим делать картинки 16x9 или 400x300, то можем задать это таким атрибутом.

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

Но на самом деле нет, уже не можем. Авторы спецификаций обсуждали-обсуждали и пришли к тому, что, кажется, можно сделать более элегантно.

<style>
img {
    width: 100%;
    height: auto;
    aspect-ratio: attr(width) / attr(height);
}
</style>

<img src="image.jpg" width="500" height="500">

Подход такой. Раз есть атрибуты width и height, которые вроде как рекомендуется указывать всегда, в CSS можно использовать свойство aspect-ratio, которое их получает функцией attr(), или прибивает гвоздями, что у этой картинки пропорции должны быть 16x9 или 500x500.

Поддержка у этого свойства достаточно хорошая, на мое удивление. Уже можно, даже нужно, 66% пользователей будут счастливы.



Есть нюанс: вы можете задавать не конкретные пропорции, а только маппинги на width и height. И так умеют уже 89% браузеров. Поэтому вперёд!

Загрузка


Дальше можно поиграться с тем, как вообще эту картинку получить.

<img src="cats.png"
     alt="Три котёнка."
     width="500" height="275"
     referrerpolicy="origin">

Для начала можем задать, какую информацию мы отправляем на сервер. Вы же иногда подключаете картинки из каких-нибудь CDN или с внешних ресурсов, не только у себя их держите.

Так вот, referrerpolicy — это атрибут, который говорит, например: тут отправляй домен, а тут вообще не отправляй заголовок referrer.

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) …

В данном случае я чётко говорю браузеру: отправляй, пожалуйста, только домен и все. Больше ничего не надо, полный URL отправлять не надо. В конце концов это тоже информация, которую тот, кто хранит картинку, может использовать.

Если вы доверяете своему CDN, отправляйте всё что угодно, конечно. Но лучше аккуратно. По умолчанию стоит такое значение referrerpolicy, которое говорит, что с http-сайта сходить за https-картинкой можно. А вот наоборот не отправляются заголовки, когда это не безопасно.

Ещё один вариант — это когда вы ходите за картинкой на другой домен.

<img src="https://another.com/cats.png"
     alt="Три котёнка."
     width="500" height="275"
     crossorigin>

Если просто рисовать картинку через тег — да, всё нарисуется. Но если вы хотите, например, получить и нарисовать эту картинку где-нибудь в canvas, то вы таким образом получаете доступ чуть ли к этим бинарным данным, к исходнику картинки.

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.

Понятно, что мы, разработчики, можем эту картинку скачать себе любым другим способом и расковырять её у себя локально. Но если вы не настроили cross-origin запрос, то от браузера получите ошибку DOMException. Она будет говорить: простите, данные с другого домена — нельзя. И в этом случае нужно добавить атрибут crossorigin картинке, чтобы всё заработало.

У него есть в том числе и значение. По умолчанию это анонимный CORS-запрос. Он такой идёт на сервер: «Ну, пожалуйста, можно я поковыряюсь в байтиках?» И если сервер в ответ: «А, давай! Пожалуйста, на тебе Accept, бери всё и ковыряй», — то в canvas вы сможете поиграться с данными.

На самом деле это история не только про canvas. Например, вы хотите бинарные данные в LocalStorage положить. Подходов и применений много, но вам нужно получить доступ к бинарникам.

Яркий пример — CodePen. Если вы там когда-нибудь рисовали на canvas что-то из внешних изображений, то, возможно, сталкивались с ошибкой доступа к данным. Я сталкивался. Приходилось хранить картинки в base64, потому что у меня бесплатный аккаунт.

Ещё есть атрибут loading.

<img src="cats.png"
     alt="Три котёнка."
     width="500" height="275"
     loading="lazy">

И он вроде как классный, но есть нюансы. У него три значения:

  • auto — значение по умолчанию, браузер сам решает, когда загружать картинку.
  • eager — значит, загружать сразу. Когда HTML-парсер находит картинку, он сиюминутно отправляет за ней запрос.
  • lazy — это когда мы говорим браузеру: «Мне когда-нибудь эта картинка понадобится, но давай ты сам определишь, когда именно, и тогда за ней и сходишь». Полезно для экономии трафика.

На самом деле атрибут обалденный. Если по умолчанию он включается и ставится lazy везде, кроме, конечно, первого viewport, то вы сходу получаете экономию трафика и пользователю, и себе. Вам же, наверное, нужно оплачивать инфраструктуру. Это бесплатная польза минимальными усилиями.

Но, например, в исходном коде Chromium можно найти такие интересные настройки.

Offline — 8000
Slow 2G — 8000
2G — 6000
3G — 2500
4G — 1250

Когда соединение офлайн или очень медленное, то браузер загружает все картинки в пределах 8000 пикселей от текущего вьюпорта. При этом на самом быстром соединении — в пределах 1250 пикселей.

Причем значения постоянно меняются. Я так понимаю, разработчики браузеров проводят замеры, эксперименты, в интернете идут холивары, какие числа более правильные, потому что каждый браузер немножко по-своему их подбирает.

Чем хуже соединение, тем больше картинок качается. Это не очень очевидно, и вам нужно понимать, что это не про экономию трафика, а именно про скорость соединения.

Зачем так сделано — объяснение простое. Пользователь обычно страницы скроллит. И на медленном соединении, пока он доскроллит, уже всё должно быть хорошо, хорошо видны картинки. У пользователя должен быть потрясающий опыт пользования вашей страницей.

Чем медленнее соединение, тем раньше за картинкой нужно сходить. На быстром соединении оно вроде как быстро загрузится, зачем париться?

Но эти значения — 1250 — достаточно большие. Используйте на свой страх и риск, если вы в первую очередь задумывались про экономию трафика, а не про скорость.

В официальной статье про lazy loading на web.dev описан способ, как это сделать с поддержкой браузеров, которые не умеют в нативную ленивую загрузку.

<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>

Если умеют — вы сможете в прототипе HTMLImageElement найти свойство loading. Тогда, например, в атрибут data-src можно положить URL картинки, как мы делали еще до появления этого свойства, а потом его перекинуть в src.

Если нет, то взять библиотечку, которая сделает то же самое, но уже без браузерных механизмов. Если вновь поковыряться в коде того же Chromium, там точно так же создается Intersection Observer, как если бы вы это делали руками. И точно так же, но с более внутренними механизмами, делается всякая магия: «Ага, здесь мы определяем, сколько до вьюпорта, дальше качаем».



Если у пользователя JS выключен, loading ставьте сколько хотите и какой хотите — всё равно будет применено значение auto. Это сделано для приватности данных, потому что если JS выключен, картинкой вы всё ещё можете по скорости соединения и порядку скачивания при ленивой загрузке вычислить, что это за пользователь. Фингерпринтинг. А если пользователь выключает JS, то он, может быть, не хочет фингерпринтинг.

Есть ещё одна интересная штука — атрибут decoding.

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

Это когда вы уже прямо тюните сайт под перформанс.

  • Вы можете выставить значение sync. Это когда у вас рисуется что угодно, например текст. И картинка тут же декодируется, чтобы нарисоваться рядом.
  • async — это сказать браузеру: «Когда будут ресурсы, тогда и декодируй».
  • И auto — это, опять же, пусть решает браузер, значение по умолчанию.

У Эдди Османи есть отличное объяснение в твиттере, почему этот атрибут в целом важен:


В голове у нас процесс такой: картинка загрузилась, потом нарисовалась. На самом деле в серединке, между «загрузилось» и «нарисовалось», есть декодинг и ресайз картинки, не нулевые по времени процессы.

Декодинг картинки на среднестатистическом устройстве Moto G — это 300 миллисекунд в примере Эдди, заметно даже глазом. Стоит задуматься, что большие картинки декодируются дольше и, может, их надо позже декодировать, если не хотим зря тратить ресурсы. Важно: желательно не давать браузеру и ресайзить тоже. Пускай сразу приходит только самое нужное.



Помните: когда у вас выполняется JS, он блокирует основной поток. Будем считать, что браузер однопоточный, и вы блокируете рендеринг. Если вы выполняете долгий JS, то и картинка не нарисуется. Потому что браузер в этот момент занят, он ваш while (true) обрабатывает.

Штука, которая вызывает много вопросов по первости: как работают srcset и sizes? Я в несколько подходов разбирался и думаю, что наконец разобрался.

Есть такая запись.

<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">

  • В srcset вы задаете картинки, а рядом — маппинг на физический размер этих изображений. Что, значит физический? Это значит, что вы нажимаете в контекстном меню в системе «Информация о файле» и сколько пикселей лежит в этом файле, столько и нужно указывать. srcset — про физическую ширину.
  • Дальше в sizes вы указываете размер области под картинку, которая должна загрузиться. Берете медиавыражения и указываете, что, например, если у viewport ширина 320 пикселей, значит, мне нужно выделить область под картинку в 280 пикселей. Вы одновременно таким образом задаете width у картинки.

Что дальше сделает браузер? Он из srcset постарается выбрать самое подходящее по размеру изображение.

Здесь самое интересное. Спеки четко не прописаны, инструкция — грузить изображение, которое явно больше. То есть браузеры решают сами, как это оптимально сделать. И в разных браузерах поведение разное.

Chrome, например, выбирает картинку больше. То есть ближайшую не меньшую, так будет правильнее. Вот вы указали sizes. Например, у вас сработало: «Бери 280 пикселей» и Chrome постарается найти ближайшую картинку: 320. Если выбор пал на размер 440, какую картинку выбрать по ширине: 320 или 480? Он выберет 480, потому что ближайшая не меньшая.

В srcset ещё можно указывать плотность пикселей.

<img src="picture.png"
     srcset="picture@2x.png 2x">

«2x» — это Retina, сюда мы можем засунуть наши изображения повышенной чёткости и отдельно их загружать. Прелесть подхода: если HTML-парсер не понимает инструкцию srcset, но умеет в src, то он загрузит src. Это как fallback, будет работать во всех браузерах, даже самых старых.

Важно: чтобы с такой адаптивностью работать, не забывайте использовать метатег viewport, где вы будете чётко задавать, как соотносится физическая и логическая ширина на устройстве.

<meta name="viewport"
      content="width=device-width">

Иначе вы будете очень долго дебажить, почему вы просите одну картинку загрузить, а она вообще не та.

Вы можете позамерять, насколько эффективно вы используете картинки на странице. Например, я зашел на сайт GDG Russia при помощи пакета imaging-heap от Filament Group.



Там есть очень интересный логотип — всегда приходит размером 1706 пикселей. При этом этот логотип всегда занимает не больше 20 пикселей. В целом там трафика немного, картинка достаточно оптимизирована, проглядывается конструктор сайтов Tilda. Но imaging-heap позволяет посмотреть, насколько эффективно картинки используются на странице. Я им часто пользуюсь. Он умеет не только смотреть в теги img, но ещё и в background-image умеет залазить. Обязательно воспользуйтесь, поинспектируйте хотя бы свой сайт.

Давайте расширим img. Уже давно есть тег picture, обёртка над img, своего рода прокси, который в себя принимает кучу всяких штук и по факту потом всё равно прокидывает все это в img.

<picture>
    <source type="image/avif"
            srcset="cats.avif">
    <source type="image/webp"
            srcset="cats.webp">
    <img src="cats.jpg"
         width="20" height="20">
</picture>

Вы можете добавить разные source. Кстати, вопрос: почему source обрабатываются сверху вниз? Применяется первый срабатывающий source, дальше не применяется. Почему не последний?

Объяснение тоже простое. Как работает HTML-парсер в браузере? Просто идёт по коду слева направо и парсит его. Когда ваши страницы приходят чанками, то браузер получит кусочек, в нём найдёт picture, и если по source сразу понятно, что он подходит, браузер сразу сможет отправить запрос за подошедшей картинкой, не дожидаясь следующего HTML-чанка. Клёво. Поэтому source работает сверху вниз: что первое пришло, то и применится.

Прелесть в том, что мы можем поддерживать разные форматы. Не так уж давно зарелизился формат AVIF, в Chrome он уже поддерживается.

У Джейка Арчибальда есть потрясающая статья, которая объясняет, почему этот формат клёвый, в каких случаях он работает хорошо. Вы можете увидеть, что для некоторых картинок AVIF сжимает лучше, чем SVG. У меня в голове это по первости не укладывалось — это же векторный формат, а нас учили, что векторный формат занимает мало. Ничего подобного.



AVIF — это видеокодек AV1, который оптимизирован и адаптирован под изображения. Внезапно видеокодек позволяет сжимать картинки лучше, чем специальные картиночные форматы.

Обязательно посмотрите в статье Джейка, что это за формат такой. Кажется, его уже пора использовать.

Ингвар Степанян в твиттере поделился отличным способом, как вообще при помощи JavaScript минимальным кодом определять, поддерживает ли браузер тот или иной формат.

async function supportsImgType(type) {
    let img = document.createElement('img');
    document.createElement('picture').append(
        Object.assign(document.createElement('source'), {
            srcset: 'data:,x', // валидный URL, который не дёргает сеть
            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}`));
}

Прикольно: вы просто создаете фиктивный picture. В этот picture вы кладете source, для которого в srcset кладете минимально валидный URL data:,x. Это валидный URL, но он не дёргает сеть, то есть даже не нужно ходить ни на какой сервер. Если браузер умеет в такой тип, он просто заполнит у картинки currentSrc. Очень простой сниппет, и вы можете проверить поддержку почти любого формата.

Давайте расширять наш picture.

<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>

Мы знаем, что есть srcset. Сюда также можно положить картинки для ретины.

Ещё у нас есть медиавыражения. Мы можем точно так же под разные медиавыражения получать разные картинки. Например, смотрим на высоту изображения, ширину вьюпорта и берем более широкую, десктопную картинку. А для маленьких экранов — мобильную.

<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>

Звучит страшно: WebP, AVIF, JPG, PNG, все эти retina, да еще и под разные размеры! Как с этим работать?

Можно, конечно, попробовать руками.

Я обожаю инструмент Squoosh, там уже поддерживается AVIF. Так вот, Squoosh позволяет сжимать картинки, менять их размеры и не только. Но руками это долго.



Ещё пользуюсь ImageOptim. Например, некоторые картинки для этого доклада сжаты там. Тоже потрясающий инструмент. Но это всё — не автоматизация.

Для доклада собрал свой подход. Я фанат Gulp. Можете посмотреть у меня в репозитории: mefody/image-processor.

Использую простые пакеты: gulp-imagemin, gulp-responsive, imagemin-guetzli, imagemin-pngquant, imagemin-svgo, imagemin-webp. Думаю, у вас может быть похоже.

Я просто из этого стартера собираю себе почти под любой проект оптимизатор картинок, который распиливает их по разным размерам. gulp-imagemin внутри себя содержит imagemin.

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

Чем этот пакет хорош? Тем, что туда можно подключать новые плагины. Но там пока нет поддержки AVIF (на момент подготовки доклада — прим. автора).

Зато там есть поддержка Guetzli. Этот алгоритм тоже разработан в Google. В чем его суть?



Что обычно делают оптимизаторы? Они снижают качество у JPG, не меняя информацию о цвете. А Guetzli ещё и меняет цвета картинок, но так, чтобы визуально для глаза это почти не чувствовалось. Ключевое слово — «почти». И оказывается, за счёт такого незаметного изменения цветов можно сделать сжатие значительно круче.

Но происходит страшное. Даже на картинке 1000x1000 у вас начинает жужжать ноутбук, всё это обрабатывается очень долго. Алгоритм банально делает очень много разных переборов и сравнений. При этом сжимает настолько потрясающе, что у меня JPG обычно весят меньше, чем WebP. Поэтому я могу из своих source в принципе выкидывать ненужный мне WebP. Но, кстати, объём у сжатых через Guetzli картинок всё равно больше, чем у AVIF. Я попробовал.

Кстати, «guetzli» — это, я так понимаю, сладость, скандинавская выпечка. Как-то в подкасте мы пытались выяснить, что такое guetzli, brotli, zopfli. Оказалось, всё это — выпечка.

AVIF уже, на самом деле, в gulp засунуть можно, но через костыли.

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')}`
        ));
});

Потому что под imagemin пакета нет (уже есть — прим. автора), но у вас есть возможность вызвать gulp-exec, который дёргает какую-нибудь команду прямо у вас на локальной машинке. Я так и сделал, и оно работает. Достаточно просто, но при этом меньше контроля из Node.js: вы не можете количество файлов отслеживать в прогрессе, но, наверное, в будущем это будет возможно.

Для этого ещё нужно дополнительное шевеление бубном на Mac.

brew install joedrago/repo/avifenc

Я устанавливаю пакеты через brew. avifenc — это C++-пакет, который умеет работать с AVIF.

Словом, работу с AVIF уже можно автоматизировать. Не ленитесь.

Что ещё можно делать? Например, дёргать всего одну картинку, которую положить на 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. Очень рекомендую вам на него посмотреть. С ним можно всевозможными настройками задать размер, retina/не retina. Даже водяные знаки можно рисовать. Подобные CDN позволяют вам не думать про сборку. У вас есть только исходник, а уже CDN берет на себя часть работы, чтобы этот исходник, например, при первом обращении обработать, закэшировать и отдать.

Это один из подходов. Почему бы и нет? Каждый из нас живет в эпоху, когда всё рассчитывается на клиенте. Если у вас какой-нибудь React, который на ходу всё равно всё знает про браузер, — можете попробовать использовать походы в CDN вместо picture и source.

Ещё одна важная штука. В media вы можете задавать любые медиазапросы, не только про ширину и высоту. Например, можно подсмотреть, что у пользователя установлена настройка, что он любит тёмную тему.

<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>

Например, у человека может быть эпилепсия. Уважайте убеждение таких пользователей о том, что они не хотят смотреть ваши гифки. И, например, когда стоит настройка prefers-reduced-motion, то просто отдавайте статический кадр.

А вот если пользователь нормально относится к гифкам, рисуйте ему эти гифки. Хотя есть отдельный доклад Вадима Макеева pepelsbey «Делайте из слона муху», где он говорит, что не надо. Лучше подключайте видео, потому что GIF безумно много весит.

Смотреть доклад «Делайте из слона муху»

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


И другие атрибуты, из прошлого.

Я вам показывал, что можно было делать карты. Теперь поговорим про картинки. Вы можете задать картинке атрибут usemap, который позволял кликать на картинки, на кусочки картинок.

<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>



Вы рисовали нечто похожее на SVG, но это не SVG. И вы, задав область, в которую пользователь кликает, могли указать ссылку, которой нужно обрабатывать этот клик. Из картинки можно было сделать ссылку, без тега <a>.

Это прикольно, и, кстати, оно до сих пор работает в браузерах. Кажется, в Safari есть нюансы, но в целом вроде как работает. Сейчас непонятно, зачем, но поиграться можно.

Еще был атрибут ismap.

<a href="/imagemapper">
    <img src="image.png" ismap />
</a>

Кстати, он и сейчас работает, я проверил. Но для него нужна серверная поддержка. Если вы пишете ismap, это говорит о том, что ссылки, которые оборачивают картинку, когда я кликаю, дополнительно задаются как CGI параметры — через координаты точки, по которой вы в этой картинке кликнули. X и Y относительно левого верхнего угла. Например, /imagemapper?3,9. Здесь 3 — смещение по горизонтали, 9 — смещение по вертикали.

Таким образом можно одну картинку-меню сделать, обработать на сервере, узнать, куда пользователь кликнул. И, например, нарисовать иконки, в которые надо кликать.

Так делали раньше. Сейчас я таких обработчиков уже не встречал, но если хотите поприкалываться, почему бы и нет.

И есть deprecated-атрибуты, такие как align, border, hspace, vspace, name, onerror. Они браузерами всё ещё поддерживаются. Работает обратная совместимость веба: нельзя просто сломать половину сайтов.

Но считается, что вместо них уже нужен CSS. Причем атрибут align работает интересно, позволяет внутри блоков всякое выравнивать. Вроде как можно через float это всё сделать, но align более гибкий, что ли. Мне он в своё время нравился, а сейчас он уже deprecated.

Рамки


Есть задача, которая часто встаёт перед разработчиками, — поместить картинку в какие-то рамки.

Если просто выровнять по ширине — пожалуйста.

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

Максимальную ширину ставим 100%, высоту — auto, и браузер масштабирует как надо. Всё будет хорошо для горизонтальных картинок.

А что если нам нужны заданные пропорции? Например, часто есть такой заказ: «Давай вставим превьюшку с YouTube, которая обязательно должна быть 16x9».

Есть старый-добрый padding-хак.

.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%;
}

Вы создаёте контейнер нулевой высоты, ставите у него padding-bottom. Напоминаю, padding-bottom рассчитывается от ширины. Внезапное поведение.

Так вы чётко задаете пропорции 16x9. Кстати, сюда можно вставить calc(), например, чтобы было совсем понятно. И внутри просто через absolute эту картинку выравниваете. В итоге у вас есть контейнер, который переполняется, обрезается, и внутри как-то рисуется картинка.

Можно проще.

.img-container img {
    object-fit: cover;
}

Для всего этого уже можно использовать object-fit, не надо настолько упарываться. Он более гибкий, более удобный. При помощи object-position вы можете дотюнить поведение, если что-то приклеилось к низу или к верху.

Поддержка уже хорошая, но если вам нужно поддерживать много всякого, то, опять же, у Вадима Макеева на его канале есть подробный разбор.

Смотреть видео

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


Когда вы используете object-fit: cover, может произойти кадрирование. Предположим, разработчик вставил картинку с неожиданными пропорциями, и котёнка немножечко покоцало.



Как это делать правильно? Если вам обязательно нужен object-fit: cover, — идея! Давайте попробуем Machine Learning, так делают крупные корпорации, я видел, Twitter так делает.


Но будьте аккуратны, потому что Twitter с таким угодил в скандал. Оказалось, что алгоритм, который определял, как правильней кадрировать, был расистским. Публично извинялись. Машинное обучение — вроде крутая штука, но там ещё очень много проблем в том, как определить, что показывать.

Мне нравится подход, который случайно нашел на CodePen.

<div class="img-container">
    <img src="picture.png"
         alt=""
         class="img-blur"
         aria-hidden="true">
    <img src="picture.png" alt="Picture">
</div>

Вы можете использовать две картинки, тогда у вас не будет пустого пространства. Точнее, URL у картинок одинаковый. Но подключаете вы их в контейнере друг за дружкой. Первую прячете, чтобы ее не было слышно, например, на скринридере.

.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));
}

Затем при помощи хитрых хаков вторая картинка вся целиком помещается в контейнер. А вторая — object-fit: cover, растягивается на весь контейнер. И вы ее блюрите.

В итоге у вас получается вот такой эффект:


Красиво. На YouTube некоторые блогеры вертикальные видео похоже обрабатывают.

По ссылочке есть полный код, как это сделать. Мне такой подход больше нравится, потому что мы хотели заполнить всё, — заполнили, при этом картинку видно всю, а мы ничего лишнего не обрезали.

Знаете, что у картинок есть в CSS особенность — там можно задавать способ, как их рендерить?

.pixelated {
    width: 512px;
    height: 512px;
    image-rendering: -moz-crisp-edges;
    image-rendering: -webkit-crisp-edges;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
}



По умолчанию браузер, увеличивая вашу картинку, как-то её смазывает. Есть алгоритм, который делает это сглаживание. Вы можете сказать: «Нет, браузер, я хочу pixelated». И, увеличивая картинку, он будет реально рисовать чёткие границы пикселей.

Это полезно, если у вас, например, сайт про пиксельную графику, Lego, что-то с этим связанное. Я на некоторых сайтах видел — обалденный эффект. Выставляете маленькую иконку, скажем, 12x12, растягиваете ее, и такой пиксельный эффект сохраняется. Это ещё и обалденная экономия трафика.

Быстрее


Как можно еще быстрее грузить картинки?

<link rel="preload"
      as="image"
      href="important-image.jpg">

Вы можете заранее сказать браузеру при помощи тега link: «Браузер, вот эта картинка мне точно понадобится». Дело в том, что по умолчанию картинки грузятся с приоритетом Low — низким. Но предположим, вы скажете: «Браузер, вот preload, это очень важная картинка, она у меня обязательно рисуется». Тогде браузер будет её качать, во-первых, с более высоким приоритетом, а во-вторых, ещё до того, как вы обратитесь к этой картинке в HTML или CSS.

Есть подробный разбор от Эдди Османи, как эта штука работает. Можете посмотреть, как preload помогает ресурсы больше скомпоновать и загрузиться быстрее.



Итоги


  1. Выбирайте правильный формат изображений.

    Есть куча материалов о том, как и что выбирать. Мой выбор простой. Если есть возможность использовать AVIF — так и делайте. Если есть поддержка WebP — используйте его. Дальше оптимизируйте JPG, оптимизируйте PNG. Последний, кстати, тоже хорошо сжимается, если правильно настройки покрутить.
  2. Выбирайте правильный размер.

    Не заставляйте браузер зачем-то рисовать огромную картинку, если она лежит в окошке 20x20 пикселей. Вы и трафик экономите таким образом, и не даете CPU тратить время на ресайз.
  3. Всё автоматизируйте.

    Надеюсь, image-processor, который я для вас собрал, будет вам полезен. Я, по крайней мере, им пользуюсь. Может, и вам пригодится.

Есть еще классные материалы, которые я могу посоветовать:

  • Images done right: Web graphics, good to the last byte. Классная статья от «Злых марсиан», прямо внутренности того, как это всё устроено. Один из авторов — Полина Гуртовая. Обалденная статья, обязательно посмотрите.
  • A Guide to the Responsive Images Syntax in HTML. Классный гайд о том, как делать адаптивно любыми способами.
  • Essential image optimization. Обалденная интернет-книга от Эдди Османи о том, что с картинками можно делать. Сначала она была первой в списке, но там сломались картинки. Вот такая ирония. Остался только текст. (Теперь срабатывает редирект на отдельный сайт — прим. автора.)

Все ссылки — здесь.
Теги:htmlcssperformancea11y
Хабы: Блог компании Яндекс Разработка веб-сайтов JavaScript Интерфейсы HTML
Всего голосов 45: ↑45 и ↓0+45
Просмотры16K

Похожие публикации

Лучшие публикации за сутки