BT – тулчейн баннерной разработки
Ранее я обещал рассказать о своем новом проекте, не связанном с D – выполняю обещание. Волею судеб несколько лет назад я стал профессиональным баннермейкером. Это разновидность фронтенд-разработки на стыке с анимационным дизайном – чаще всего я делаю HTML-баннеры и виджеты с разнообразной анимацией, эффектами и интерактивными механиками. Поскольку почти все рекламные сети имеют ограничение для баннеров по весу (и довольно серьезное – обычно 150 кб), эта профессия еще и пересекается в какой-то степени с демосценой, где на первом месте – искусство минимизировать информацию и генерировать ее процедурно.
Баннеры – это обычные HTML-странички, которые показывают пользователю рекламный сюжет и содержат ссылку на сайт рекламодателя. Они могут быть изготовлены при помощи самых разных инструментов, в том числе визуальных (Adobe Animate, Google Web Designer), но самое гибкое решение – писать непосредственно на HTML и JavaScript, рисуя графику либо обычными элементами DOM, либо через canvas. Благодаря отказу от Animate, вы не привязаны к его JS-библиотеке, которая сама по себе сжирает много веса, если ее нужно приложить к баннеру локально. Однако для отрисовки мало-мальски сложной анимации одним CSS вы не обойдетесь, и вам нужны такие библиотеки, как GSAP, Anime.js и др. – благо, весят они совсем немного. Основная сложность – уместить все ресурсы баннера в те самые 150 кб, что порой представляет нетривиальную задачу.
Для решения этой и многих других задач, которые возникают при разработке HTML-баннеров, я написал на Node.js комплект инструментов BT (Banner Toolchain). Его идея уходит корнями во внутренний инструментарий компании SmartHead, которым я пользовался три года и решил переделать полностью с нуля, уже в качестве независимого проекта с более эффективной реализацией большинства фич.
BT – это локальный сервер, предназначенный для разработки одностраничных сайтов с упором на минимизацию веса всех ресурсов. Например, в нем есть встроенный оптимизатор изображений, генератор CSS-анимации, функция записи баннера в GIF и видео, а также сборщик для подготовки баннера под все популярные рекламные площадки и упаковки в архив.
Dev-сервер
BT – это всего одно приложение, запускающееся командой bt run. Для работы над баннерным проектом нужно проинициализировать пустую папку одним из готовых шаблонов командой bt init. В приложении предусмотрена возможность добавлять пользовательские шаблоны (и они теоретически могут представлять собой любые наборы файлов, однако внутренние инструменты рассчитаны на работу со стандартной структурой проекта в шаблоне default).
Сервер основан на фреймворке Fastify – по-моему это один из самых быстрых серверов для Node, который еще и весьма удобен и интуитивен в использовании. Fastify обслуживает не только статические файлы баннера, но и клиентскую часть всех инструментов BT, внутренний API, а также предоставляет SSE-интерфейс для отправки на клиент уведомлений об изменениях файлов проекта.
Самый интересный из всех инструментов – страница предпросмотра баннера (http://localhost:8000/preview), через которую доступны почти все возможности BT. Тут и прогрессбар анимации, и возможность указать точные размеры страницы, как в DevTools, и сборка баннера в архив, и захват скриншотов и видео, и многое другое.
Сервер работает параллельно с запущенным Rollup – анимация и логика баннера пишутся в виде ES-модулей, которые в реальном времени бандлятся в единый скрипт. Есть также поддержка Sass и встроенный генератор CSS-анимации из выражений fromTo, похожих на вызовы GSAP – эта штука работает как кастомный плагин Rollup.
Оптимизация изображений
Понятно, что скрипты и CSS можно минифицировать – для этого существуют специальные библиотеки – да и сжимаются они очень хорошо. Но что делать с картинками, особенно PNG с альфа-каналом? ZIP-сжатие для них ничего не дает, данные в PNG уже сжаты Deflate’ом. На помощь приходит сжатие с потерями, при помощи которого можно устранить из картинки избыточную информацию так, что она будет на глаз почти неотличима от оригинала. Обычный 24-битный PNG конвертируется в 8-битный с индексированными цветами (PNG8). Этот метод уходит корнями в глубокую старину – индексированные изображения использовались в игровых консолях 80-х и 90-х. Задача сводится к квантованию цвета (color quantization) для получения палитры из заданного количества цветов и изображения, в котором вместо значений пикселей будут индексы цветов палитры.
Многим, наверное, знаком сервис TinyPNG, который оптимизирует вес полупрозрачных изображений с сохранением максимально высокого качества. BT включает аналогичный инструмент Image Optimizer, причем мое решение более универсально: есть возможность указать качество результата (количество цветов в палитре), выбрать алгоритм дизеринга и квантования, в то время как TinyPNG вообще не имеет пользовательских настроек. Поддерживаются все популярные алгоритмы дизеринга: Floyd-Steinberg, Riemersma, Stucki, Atkinson, Burkes, Sierra, Two-Sierra, Sierra Lite, Jarvis. Чаще всего я использую Atkinson в комбинации с методом квантования палитры WuQuant. Работает все это благодаря замечательной JS-библиотеке image-q, дополнительно также используется lossless-сжатие с применением чудо-утилиты ECT. В среднем Image Optimizer выдает результат, сравнимый с TinyPNG, но нередко удается выкрутить чуть более эффективное сжатие за счет уменьшения количества цветов – а в баннерах бывает так, что дорог каждый лишний килобайт. А самое главное – Image Optimizer поддерживает также JPEG, SVG и WebP, позволяя свободно конвертировать между всеми форматами. Сжатие JPEG основано на популярном кодировщике MozJPEG. Наконец, инструмент позволяет генерировать Base64-код для встраивания изображений в CSS в качестве background-image.
Сборка
Готовый баннер можно подготовить под технические требования рекламных площадок – таких, как Яндекс Директ, Display & Video 360, Adfox и многие другие. Всего поддерживается 26 базовых требований и более 100 площадок на их основе. Технические требования и ограничения площадок задаются в виде спецификаций в формате JSON, так что можно добавлять новые площадки без изменения кода сборщика.
Сборщик манипулирует всеми ресурсами баннера, может вносить изменения в структуру HTML, добавлять необходимые по ТТ элементы, мета-теги и атрибуты. Также он проверяет баннер на соответствие ограничениям спецификации – вес архива, количество файлов, поддерживаемые форматы файлов и т.д. – и выводит предупреждения в случае нарушений.
Захват
Предмет моей особой гордости – Capturer, инструмент захвата скриншотов и видео: довольно часто требуется создать для баннера картинку-заглушку или сделать на его основе видеоролик для демонстрационных целей. Эта задача решается при помощи Puppeteer, библиотеки для запуска Chromium и управления им через Node.
“Виртуальный” браузер в приложении – полезнейшая штука: можно открыть в нем любую страницу, выполнить скрипт и получить любые данные. Также Puppeteer умеет делать скриншоты, в том числе с прозрачным фоном – это бесценная фича, так как при снятии скриншотов в браузере фон будет неизбежно белый. А если есть возможность делать снимки и управлять страницей при помощи кастомного скрипта, то можно записывать и видео – это у меня делается при помощи FFmpeg с его замечательным режимом image2pipe, в котором кодировщик принимает на вход отдельные кадры-изображения через стандартный конвейерный интерфейс. Перед снятием кадра GSAP-анимация сдвигается на один кадр вперед. Процесс записи небыстрый, но зато результат получается гораздо качественнее, чем если записывать с экрана вручную – ну и, конечно, поддерживается частота 60 кадров в секунду, чего сложно добиться в большинстве бесплатных программ для скринкаста.
Планируемые возможности
Я несколько раз успешно применил BT на практике, но все еще продолжаю работать над первым стабильным релизом. У меня еще много идей и нереализованных планов: например, эмулятор событий рекламных площадок на странице предпросмотра, инструмент для работы с раскадровкой, поддержка растеризации веб-графики, поддержка вариативных шрифтов, конвертер 3D-моделей для встроенного WebGL-движка и др. Также было бы интересно оптимизировать квантование изображений, переписав image-q на D с использованием dlib – если эта идея будет реализована, то я, конечно, напишу об этом отдельную подробную статью.