Чёткость до последнего байта: как делать веб-графику хорошо
по-серьезному о графике в интернетах: основы форматов изображений, инструменты для оптимизации и то, как люди воспринимают веб-графику
Перевод. Часть 1
Перевод. Часть 1
При подготовке очередного ланч-тайма мы наткнулись на статью в блоге Evil Martians от Полины Гуртовой, Риты Клубочкиной и Энди Барнова — и не смогли не перевести её :) Потому что ребята разложили по полочкам все-все форматы графических файлов в интернетах, и получилось круто, понятно и интересно. А ещё — много, поэтому сегодня публикуем первую часть материала.
Статья будет полезна фронтендерам, ПМ-мам, контентщикам и вообще всем, кто имеет дело с изображениями в интернетах (возможно, даже вашей маме).
Статья будет полезна фронтендерам, ПМ-мам, контентщикам и вообще всем, кто имеет дело с изображениями в интернетах (возможно, даже вашей маме).
~
Знаете ли вы, что средняя веб-страница для десктопа в 2019 занимала 2 МБ трафика, и половина всего, что веб-сервер отправляет браузеру пользователя, — это графика? JPEG, PNG, SVG, GIF и некоторые другие аббревиатуры известны каждому, кто когда-либо создавал хоть что-либо в «цифре». Может показаться, что всё отображаемое на странице касается только фронтенд-разработки, но на самом деле понимание специфики веб-изображений важно для всех членов продуктовой группы: от тех, кто отвечает за серверную часть, до дизайнеров, менеджеров и специалистов техподдержки клиентов.
Вместо тысячи слов
Если у вас не так много времени, чтобы читать эту длинную и извилистую статью, вот инфографика.
Встретились как-то в баре… кривая и цветовая кодировка
Технически гипертекст (текст, который ссылается на другой текст), представленный Дугласом Энгельбартом в 1968 году как основа современной веб-коммуникации, не нуждается в изображениях для передачи информации. Но реальность такова, что вниманием пользователя нужно управлять с помощью графического контента. Изображения, видео, CSS-анимация, рисованная графика Canvas API, WebGL, даже Flash — тёмная технология древних времен — все средства хороши в постоянной борьбе за удержание пользователя.
Для компьютера каждое изображение — это просто последовательность конкретных инструкций. То, как они переводятся в аппаратные пиксели, отображаемые на экране, — само по себе захватывающая история. Большинство форматов изображений, за исключением BMP (кто там в пейнте ещё рисует?), в точности не хранит значения пикселей. Немножко математики, и данные, содержащиеся в файле, декодируются в массив значений с цветовой кодировкой. Кодировка RGB (red, green, blue) — самая очевидная для передачи цвета.
Кодировка YCbCr тоньше, поскольку учитывает работу человеческого глаза и мозга: на самом деле мы более восприимчивы к изменениям яркости, чем к изменениям цвета.
Когда мы имеем дело с векторной графикой, то особенно не задумываемся о тройных или четверных кодировках (CMYK), как в растровых форматах. Больше интересуют геометрические примитивы: линии, круги и квадраты, которые по факту являются просто кривыми Безье.
PNG, GIF, JPEG, WebP, HEIC, AVIF (для видео) — растровые форматы, SVG — векторный формат. Ниже о каждом будет поподробнее.
Для компьютера каждое изображение — это просто последовательность конкретных инструкций. То, как они переводятся в аппаратные пиксели, отображаемые на экране, — само по себе захватывающая история. Большинство форматов изображений, за исключением BMP (кто там в пейнте ещё рисует?), в точности не хранит значения пикселей. Немножко математики, и данные, содержащиеся в файле, декодируются в массив значений с цветовой кодировкой. Кодировка RGB (red, green, blue) — самая очевидная для передачи цвета.
Кодировка YCbCr тоньше, поскольку учитывает работу человеческого глаза и мозга: на самом деле мы более восприимчивы к изменениям яркости, чем к изменениям цвета.
Когда мы имеем дело с векторной графикой, то особенно не задумываемся о тройных или четверных кодировках (CMYK), как в растровых форматах. Больше интересуют геометрические примитивы: линии, круги и квадраты, которые по факту являются просто кривыми Безье.
PNG, GIF, JPEG, WebP, HEIC, AVIF (для видео) — растровые форматы, SVG — векторный формат. Ниже о каждом будет поподробнее.
Для примера — большое изображение
Исходный размер изображения ниже, полученного на профессиональную видеокамеру — 37,8 МБ. Из уважения к вашему интернет-провайдеру, его сжали до 3,5 МБ без потери качества.
Но оптимизация самого изображения — это не первое, что стоит учитывать при работе с визуальным контентом в интернете. Для начала, оцените весь стек, стоящий за доставкой этих сочных байтов по сети к конечному пользователю. Для этого задайте себе пару вопросов:
- Загружаете ли вы все изображения одновременно или сосредотачиваетесь только на тех, которые должны отображаться в первую очередь (используете Lazy Loading)?
- Думали ли вы про HTTP/2, который поддерживает мультиплексирование (передачу нескольких потоков данных с меньшей скоростью по одному каналу) на уровне протокола?
- Вы эффективно сжимаете ваши файлы?
- Вы используете CDN?
Ничто не совершенно
Ваша ответственность перед пользователями в том, чтобы предоставить им изображения самого высокого качества, используя при этом минимум ресурсов: вычислительной мощности (как сервера, так и клиента), памяти компьютера и пропускной способности канала связи.
Мы будем говорить о техническом качестве изображения и не будем учитывать композицию, перспективу или цветовую гармонию. Просто представим, что у нас уже есть идеальное изображение, которое нужно разместить в интернете самым оптимальным способом.
Качество изображения — степень, в которой полученное изображение отличается от идеального. И под качеством имеется в виду следующее:
Мы будем говорить о техническом качестве изображения и не будем учитывать композицию, перспективу или цветовую гармонию. Просто представим, что у нас уже есть идеальное изображение, которое нужно разместить в интернете самым оптимальным способом.
Качество изображения — степень, в которой полученное изображение отличается от идеального. И под качеством имеется в виду следующее:
Резкость
Резкое векторное изображение слева и размытое растровое справа
Точность цветопередачи
Настоящий цвет слева против двадцати цветов справа
Отсутствие артефактов
Плавный рендеринг слева по сравнению с пикселизацией справа
Глаза, мозг и контекст
Как фронтенд-разработчик, верстающий макет в HTML и CSS, или как дизайнер, создающий этот макет, вы решаете одну и ту же проблему: иллюстрируете некоторую концепцию для ваших пользователей с помощью изображений. Поэтому они имеют значение ещё до того, как вы начнете применять какие-то инструменты. Например:
Разные контексты изображений
На первой картинке изображение — ключевой контент: мы ожидаем, что пользователь потратит некоторое время на его просмотр, поэтому нам нужно максимально возможное качество. На второй картинке текст важнее. Картинка там для антуража, поэтому её можно сжать по-максимуму, и никто не заметит. На третьем изображении — интерактивный элемент, где и текст, и изображение одинаково важны, поскольку они требуют действий пользователя. Такие элементы должны быть масштабируемыми, «многоразовыми» и занимать как можно меньше места, поэтому имеет смысл отказаться от растра в пользу вектора.
Другой критический фактор — человеческое восприятие. Яркость и контрастность важнее цвета, поэтому на нём можно сэкономить (и, опять же, никто не заметит).
Также следует помнить, что разные экраны и разные браузеры отображают графику немного по-разному. Иногда уловить это почти невозможно (особенно, если у вас нет экрана Retina), а учитывать — стоит:
Другой критический фактор — человеческое восприятие. Яркость и контрастность важнее цвета, поэтому на нём можно сэкономить (и, опять же, никто не заметит).
Также следует помнить, что разные экраны и разные браузеры отображают графику немного по-разному. Иногда уловить это почти невозможно (особенно, если у вас нет экрана Retina), а учитывать — стоит:
Одно и то же изображение в разных браузерах
Не все пиксели созданы равными
Что такое пиксель, в конце концов? Сейчас даже малыш, который некоторое время смотрел «Свинку Пеппу» на родительском телефоне, вероятно, имеет некоторое представление об этом. Однако всё не то, чем кажется.
Когда мы говорим «пиксель», то на самом деле имеем в виду одну из, по крайней мере, трех совершенно разных вещей:
Самый хитрый из них — это так называемый пиксель CSS. Его описывают как единицу длины, которая приблизительно равна ширине или высоте одной точки, которую человеческий глаз может комфортно видеть без напряжения.
Когда вы читаете текст на своем телефоне, вы держите экран на определенном расстоянии от глаз, но когда вы смотрите на экран компьютера, вы стараетесь отодвинуться подальше, чтобы читать было удобно. Чем больше расстояние между сетчаткой и экраном, тем больше будет размер «пикселя CSS».
Когда мы говорим «пиксель», то на самом деле имеем в виду одну из, по крайней мере, трех совершенно разных вещей:
- точку в аппаратном смысле,
- точку в смысле CSS,
- точку в смысле «данных изображения».
Самый хитрый из них — это так называемый пиксель CSS. Его описывают как единицу длины, которая приблизительно равна ширине или высоте одной точки, которую человеческий глаз может комфортно видеть без напряжения.
Когда вы читаете текст на своем телефоне, вы держите экран на определенном расстоянии от глаз, но когда вы смотрите на экран компьютера, вы стараетесь отодвинуться подальше, чтобы читать было удобно. Чем больше расстояние между сетчаткой и экраном, тем больше будет размер «пикселя CSS».
Как измеряется CSS-пиксель
Не вдаваясь в подробности, мы можем просто помнить, что «физический размер» пикселя CSS — это неизменное значение в миллиметрах, своё для каждого типа экрана. На экранах Apple Retina (iPhone, iPad, Mac) это значение варьируется от 0,11 до 0,23 мм. Даже не спрашивайте нас о форме пикселя (это сложный вопрос!), просто помните — используется одно значение, а не набор чисел.
Пиксель CSS также не совпадает с аппаратным пикселем — тот является минимальным размером точки в соответствии с физическими характеристиками конкретного экрана.
Теперь мы можем наконец поговорить о device pixel ratio (DPR) — соотношении пикселей устройства. Понимание DPR чрезвычайно важно, если вы хотите, чтобы ваши изображения выглядели одинаково хорошо на всех устройствах.
Вы можете открыть вкладку DevTools в любом браузере (нажмите F12), который вы используете прямо сейчас, и набрать devicePixelRatio, чтобы узнать DPR вашего текущего экрана. При этом, если перетащить окно на другой экран (например, на внешний дисплей) — значение обновится. Оно рассчитывается по формуле:
Пиксель CSS также не совпадает с аппаратным пикселем — тот является минимальным размером точки в соответствии с физическими характеристиками конкретного экрана.
Теперь мы можем наконец поговорить о device pixel ratio (DPR) — соотношении пикселей устройства. Понимание DPR чрезвычайно важно, если вы хотите, чтобы ваши изображения выглядели одинаково хорошо на всех устройствах.
Вы можете открыть вкладку DevTools в любом браузере (нажмите F12), который вы используете прямо сейчас, и набрать devicePixelRatio, чтобы узнать DPR вашего текущего экрана. При этом, если перетащить окно на другой экран (например, на внешний дисплей) — значение обновится. Оно рассчитывается по формуле:
devicePixelRatio = размер пикселя CSS / размер аппаратного пикселя
Возьмем изображение с шириной 1000 пикселей и высотой 1000 пикселей.
Поместим его на странице:
<img src="cake.jpg" style="width: 1000px;">
Установив width равным 1000px, вы говорите браузеру отображать 1000*1000 = 1 млн пикселей CSS. Если DPR (соотношение пикселей вашего устройства) равно единице (экран — не Retina), то задействуется 1000 * 1 * 1000 * 1 = 1 млн аппаратных пикселей.
Один аппаратный пиксель отвечает за один пиксель «изображения», как и должно быть интуитивно.
Но что, если у вас Retina, и DPR=2? Теперь ваше изображение отображается с помощью 1000 * 2 * 1000 * 2 = 4 млн аппаратных пикселей. Это означает, что один пиксель вашего изображения отображается четырьмя аппаратными пикселями. Иными словами, изображение масштабируется: каждый пиксель его данных «растягивается» для отображения на экране.
Один аппаратный пиксель отвечает за один пиксель «изображения», как и должно быть интуитивно.
Но что, если у вас Retina, и DPR=2? Теперь ваше изображение отображается с помощью 1000 * 2 * 1000 * 2 = 4 млн аппаратных пикселей. Это означает, что один пиксель вашего изображения отображается четырьмя аппаратными пикселями. Иными словами, изображение масштабируется: каждый пиксель его данных «растягивается» для отображения на экране.
Маленький пиксель в большом мире
Как мы выяснили, размеры изображения иногда не соответствуют аппаратному обеспечению один к одному. А поскольку браузеры не могут определить содержание растрового изображения, они используют универсальные алгоритмы (математика!), чтобы подгадать лучшую стратегию изменения размера. И, как вы можете догадаться, они не всегда угадывают правильно.
Вы можете управлять этим процессом с помощью CSS-свойства image-rendering (отображение изображения). Оно помогает задать стратегию, но вы по-прежнему не можете контролировать конкретный алгоритм, выбранный браузером.
Вы можете управлять этим процессом с помощью CSS-свойства image-rendering (отображение изображения). Оно помогает задать стратегию, но вы по-прежнему не можете контролировать конкретный алгоритм, выбранный браузером.
Разные значения image-rendering CSS и как они отображаются в Chrome
Масштабирование области 4px до 16px
При масштабировании области 4px до 16px в браузере доступно только четыре пикселя изображения, остальные 12 необходимо угадать. Размытые края и другие артефакты — результат такого «угадывания» или интерполяции, выполняемой алгоритмом изменения размера.
Ваша цель в том, чтобы избежать вот такого масштабирования любым способом:
Ваша цель в том, чтобы избежать вот такого масштабирования любым способом:
- Постарайтесь понять контекст изображения и установить приемлемое качество.
- Измените размер оригинальной картинки на max_size_of_your_container * DPR.
- Подготовьте несколько версий изображений для большинства распространенных DPR (равному 1, 2 и даже 3).
- Учтите это в вашей разметке:
<img srcset="cupcake.png 1x, cupcake@2x.png 2x, cupcake@3x.png 3x" src="cupcake.png">
SVG: очень хорошее масштабирование
SVG — это масштабируемая векторная графика. Так что по определению SVG-изображения не боятся масштабирования. Почему?
Взглянем поближе на анатомию файла SVG. Во-первых, содержимое написано в виде простого текста, который читается человеком и подозрительно напоминает старый-добрый XML.
Ниже — печенька, созданная при помощи простых геометрических фигур, и экспортированная в виде svg-файла:
Взглянем поближе на анатомию файла SVG. Во-первых, содержимое написано в виде простого текста, который читается человеком и подозрительно напоминает старый-добрый XML.
Ниже — печенька, созданная при помощи простых геометрических фигур, и экспортированная в виде svg-файла:
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="120" viewBox="0 0 200 120">
<g fill="none">
<ellipse cx="100" cy="60" fill="#FDC970" rx="100" ry="60"/>
<path fill="#794545"
d="M69.5433628,33.5480878 C65.8626549,33.5480878 61.7766372,31.1161129 58.1720354,32.2999373 C56.2746903,32.9232602 56.3815929,38.2702194 60.5900885,38.6347335 C65.4106195,39.0530408 69.8828319,39.0225705 69.3709735,33.1215047 C69.3550443,32.9383072 69.0442478,32.9992476 68.881062,32.9383072 M122.896283,19.855674 C118.705133,19.4005016 114.333451,17.1182445 110.322832,18.4897806 C108.631504,19.0679624 108.557522,23.9431975 110.322832,24.1549843 L110.835398,24.7019436 C113.283894,24.9953605 115.762124,25.3203762 118.217699,25.1021944 C121.980531,24.7681505 123.187611,20.541442 119.776283,18.861442 M153.981593,50.2878997 C152.356106,49.1322884 150.801062,47.8563009 149.105841,46.8206897 C143.614159,43.4659561 139.981239,46.0728527 142.624779,53.3021944 C142.882478,54.0071473 143.838938,54.2219436 144.547965,54.2862696 C146.88177,54.499185 149.286726,54.6590596 151.573097,54.1188715 C152.198584,53.9710345 152.378407,51.1380564 152.378407,48.2467712 M90.3642478,74.3676489 C90.1281416,74.2999373 82.6669027,71.6516614 80.7334513,72.3396865 C79.4789381,72.7862069 76.300177,74.2623197 77.5178761,74.8126646 L77.1681416,78.876489 C84.1083186,82.0122884 88.0523894,79.6845141 87.441062,74.061442 M41.1472566,60.9460815 C34.3621239,61.3455799 42.7111504,61.193605 35.5578761,60.1429467 C32.7461947,59.729906 25.1047788,62.6068966 30.1058407,66.3366771 C36.7384071,71.2830094 41.4778761,66.7895925 40.3543363,60.5902194 M141.784425,93.0744828 C135.735221,93.0744828 143.533451,93.3603762 131.213451,89.6674608 C127.678938,88.6081505 125.312566,93.9363009 127.677168,96.1651411 C130.754336,99.0647022 140.590442,98.4154232 140.590442,93.0221944 M113.093097,50.5941066 C110.309381,50.4473981 107.51823,49.9011912 104.740885,50.1547335 C103.281416,50.2878997 102.962832,56.4951724 105.088142,56.8611912 C108.850973,57.5097179 119.12708,58.2022571 112.47292,49.9373041"/>
</g>
</svg>
Пока её нельзя увидеть. Вместо этого — набор тегов:
Внешний вид фигуры определяется атрибутами. В нашем примере rx, d, cx, и другие подобные атрибуты отвечают за размер и положение. Это координаты, но не в системе координат экрана — они рассчитываются относительно параметра viewBox у SVG-изображения. В нашем случае:
viewBox="0 0 200 120″
Это значит, что изображение имеет отступ в 0 пикселей слева и 0 пикселей сверху, ширину 200 пикселей и высоту 120 пикселей. Это размер и расположение области просмотра, в которой находится наше изображение. Наш SVG также имеет width- и height-атрибуты — они определяют реальный размер самого изображения. В нашем случае это будет 200×120 CSS-пикселей (картинка 1).
- <svg> — это контейнер для нашей картинки,
- <g> — единица композиции (на самом деле этот тег бесполезен в примере кода выше, он только для демонстрации),
- <ellipse>, <path> — геометрические примитивы. <path> — самая универсальная форма, которую можно использовать для всех других фигур (круги, треугольники, сложные кривые).
Внешний вид фигуры определяется атрибутами. В нашем примере rx, d, cx, и другие подобные атрибуты отвечают за размер и положение. Это координаты, но не в системе координат экрана — они рассчитываются относительно параметра viewBox у SVG-изображения. В нашем случае:
viewBox="0 0 200 120″
Это значит, что изображение имеет отступ в 0 пикселей слева и 0 пикселей сверху, ширину 200 пикселей и высоту 120 пикселей. Это размер и расположение области просмотра, в которой находится наше изображение. Наш SVG также имеет width- и height-атрибуты — они определяют реальный размер самого изображения. В нашем случае это будет 200×120 CSS-пикселей (картинка 1).
Баловство с различными атрибутами SVG: 1. Исходная картинка; 2. Убрали атрибуты ширины и высоты; 3. Ширина = 30 пикселей; 4. Атрибут preserveAspectRatio: none; 5. Атрибут preserveAspectRatio: meet; 6. Атрибут preserveAspectRatio: slice.
Удалим width- и height-атрибуты. Теперь размер нашего изображения определяется размером окна браузера (картинка 2). Другой способ установить размер для изображения — задать width и/или height в CSS. Например:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 120" style="width: 90px" >
Теперь размер изображения составляет 20% от высоты окна браузера (картинка 3). Но подождите, почему наше печенье расположено по центру? Причина — еще один атрибут, который называется preserveAspectRatio. Его значение по умолчанию — xMidYMid meet. Это значит: сохранить исходные пропорции изображения, сохранить содержимое текущего viewBox видимым, масштабировать изображение как можно больше, центрировать результат как по вертикали, так и по горизонтали. Но если поиграть с его значениями, получится три разных результата — картинки 4−6.
А если нам нужна только половинка печенья, можно просто уменьшить ширину viewBox (картинка 6):
А если нам нужна только половинка печенья, можно просто уменьшить ширину viewBox (картинка 6):
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 120" preserveAspectRatio="xMinYMin meet" style="width: 90px">
Используя атрибуты viewBox и preserveAspectRatio вместе, можно масштабировать изображения миллионами способов.
Скрытые способности SVG
Помимо тегов, кривых и координат, SVG скрывает ещё некоторые сюрпризы. Во-первых, вы можете поместить растровые изображения внутрь. Этот прием полезен, если нужно добавить интерактивность к другому растровому изображению или как-то пометить его. Поэтому всегда проверяйте, а действительно ли SVG-изображение, с которым вы имеете дело, векторное.
Во-вторых, мы можем анимировать пафы (<path>) внутри SVG-изображения, и сделать само изображение анимированным.
Наконец, SVG-файлы могут быть опасны. Ничто не мешает кому-то поместить тег <script> и немного JavaScript в синтаксис XML. А браузер запустит этот код. Так что соблюдайте осторожность при работе с SVG из ненадежных источников и тщательно проверяйте, что там внутри.
Во-вторых, мы можем анимировать пафы (<path>) внутри SVG-изображения, и сделать само изображение анимированным.
Наконец, SVG-файлы могут быть опасны. Ничто не мешает кому-то поместить тег <script> и немного JavaScript в синтаксис XML. А браузер запустит этот код. Так что соблюдайте осторожность при работе с SVG из ненадежных источников и тщательно проверяйте, что там внутри.
SVG-файлы могут быть опасны
SVG против растровых форматов
Ну ок, SVG-изображения имеют суперспособности. Но должны ли мы всегда использовать их для простой графики? Нет! Использование SVG дает нам файл с изображением печеньки в 26 КБ, в то время как растровый WebP (подробнее о нем позже) того же изображения умещается в 16 КБ. И если нет никакой разницы, зачем платить больше?
Вы можете работать с SVG — создавать и редактировать его — прямо в консоли браузера. Но удобнее и профессиональнее заняться этим в редакторе векторных изображений. Например, в Boxy SVG с полным набором опций для работы с векторной графикой, а также встроенным редактором кода, если очень хочется поковыряться.
А теперь пришло время оптимизировать SVG-изображение с помощью svgo. Благодаря этому можно получить SVG-файл в 13 Кб вместо растрового файла формата WebP в 16 Кб. Вектор снова выиграл!
Поскольку изображения SVG — это просто текстовые файлы, они очень хорошо сжимаются. Мы ещё раз повторим: «Идеального формата не существует», но вы можете выбрать подходящий формат для вашей задачи и применить столько оптимизаций, сколько захотите.
Вы можете работать с SVG — создавать и редактировать его — прямо в консоли браузера. Но удобнее и профессиональнее заняться этим в редакторе векторных изображений. Например, в Boxy SVG с полным набором опций для работы с векторной графикой, а также встроенным редактором кода, если очень хочется поковыряться.
А теперь пришло время оптимизировать SVG-изображение с помощью svgo. Благодаря этому можно получить SVG-файл в 13 Кб вместо растрового файла формата WebP в 16 Кб. Вектор снова выиграл!
Поскольку изображения SVG — это просто текстовые файлы, они очень хорошо сжимаются. Мы ещё раз повторим: «Идеального формата не существует», но вы можете выбрать подходящий формат для вашей задачи и применить столько оптимизаций, сколько захотите.
На самом интересном месте останавливаемся — впереди вторая часть материала, в которой подробно расскажем о сжатии изображений, особенностях растровых форматов и о том, как их использовать в интернете.