Генерация SVG из изображений может использоваться для Placeholder’ов.
Я занимаюсь оптимизацией изображений и картинок для их быстрой загрузки. Одна из самых интересных областей исследования это Placeholder’ы: что показывать, когда изображение еще не загружено.
В последние дни я сталкивался с некоторыми методами загрузки, которые используют SVG, и я хотел бы описать их в этом посте.
В этом посте мы рассмотрим следующие темы:
- Обзор различных типов Placeholder’ов
- Placeholder на основе SVG (контуры, фигуры и силуэты)
- Автоматизация процесса.
Перевод выполнен при поддержке компании EDISON Software, которая профессионально занимается созданием корпоративных сайтов на WordPress, а так же дизайнит персонажей для фирменного стиля.
Обзор различных типов Placeholder’ов
В прошлом посте я писал о Placeholder’e и ленивой загрузке изображений, а также говорил об этом. Когда вы делаете ленивую загрузку изображений, неплохо подумать о том, что делать в качестве Placeholder’а, поскольку это может иметь большое влияние на восприятие пользователя. Недавно я описывал несколько вариантов:
Несколько вариантов заполнения области изображения перед его загрузкой.
Сохранение пространства для изображения пустым: в мире адаптивного дизайна это предотвращает сдвиг контента. Эти изменения компоновки плохи с точки зрения пользователя, и для представления. Браузер вынужден выполнять вычисления компоновки каждый раз, когда он извлекает размеры изображения, оставляя для него пространство.
- Placeholder: Представьте, что мы показываем изображение профиля пользователя. Мы могли бы отобразить силуэт на месте изображения. Он отображается не только при загрузке основного изображения, но также и при неудачном выполнении запроса или когда пользователь вообще не установил изображение профиля. Эти изображения, как правило, основаны на векторах, и из-за их небольшого размера являются хорошим кандидатом, который должен быть встроен.
- Solid colour: возьмите цвет из изображения и используйте его в качестве цвета фона для Placeholder’а. Это может быть доминирующим цветом, наиболее ярким… Идея состоит в том, что он основан на загружаемом изображении и должен помочь сделать переход между “без изображения” на изображение более плавным.
- Размытое изображение: также называется техникой размытия. Вы создаете малую версию изображения, а затем переходите к полной. Начальное изображение маленькое как в пикселях, так и в размерах. Для удаления артефактов изображение масштабируется и размывается. Ранее я писал об этом в этих статьях How Medium does progressive image loading, Using WebP to create tiny preview images и More examples of Progressive Image Loading.
- Оказывается, есть много других вариантов, и многие умные люди разрабатывают другие методы создания Placeholder’ов.
Один из них это использование градиентов вместо сплошных цветов. Градиенты могут создавать более точный предварительный просмотр конечного изображения с минимальными расходами (увеличение полезной нагрузки).
Использование градиентов в качестве фона. Скриншот от Gradify, который больше не в сети. Код на GitHub.
Другой метод — использование SVG на основе изображения.
Placeholder’ы на основе SVG
Мы знаем, что SVG идеально подходит для векторных изображений. В большинстве случаев мы хотим загрузить растровое изображение, поэтому вопрос заключается в том, как векторизировать изображение. Некоторые опции используют контуры, фигуры и области.
Контуры
В предыдущем посте я объяснил, как узнать контуры в изображении и создать анимацию. Моя первоначальная цель состояла в том, чтобы попытаться нарисовать области, векторизируя изображение, но я не знал, как это сделать. Я понял, что использование контуров также может быть инновационным, и я решил оживить их, создав эффект «рисования».
Codepen
Drawing images using edge detection and SVG animation
Формы
SVG также может использоваться для рисования областей из изображения без использования контуров / границ. В некотором смысле, мы будем векторизовать растровое изображение для создания Placeholder’a.
Когда-то я пытался сделать что-то подобное с треугольниками. Вы можете увидеть результат в моих беседах в CSSConf и Render Conf.
Codepen выше является доказательством концепции Placeholder’ов на основе SVG, состоящий из 245 треугольников. Генерация треугольников основана на триангуляции Делоне с использованием polyserver Поссана. Как и ожидалось, чем больше треугольников использует SVG, тем больше размер файла.
Primitive и SQIP, метод LQIP на основе SVG
Тобиас Балдауф работает над другим методом низкого качества изображения, используя SVG, называемый SQIP. Прежде чем копаться в SQIP, я дам обзор Primitive, библиотеки, на которой основан SQIP.
Primitive довольно увлекательный, и я определенно рекомендую вам его попробовать. Он преобразует растровое изображение в SVG, состоящий из перекрывающихся фигур. Его небольшие размеры делают его подходящим для наложения его прямо на страницу. Еще один хороший вариант, а также значимый placeholder в начальной загрузки HTML.
Primitive генерирует изображение, основанное на таких фигурах, как треугольники, прямоугольники и круги (и несколько других). На каждом шаге он добавляет новую. Чем больше шагов, тем результирующее изображение выглядит ближе к исходному. Если у вас на выходе SVG, это также означает, что размер выходного кода будет больше.
Чтобы понять, как работает Primitive, я провел его через пару изображений. Я сгенерировал SVG для художественной работы, используя 10 фигур и 100 фигур:
к картинкам(Обработка этого изображения с помощью Primitive, используя 10 Фигур и 100 фигур.)
При использовании 10 фигур в изображении мы начинаем понимать исходное изображение. В контексте placeholder’a изображений существует возможность использовать этот SVG в качестве placeholder’a. На самом деле, код для SVG с 10 фигурами очень мал, около 1030 байт, который снижается до ~ 640 байт при передаче вывода через SVGO.
<path fill=”#817c70" d=”M0 0h1024v1024H0z”/><path fill=”#03020f” d=”M178 994l580 92L402–62"/><path fill=”#f2e2ba” d=”M638 894L614 6l472 440"/><path fill=”#fff8be” d=”M-62 854h300L138–62"/><path fill=”#76c2d9" d=”M410–62L154 530–62 38"/><path fill=”#62b4cf” d=”M1086–2L498–30l484 508"/><path fill=”#010412" d=”M430–2l196 52–76 356"/><path fill=”#eb7d3f” d=”M598 594l488–32–308 520"/><path fill=”#080a18" d=”M198 418l32 304 116–448"/><path fill=”#3f201d” d=”M1086 1062l-344–52 248–148"/><path fill=”#ebd29f” d=”M630 658l-60–372 516 320"/>
Изображения, сгенерированные с 100 фигурами, больше, как и ожидалось, весом ~ 5 КБ после SVGO (ранее 8КБ). Они имеют большой уровень детализации с небольшой полезной нагрузкой. Решение о том, сколько треугольников использовать, будет во многом зависеть от типа изображения (например, контраста, количества цветов, сложности) и уровня детализации.
Можно было бы создать скрипт, похожий на cpeg-dssim, который изменяет количество используемых фигур до тех пор, пока не будет достигнут порог структурного подобия (или максимального количества фигур в худшем случае).
Эти SVG также отлично подходят для использования в качестве фоновых изображений. Будучи ограниченным по размеру и основанным на векторе, они являются хорошим кандидатом для изображений героев и больших бекграундов, которые в противном случае показывали бы артефакты.
SQIP
По словам Тобиаса:
SQIP-это попытка найти баланс между этими двумя крайностями: использование Primitive для генерации SVG, состоящего из нескольких простых фигур, которые приближают основные объекты, видимые внутри изображения, оптимизирует SVG с помощью SVGO и добавляет к нему фильтр Gaussian Blur. Это производит SVG placeholder, который весит всего ~800-1000 байт, выглядит гладко на всех экранах и обеспечивает визуальную реплику содержимого изображения.
Результат аналогичен использованию крошечного изображения Placeholder’a для технологии размытия (что делают Medium и другие сайты). Разница заключается в том, что вместо использования растрового изображения, например JPG или WebP, Placeholder является SVG.
Если мы запустим SQIP против исходных изображений, мы получим следующее:
Выходные изображения с использованием SQIP для первого изображения и второго.
Выходной SVG составляет ~ 900 байт, и, проверяя код, мы можем обнаружить фильтр
feGaussianBlur
, применяемый к группе фигур:SQIP также может выводить тег изображения с содержимым SVG Base 64 encoded:
<img width="640" height="640" src="example.jpg” alt="Add descriptive alt text" style="background-size: cover; background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAw…<stripped base 64>…PjwvZz48L3N2Zz4=);">
Силуэты
Мы просто посмотрели на использование SVG для контуров и примитивных фигур. Другая возможность заключается в векторизации изображений, «прорисовывая» их. Несколько дней назад Mikael Ainalem поделился codepen’он, указав, как использовать 2-цветный силуэт в качестве placeholder’a. Результат очень красивый:
SVG в этом случае были нарисованы вручную, но техника быстро породила интеграцию с инструментами для автоматизации процесса.
- Gatsby, статический генератор сайта, использующий React, поддерживает эти прорисованные SVG сейчас. Он использует JS PORT potrace для векторизации изображений.
- Craft 3 CMS, который также добавил поддержку силуэтов. Он использует PHP Port of potrace.
- image-trace-loader, загрузчик Webpack, который использует potrace для обработки изображений.
Также интересно посмотреть сравнение вывода между загрузчиком Webpack от Emil (основано на potrace) и отрисованными вручную SVG от Mikael.
Я предполагаю, что вывод, генерируемый potrace, использует параметры по умолчанию. Однако их можно настроить. Проверьте параметры для Image-trace-loader, которые в основном передаются в potrace.
Итого
Мы рассмотрели различные инструменты и методы для генерации SVG из изображений и использования их в качестве placeholder’a. Также WebP — фантастический формат для эскизов, SVG также интересный формат для использования в placeholder’ах. Мы можем контролировать уровень детализации (и, следовательно, размер), он очень сжимается и легко управляется с помощью CSS и JS.
Дополнительные ресурсы
- Geometrize — это порт Primitive, написанный на Haxe. Существует также реализация на JS, которую вы можете попробовать прямо в своем браузере.
- Primitive.js, который является портом Primitive в JS. Кроме того, primitive.nextgen, который представляет собой порт приложения Primitive для настольных компьютеров с использованием Primitive.js и Electron.
- Есть несколько аккаунтов Twitter, где вы можете увидеть примеры изображений, созданных с помощью примитива и геометрии. Посмотрите @PrimitivePic и @Geometrizer.
- imagetracerjs, который является трассировщиком растрового изображения и векторизатором, написанным на JavaScript. Есть также порты для Java и Android.
Комментарии (40)
evnuh
21.11.2017 16:29Как по мне, так вариант с простым векторным svg без гаусса лучше всего смотрится в роли плейсхолдера.
А что-нибудь известно по ресурсам, требуемым для работы primitive и генерации простейшего svg из 10 фигур? Быстрее оно, чем генерация миниатюр через imagemagick?stychos
21.11.2017 17:18Тут вопрос скорее об объёмах хранилища. Не думаю, что быстрее, это ведь надо не только прочесть картинку, но и выполнить ряд триангуляций.
evnuh
21.11.2017 17:30+1Вот потому и вопрос. По ощущениям Делоне явно сложнее, чем свёртка по пикселям, но всё может решить реализация. Primitive на Go vs imagemagick на C.
А по размеру SVGO явно получше будет с <1kb против нынешних прейсхолдеров в 2kb. Не бесплатно же нам даётся эта экономия места, скорее всего в обмен на ЦПУ.
ExplosiveZ
21.11.2017 16:42+1Я уж думал, что у меня бразуер сломался. Твиты в виде картинок оказались слишком жестокими для меня.
stychos
21.11.2017 17:20Я вот всё никак не пойму - как этим твиттером пользоваться?
Stalker_RED
21.11.2017 18:56+2Если очень коротко, там можно писать сообщения и читать их. Подписываетесь на людей или темы которые вам интересны, и читаете.
JTG
21.11.2017 17:32+4+1 в копилку способов нагрузить браузер бесполезной работой.
edge790
21.11.2017 18:13+1можно же и на бэке сделать. Поставить чтобы аватарку нельзя было менять чаще чем раз в 5(10,20, 100500) раз в минуту или делать плейсхолдеры, только когда она "продержалась" 1(2,3,7, 14) дней.
Отправлять svg гораздо дешевлеTimsTims
21.11.2017 18:54+5Он имел ввиду про то, что отрисовка SVG относительно трудозатратная задача, в отличии от тупого вывода jpeg (как ни странно).
edge790
21.11.2017 19:16+1Действительно, я какую-то глупость написал. Думал что под бесполезной работой подразумевается "генерация svg на фронте", но только сейчас понял что для этого нам нужно само изображение "=.=
По поводу отображения svg vs jpeg — это стандартная дилемма: память vs вычисления.
С одной стороны у большинства пользователей уже есть быстрый домашний и (иногда) мобильный интернет, в кафе и торговых центрах есть Wi-Fi и загрузить лишнюю маленькую jpeg не составит труда, но с другой стороны браузеру итак есть что грузить, а тут ещё "лишние" jpeg файлы, пускай и небольшого размера.
С другой стороны вычислительные мощности бОльшей части устройств смогут справиться с выводом 10 фигур, что будет очень кстати для пользователей мобильных устройств.
Да это решение — не панацея, но оно имеет свои плюсы (например стилизация соцсети/сервиса). Думаю особенно красиво это смотрелось бы с специальной задержкой перед показом картинки и последовательным превращением фигур в фотографию.
В остальных случаях, как и сказал TimsTims можно использовать прогрессивный JPEG
DistortNeo
21.11.2017 23:19+1А что мешает в качестве плейсхолдеров использовать те же инлайнутые JPG или PNG?
TimsTims
21.11.2017 18:55+4Кажется, автор заново изобретает велосипед: есть ведь прогрессивная загрузка JPEG, которой почти никто не пользуется…
SHVV
21.11.2017 22:12На сколько я понял, основной упор делается на то, что плейсхолдеры можно встроить непосредственно в саму страницу, и они отобразятся сразу, до того как браузер начнёт грузить реальные картинки. В случае с Progressive JPEG, лаг между пустым местом и хоть чем-то напоминающим картинку будет несколько выше.
TimsTims
22.11.2017 09:37В случае с Progressive JPEG, лаг между пустым местом и хоть чем-то напоминающим картинку будет несколько выше.
Отнимаем несколько десятых секунды на отрисовку SVG, иэ тот лаг уже компенсируется при «нормальном» интернете. Даже более того, небольшое зависание браузера и компьютера (100% CPU) перевешивает чашу весов в пользу JPEG.
SVG из статьи выигрывает только когда у пользователя очень плохой канал интернета(или ваш сайт очень жирный), и ему загрузить 100% CPU на пару секунд выгоднее, чем ждать +60 секунд на полную прогрузку.
amarao
21.11.2017 19:21+21. 1-4к — этого вполне достаточно для jpeg-превьюшки терпимого качества.
2. Большая часть этого svg — текст, который можно было бы просто закодировать как координаты, прозрачности и цвета для треугольников. Если каждый треугольник описывается 6 u16 и цветом, то это 16 байт на треугольник. 100 треугольников — 1600 байт. Если же обнаглеть и разрешить огрублять позции треугольников до байта, то это 10 байт на треугольник, 100 треугольников — 1кб без сжатия.
Но у меня вопрос: если на странице 30 превьюшек, то сколько мегафлопов будет сожрано у пользовательской батарейки в процессе «предрендеринга»?edge790
21.11.2017 20:17+2Меня беспокоил такой же вопрос.
Решил проверить, сделал примитивный тест на codepen, который просто создает svg с n количеством треугольников.
Браузер на компьютере подвисает на 3-4 секунды от 100 000 треугольников, после чего функционирует нормально.
У телефона такие же проблемы начинаются с 10 000 треугольников (Xiaomi Redmi 3S)amarao
21.11.2017 20:19+4Итого: 30 превьюшек по 100 треугольников делают нам 3000 треугольников. То есть примерно секунду «подвисания» браузера мобильного телефона или 0.3с десктопа (100% CPU, насколько я понимаю).
Я бы сказал, что дофига.
ad1Dima
22.11.2017 09:34Ламера вопрос: а если ту же свгшку готовым файлом вставить, а не добавлять треугольники в цикле по одному, разве быстрее не будет?
edge790
24.11.2017 14:54Хороший вопрос.
Первая моя мысль мысль: выгода была бы такой, что ей можно было бы пренебречь, потому что цикл из 10 000 элементов проходится относительно легко.
Вторая что JS страдает при каждом добавлении нового элемента в DOM, поэтому выгода могла быть большой.
Третья: готовым файлом — это как?ad1Dima
24.11.2017 14:57Третья: готовым файлом — это как?
placeholder.svg
Не знаю я, как это в вебе правильно делать. Вероятно как-то так habrahabr.ru/company/edison/blog/342848/?reply_to=10536428#comment_10531650
DistortNeo
24.11.2017 15:06Вторая что JS страдает при каждом добавлении нового элемента в DOM, поэтому выгода могла быть большой.
По-моему, это было актуально 2-3 года назад.
Сейчас что добавление нового элемента в DOM, что генерация одним большим куском работают одинаково быстро.
MrShoor
22.11.2017 02:35Но у меня вопрос: если на странице 30 превьюшек, то сколько мегафлопов будет сожрано у пользовательской батарейки в процессе «предрендеринга»?
Тут кстати есть интересный момент. Если мы знаем, что наша svg содержит только одноцветные треугольники, то отрисовать это с помощью GPU расплюнуть. Поэтому если заморочится и написать JS рисующий через WebGL запросто можно получить ощутимый профит, и не сильно жечь батарею.
radist2s
21.11.2017 20:16+2Кстати, для вывода
data-uri
SVG изображений можно не кодировать в Base64, достаточно закодировать SVG в виде Url, то есть прогнать через какой-нибудьhttpencode
<img style="background: url(data:image/svg+xml;utf8,[svg_http_encoded]">
Это позволит во-первых, немного уменьшить размер текста, во-вторых, браузеру не нужно будет выполнять декодирование base64, что может быть медленным, в-третьих, это лучше сжимается gzip'ом. Если кодировка не важна, можно ее опустить:
data:image/svg+xml,[svg_http_encoded]
lenar
21.11.2017 21:33Насколько я помню, в IE11 есть странный глюк: если не кодировать в base64, это не будет работать.
radist2s
22.11.2017 14:26Работает вплоть до IE9, было бы круто ели бы вы показали пример если что-то действительно не работает, чтобы не наступить на грабли, если что.
Aingis
22.11.2017 14:47Для IE11 надо экранировать больше символов, включая „<“ и „>“. (Но меньше чем стандартная url-кодировка.) Например, так делает URL-encoder for SVG авторства @yoksel. См. мой пулл-реквест в кодировщик с перечнем экранируемых символов. Там же используется трюк с тем, что можно не экранировать пробелы, если заключить URL в кавычки.
P.S. Кусок «;utf8
», кстати, лишний. Я вообще не нашёл способа изменить кодировку в data-url'е.
Psychosynthesis
22.11.2017 02:48А разве нет ограничения на размер URL?
radist2s
22.11.2017 14:35+1А какая разница, мы
httpencoded
засунем внутрь url() или base64? Но, в целом, ограничение на размер data:URL было у IE8(32кб) и какой-то старой Opera.Psychosynthesis
22.11.2017 16:41Извините, я сейчас не так пристально слежу за темой веб-разработки, не знал что это уже не актуально.
theonlymirage
21.11.2017 22:29Статья интересная, но на превьюшке для затравки алгоритм отработал странно. Вместо ожидаемого одного треугольника, видим два.
Кого это заинтересует + найдётся время изучить предложенные алгоритмы генерации (с текущими исходниками) — думаю там есть что улучшить и написать хорошую статью.
Psychosynthesis
22.11.2017 02:51Я правильно понимаю что вот этот пример: codepen.io/jmperez/pen/oogqdp
На нативном JS написан?Stalker_RED
22.11.2017 08:56А что, есть много других js-ов?
Psychosynthesis
22.11.2017 16:39Вы прям как эталонный хабровчанин отвечаете.
Я таки думал очевидно, что вопрос про фреймворки и библиотеки, ибо я не знаток Codepen, ведь вполне было возможно что там где-то в настройках используемые библиотеки подключаются. Я бегло посмотрел код, не нашёл ничего из того же jQuery, но всё же решил уточнить.
dom1n1k
22.11.2017 12:52+1Заливка доминирующим цветом — абсолютно бесполезная ерунда.
SVG-картинка из треугольников — теоретически интересно, практически смысла ноль. Они пишут, что 100 треугольников после оптимизации весят около 5 кб. Я прикинул: аналогичный вес имеет PNG-картинка размером порядка 100-120px по каждой стороне, которая дает отличное представление об изображении и намного меньше ест ресурсы. Вообще говоря, 100px для LQIP — это сильно избыточно, обычно берут что-то в пределах 20-50px.
SVG-силуэты — с сугубо утилитарной точки зрения тоже очень спорно, но тут можно совместить практичность и эстетику, создав интересный дизайнерский эффект. Но опять же не универсально.
Мой выбор — банальный растровый LQIP весом не более 1 кб, причем даже без блура.
sutarmin
Статья отличная, за перевод спасибо. Но твиты картинками… За что вы так с нами? :)