
Поделюсь с вами необычным опытом разработки упаковщика проекта с большой анимационной сценой в один независимый HTML файл, который может воспроизводиться в любом браузере без интернета и веб-сервера.
Вводная
Несколько лет назад в моей прошлой статье на Хабре я рассказывал о создании своего собственного видео формата, который заменил в моем проекте mp4 и позволил повысить качество рендера анимации и при этом не сильно потерять в размере. С тех пор проект прилично подрос, и сейчас вся анимация весит ~150МБ в этом видео формате.

Теперь появилась амбициозная задача - дать пользователям возможность скачать весь проект целиком как анимацию, которая не будет никак зависеть ни от веб-сервера, ни от интернета в целом. Единственная необходимое требование - наличие любого современного браузера, который может открыть файл с локального диска.
Задача
Встает вопрос: как дать пользователям возможность скачать весь проект так, чтобы его можно было воспроизвести почти на любом устройстве? Простого варианта попросту нет, так как вся анимация - это не один файл, это множество различных файлов, подключающихся по мере просмотра сцены, для рендера которых нужен движок JS с поддержкой отдельных потоков (Web Workers) и ряд современных Web APIs. Конечно, можно все упаковать в исполняемые файлы со встроенным браузером, блэкджеком и… Но нет, этот путь тернист, избыточен, да и скачивать исполняемую программу слегка небезопасно от noname производителя.
Раз этот проект является веб-страницей, в которой все подгружается по http, то в конечном итоге нужно все файлы проекта встроить в один html, который может быть воспроизведен на практически любом современном устройстве. Но, в проекте на данный момент 712 различных файлов необходимых для рендера анимации, а их суммарный размер - 168МБ! При этом файлы каждую неделю добавляются новые и суммарный размер проекта постоянно растет. Встроить все это в один HTML файл и при этом не подвесить браузер при его открытии - нетривиальная задача, и далее расскажу как её решил.
Как встроить бинарные файлы в HTML
Думаю большинство читателей знакомы с схемой data-uri, которая часто применяется для встраивания различного бинарного содержимого прямо в код html. Если не знакомы, то коротко расскажу в чем её суть: вместо URL к определенному ресурсу (например, изображению) мы в поле адреса вставляем само содержимое файла, закодированное с помощью Base64 в алфавит, состоящий из безопасных для HTML символов ASCII. Таким образом браузер не делает HTTP запрос к ресурсу, а загружает его из данных, которые есть в HTML.
Т.е. способ как встроить бинарные данные в HTML давно изобретен - это т.н. Binary-To-Text кодировки, которые превращают наш файл в кусок текста, валидный для HTML парсеров. Base64 - это наиболее популярная bin-to-text кодировка в мире IT, поддерживаемая из коробки практически во всех современных языках. Однако, её эффективность составляет лишь 75%, т.е. условно для кодирования 168MБ потребуется строка из 223МБ символов разрешенных в HTML. Многовато…
Рассмотрим другие bin-to-text кодировки, менее популярные, но имеющие бОльшую эффективность.
Одна из самых интересных - это Ascii85/Base85, прекрасная и элегантная кодировка для веба с эффективностью 80%, которая строится на простой математике: для кодирования 4 байт требуется 5 байт с алфавитом состоящем из 85 символов ASCII, так как 232 < 855. Однако, 80% не сильно больше 75%, для наших 168MБ потребуется 210МБ, это все ещё очень много.
Далее рассмотрим Base122. Малоизвестная кодировка, которую сам автор не рекомендует применять в HTML, тем не менее у неё достаточно высокая эффективность - 87.5%. Суть этой кодировки в том, что она упаковывает бинарные данные в структуру UTF-8 октетов. Мне эта тема показалось очень интересной, поэтому я глубоко изучил как сам UTF-8 устроен, так и то, как эта кодировка упаковывает в него.
Для кодирования одного символа UTF-8 использует от 1 до 4 байт, зависит это от того, что за символ. Если символ входит в ASCII, то для его кодирования нужен 1 байт (первый бит всегда будет 0), а для кодирования русского текста нужны 2 байта на каждый символ.
На следующем изображении показана структура байт UTF-8 для кодирования одного символа:

Суть bin-2-text кодирования через UTF-8 - это использовать свободные биты (зеленые), в которые мы сможем перенести биты из исходных данных. Однако, эмпирическим путем обнаружил, что нельзя просто так использовать все зеленые биты, так как в UTF-8 есть диапазоны, которые запрещено использовать и текст будет невалидным, если в нем появятся символы из этих диапазонов.
В итоге оказалось, что для более-менее безопасного кодирования бинарных данных в UTF-8, когда читалка HTML с высокой вероятностью сможет валидно все распознать, эффективность этой кодировки становится около 80-83%, а в некоторых тестах и ниже (все зависит от самих данных). Поэтому Base122 и её вариации не подходят из-за нестабильной эффективности и риска получить невалидный для парсера текст.
Более эффективных кодировок я не нашел, поэтому решил попробовать отойти от общепринятых правил и придумать что-то свое.
Разработка своей Binary-to-Text кодировки
Основная проблема всех bin-to-text кодировок - это размер алфавита: чем он больше, тем выше эффективность кодирования. В ASCII, который состоит из 128 символов, безопасно в HTML можно использовать только 96 символов, а остальное - это различные контрольные символы и символы самого HTML. Т.е. максимальный алфавит для безопасного кодирования - 96. Ближайшее оптимальное соотношение количества бит на вход и количество на выход при таком алфавите будет следующим: на 46 бит (5.75 байт) входных бинарных данных нужно 56 бит (ровно 7 байт) кодированных. Т.е. эффективность 82.1%. Это немного лучше, чем у Base85, но усложняется код, так как нужно работать дробным числом байт. Тем не менее результат есть, назовем эту кодировку Base96 в рамках этой статьи.
Вариантов не остается, нужно как-то увеличивать размер алфавита. Но как это сделать, ведь HTML и JS у нас на юникоде, а в нем только 96 символов можно! И тут на помощь приходит старый друг, за упоминание которого многие меня закидают помидорами - кодировка Windows-1251! Да-да, та самая русская кодировка, которая когда-то использовалась как в DOS, так и Windows, так и в вебе. К слову, её до сих пор используют в вебе крупные русские площадки, к примеру vk.com и pikabu.ru.
HTML5 настоятельно рекомендует использовать только UTF-8 кодировку. Это полностью правильная рекомендация и я её поддерживаю. Тем не менее, в спецификации HTML5 черным по белому написано, что браузеры должны поддерживать Windows-1251, поэтому хоть это и не рекомендуется, но использовать эту кодировку в HTML документах никем не запрещается.
В моем проекте используется только русский текст и английский, так что Windows-1251 подходит на все 100%. Все специальные символы юникода, которые кое-где могут использоваться в проекте, без проблем выводятся, так как их вывод делает JS, а он независимо от кодировки документа работает с юникодом (браузер автоматически конвертирует кодировку документа html в кодировку, необходимую для работы js).
Итак, что нам дает эта кодировка? Так как она однобайтовая, то мы уже не привязаны с ASCII из 128 символов, в которых только 96 валидные. Теперь мы можем использовать ANSI из 256 символов, в которой 223 валидных для HTML символа!

Ближайшее хорошее соотношение бит на вход и выход будут следующими: на 39 бит оригинальных данных требуется 40 бит кодировки. Т.е. теряем всего 1 бит из 5 байт! Эффективность становится рекордные 97.5%! С такой эффективностью 168МБ бинарных данных могут быть безопасно добавлены в HTML как 172МБ, т.е. потеря примерно 2.3%!
Назовем это кодировку Base223* в рамках статьи. Звездочка в названии как бы указывает на то, что для кодировки нужны особые условия, и в частности это кодировка самого документа HTML.
Алгоритм кодирования в Base223* следующий:
Читаем следующие 5 байт из входного потока;
Если прочитали меньше 5 байт, то добавляем справа недостающее число байт с кодом 0x00. Запоминаем сколько добавили байт в переменную P;
Из прочитанных 5 байт (40 бит) извлекаем левые 39 бит.
Представляем эти 39 бит как беззнаковый int64.
-
Поэтапно делим число на 223n с округлением вниз, где n меняется от 4 до 0. В каждом последующем этапе отнимаем от числа накопленные вычисления предыдущих этапов. В итоге получаем 5 чисел от 0 до 223. Пример вычисления, где x - это исходное число из п.4:
c0 = ⌊x / 2234⌋; acc = c0 * 2234
c1 = ⌊(x - acc) / 2233⌋; acc = acc + c1 * 2233
c2 = ⌊(x - acc) / 2232⌋; acc = acc + c2 * 2232
c3 = ⌊(x - acc) / 2231⌋; acc = acc + c3 * 2231
c4 = ⌊(x - acc) / 2230⌋;
Полученные 5 чисел переводим в нужные символы по алфавиту (алфавит - это заранее подготовленный массив из 223 символов).
Остаточный левый бит из прочитанных исходных 5 байт заносим в буфер.
Если в буфере накоплено 39 бит или это конец входного потока данных, то представляем буфер как число из 39 бит кодируем его в текст как описано в п.5.
Если достигнут конец входного потока, то добавляем в конец кодированного текста 1 символ из алфавита, порядковый номер которого записан в переменной P (значение сколько мы добавили вспомогательных байт, чтобы прочитать сегмент длиной 5 байт).
Для лучшего понимания как работает описанный выше алгоритм на следующей анимированной диаграмме показан процесс кодирования небольшой строки:

Пример реализации алгоритма можно глянуть в следующем JS коде.
В реализации на JS столкнулся с проблемой производительности. Так как кодировка требует работы с 64-битными числами, то в JS для этого потребовалось использовать BigInt. К моему удивлению, банально любая операция с BigInt в 10, а то и больше раз медленнее работает, чем такая же операция с 32-битными числами. Поэтому пришлось в алгоритме декодирования максимально убирать все операции с BigIng и стараться все вычислять на обычном 32-битном Number. Операцию кодирования не трогал, так как я её реализовал на JS чисто для теста.
На следующем графике показана зависимость эффективности кодирования от длины входных данных. Эффективность 97% достигается уже на 550 байтах, 97.45% на 2000 байт, 97.49 на 9750 байтах.

Временная сложность алгоритма - линейная O(n), т.е. время на кодирование одного байта одинаково для любой длины данных.
Сведем в одну таблицу все результаты исследования Binary-To-Text кодировок.
Кодировка |
Эффективность |
Плюсы |
Минусы |
Base64 |
75% |
Очень быстрая за счет нативных функций практически во всех языках. Безопасная для HTML. |
Низкая эффективность |
Ascii85/Base85 |
80% |
Относительно быстрая, так как работает с 32-разрядными числами, и не требует обработки дробных байт |
Относительно низкая эффективность |
Base96 |
~82.1% |
Относительно хорошая эффективность |
Сложность в реализации, особенно на JS из-за обильной работы с BigInt (int64) |
Base122 |
80-87.5% |
Высокая эффективность |
Плавающая эффективность и небезопасная для HTML. Высокий риск словить неожиданные проблемы с невозможностью декодирования данных |
Base223 |
~97.5% |
Наивысшая эффективность |
Требует использование Windows-1251 для закодированных данных |
Строим огромный HTML документ
С тем, как перенести бинарные файлы в HTML, разобрались. Теперь вопрос как организовать структуру документа, где и как будут располагаться файлы, в каком типе узла дерева документа (элементе, комментарии, текстовом узле и тд), в какой момент их извлекать и где хранить извлеченные данные, в памяти или где-то ещё?
Вопросов много, разберем их по порядку.
Хранение файла в структуре документа
Как уже выше упоминалось, в структуре документа необходимо хранить около 712 файлов, суммарный размер которых в уже закодированном виде составляет примерно 172МБ. Объем данных достаточно большой и важно, чтобы браузер не пытался эти данные использовать для отображения на странице, т.е. эти данные не должны участвовать в reflow, repaint и composite операциях браузера. И для этого есть 2 подходящих узла дерева документа (DOM):
Комментарий (
<!-- …. -->)Тег скрипта с указанием типа application/octet-stream (
<script>)
Изначально я думал все делать на комментариях, но отказался по причине достаточно строгих правил их наполнения. Для соблюдения этих правил потребовалось бы в алгоритме кодирования и декодирования добавлять логику исключения определенных последовательностей символов, либо сокращать размер алфавита на 2 символа > и -.
Поэтому выбор остановился на теге <script>. Согласно HTML спецификации он подходит для задачи, так как его можно применять для хранения различных данных, не только javascript. Внутри тега script действуют особые правила парсинга, которые допускают использовать практически любое текстовое наполнение без экранирования спецсимволов. Единственно, что нельзя допускать - это наличие в данных последовательности символов тега закрытия скрипта - </script>.
В алфавите кодирования Base223* уже исключен спецсимвол <, поэтому уменьшать алфавит не нужно, и менять логику кодировщика тоже не нужно. Идеально!
В data-атрибуты тега добавим дополнительную информацию: путь файла относительно корня сайта, и алгоритм сжатия (gzip или без сжатия).
Пример тега script, в котором упакован текстовый файл с единственной строкой “Привет мир!” в UTF-8 кодировке:
<script type="application/octet-stream"
data-file="/misc/hello-world.txt"
data-compression="raw">ЦDжО?БЋ_ЅDЦс0FЇЦWТсІe‰оcY </script>
Чтение файлов из структуры документа
Очень важно сделать загрузку документа быстрой, и при этом стараться не держать весь объем декодированных файлов в оперативной памяти.
Распарсить 170+ МБ HTML данных не простая задача даже для быстрых браузеров, но все сильно усложняется, если помимо работы парсера браузера нужно ещё и декодировать Base223*.
Также оставлять в DOM множество script элементов с большим содержимым опасно, это может где-то, да сказаться на скорости работы DOM или CSSOM.
Учитывая все вышеизложенные опасения пришел к следующей схеме процесса загрузки файлов:
После того, как весь HTML был распаршен запускается поиск всех script элементов, содержащих закодированные файлы.
Содержимое каждого такого скрипта без декодирования переносится в IndexedDb - мощное хранилище, как раз идеально подходящее для хранения файлов.
Тег скрипта удаляется из DOM.
-
При запросе файла его данные выгружаются из БД и выполняется проверка:
Если данные в виде строки, то она декодируются, если необходимо, то распаковывается (gzip) в массив байт Uint8Array и затем обратно записываются в БД уже в виде массива распакованных байт;
Если данные в виде массива байт, то никакой дополнительной обработки не делается.

Таким образом после загрузки документа все файлы перемещаются в сыром виде в IndexedDB. По мере необходимость файл зачитывается из БД и если он ещё не дешифрован/распакован, то выполняется дешифрация (+распаковка gzip) и обратно записывается в БД.
Как результат DOM очищен от ненужных элементов, файлы хранятся не в памяти JS, и декодирование делается ленивым способом.
Boot-скрипт и прогресс загрузки
Теперь у нас есть встроенные файлы в HTML, мы знаем как и когда их читать из DOM, осталось обработать этап загрузки самого HTML, так как браузеру нужно какое-то время на чтение данных с диска и парсинг всего в DOM. Т.е. нужно показать пользователю этап загрузки документа в виде прогресс-бара. Также нам нужно удостовериться, что документ открывается в верной кодировке, иначе ничего работать не будет, и выполнить проверки наличия у браузера необходимых API для работы рендера анимации и самого интерфейса.
Для выполнения перечисленных требований появляется необходимость добавить boot-сектор в документ, который будет содержать незакодированный легковесный JavaScript код, выполняющий все необходимые проверки и подготовки документа.
Загрузочный js будет располагаться сразу после тега <meta>, где указывается кодировка документа. У браузеров очень сложный механизм определения в какой кодировке читать документ. В этом механизме учитываются и заголовки ответа сервера, и наличие BOM-маркера в начале документа, и наличие различных meta тегов, указывающих кодировку, и ряд других проверок. У них у всех разный приоритет влияния и порядок выполнения. Увы, в некоторых случаях браузер будет игнорировать наличие мета тега <meta charset="windows-1251"> в шапке документа. Например, если этот html файл попытаться отдать с сервера, который отправит заголовок Content-Type: text/html; charset=utf-8. Поэтому для обработки такой проблемы наш boot-скрипт будет пытаться расшифровать CSS файл, который также находится в head блоке. Если декодирование провалилось, то загрузка всего документа игнорируется и на экране выводит следующее сообщение.

Далее boot-скрипт проверяет, что браузер пользователя поддерживает все необходимые для работы проекта API и технологии. Для этого выполняется небольшой JS, в котором тестируются различные современные селекторы и проверяется наличие в JS определенных классов. Пример кода такой проверки:
function testBrowser() {
try {
res = CSS.supports('color: color-mix(in srgb, #ff00ff, transparent 50%)')
&& CSS.supports('selector(& .foo)')
&& CSS.supports('selector(a:is(.b, .c))')
&& CSS.supports('selector(a:has(.b))')
&& CSS.supports('mix-blend-mode: hard-light')
&& CSS.supports('background-clip: padding-box')
&& ('DecompressionStream' in window)
&& ('attachInternals' in document.documentElement)
&& ('replaceAll' in (new String('')))
&& ('endsWith' in (new String('')))
&& eval('/<template((?!<template).)+?<\\/template>/ms') instanceof RegExp;
} catch (e) {
return false;
}
return res;
}
Если проверка будет провалена, то пользователь увидит соответствующее сообщение о невозможности открыть страницу в этой версии браузера.
Далее boot-скрипт вставляет в блок <head> ранее распакованный основной CSS проекта и дешифрует + распаковывает основной JS проекта (около 2.2МБ в распакованном виде). Этот JS будет добавлен в документ в самом конце, когда браузер сообщит, что он закончил парсить html.
Финальный этап работы boot-сектора - регистрация функции, которая будет обновлять значение в прогресс баре. Эта функция будет вызываться небольшими script элементами, которые расположены после каждого файла в структуре HTML.
<script type="application/octet-stream" data-file="/fin/1709142125-1-0.f796.br"
data-compression="raw">....</script>
<script>embeddedProg.updateLoader("/fin/1709142125-1-0.f796.br", 553265);</script>
<script type="application/octet-stream" data-file="/fin/1669618724-1-1.f796.br"
data-compression="raw">....</script>
<script>embeddedProg.updateLoader("/fin/1669618724-1-1.f796.br", 1073930);</script>
<script type="application/octet-stream" data-file="/interactive/chuck/punch.mp3"
data-compression="raw">....</script>
<script>embeddedProg.updateLoader("/interactive/chuck/punch.mp3", 23636);</script>
Таким образом по мере парсинга HTML браузер будет периодически прерываться на короткое время, чтобы выполнить js, обновляющий значение прогресc-бара.
Процесс загрузки документа с обновление прогресс бара показан на следующей гифке:

Заключение
Упаковать большой сайт в один файл html можно почти не потеряв в размере, если правильно подойти к вопросу выбора Binary-To-Text кодировки. В каких-то случаях, если сайт небольшой, достаточно будет и base64 или ascii85, но если сайт имеет большие файлы, то стоит рассмотреть нестандартные кодировки, которые могут значительно увеличить размер алфавита для шифрования. В моей случае хорошо подошла Windows-1251. А вот пытаться использовать UTF-8 для кодирования бинарных данных не стоит, так как либо вы получите надежный, но неэффективный алгоритм, либо эффективный, но ненадежный, как Base122.
Комментарии (50)

radtie
09.10.2025 07:20А вы не рассматривали хранение контента после загрузки документа в ObjectUrl вместо IndexDB?
ArrayBuffer > Blob > URL.createObjectURL
На первый взгляд это выглядит разумным, но может столкнулись с какими то ограничениями?
horpia Автор
09.10.2025 07:20У меня гибридно. В IndexedDB я первоначально сохраняю, а далее в завимости от того, куда нужны данные выгружаю их в один из следующий форматов:
в ArrayBuffer, если данные нужны в бинарной форме. В частности сама анимация рисуется через JS, поэтому мне нужен массив байт для работы рендера.
в string, если это текстовые данные (css, js, json и тд);
в ObjectUrl через createObjectURL , если мне просто нужна ссылка на файл, чтобы отобразить в img теге или загрузить в Audio объект.

vybo
09.10.2025 07:20
А где тут 96 ASCII-символов, 95 же только получается?

horpia Автор
09.10.2025 07:20В варианте кодировки с 96 символами я использовал алфавит, где был либо символ
<, либо символ переноса строки. Оба эти символа допустимо использовать в блоке script. Если их оба использовать, то алфавит будет аж 97! Но все же для защиты от случайного закрытия тега скрипта, символ<нужно убрать.
Overphase
09.10.2025 07:20а символ 0x7F?

horpia Автор
09.10.2025 07:20Он запрещен в HTML во всех кодировках совместимых с ASCII.
Т.е. документ будет не валидным, если будет присутствовать такой символ что в utf8, что в cp1251

Overphase
09.10.2025 07:20Да нет. Я в контексте вопроса выше от @vybo У вас в статье написано
безопасно в HTML можно использовать только 96 символов,
Минус 0x7F, получается 95. То есть вы 96-м считаете, судя по картинке, табуляцию (0x09), но это не очевидно. При простом рендере HTML табуляция эквивалентена пробелу (как и перевод строки 0x0A). Понятно, что вы рассматриваете содержимое HTML как поток сырых данных и, видимо, учитываете то ли табуляцию, то ли перевод строки как отдельный символ, но в вашей исходной фразе нет такого пояснения, и я с vybo запутались в вашем подсчёте символов.

horpia Автор
09.10.2025 07:20да, понял теперь про что вы. Так и есть, при рендере HTML многие пробельные символы схлопываются. Но для нас важен сам контент, он само собой никуда не схлопывается если брать textContent от узла DOM. Табуляцию безопасно использовать, а вот перенос строки нужно много тестов на разных ОС, так как в некоторых случаях \n (т.е. 0x0A) не читался назад. А вот с \r (0x0D) совсем беда была. Поэтому чтобы сильно не рисковать я решил исключить \n из алфавита, хотя в теории можно его использовать, нужно просто все тесты перепроверить на разных устройствах.

Overphase
09.10.2025 07:20В первом ответе этой ветки вы упомянули перевод строки, а не табуляцию. В общем, кажется теперь понятно, что и где вы имели в виду, но путаница была.

nuclight
09.10.2025 07:20И кстати, & бы тоже убрать для надежности, да и на все <33 тоже полагаться - ну такое себе, мало ли кто испортит документ по пробельным символам/переводам строк (да, включая табы).

horpia Автор
09.10.2025 07:20В теге script использовать & безопасно. Этот символ неотъемлемая часть js, в котором используется для операторов И и побитовое И.
А пробелы и табы просто так никто не будет трогать в документе. Если речь про использование Base223 где-то ещё, то само собой алфавит нужно пересматривать под ту задачу, к которой он применяется. В моем случае html файл собирается и его содержимое никто менять не должен. Точно также, как если собрать бинарник, его трогать нельзя, перестанет работать.

Zara6502
09.10.2025 07:20не добрался до дома чтобы проверить, что же станет с вашим файлом после применения расширения SingleFileZ :)

demimurych
09.10.2025 07:20Использование кодировки отличной от UTF8, в случае хромиум подобных браузеров и v8, имеет серьезный недостаток - потеря возможности кеширования данных связанных с интерпретацией и оптимизацией javascript кода.
Иными словами, при каждой новой загрузке такого файла, весь процесс проходит так как в первый раз.

horpia Автор
09.10.2025 07:20Да, все отличные от UTF-8 кодировки накладывают различные особенности в работе браузера. Если рассматривать какой-нибудь сайт, где важна скорость загрузки, кеширование всего, и кучу разных метрик web vitals, то полностью поддерживаю, только utf-8 нужно использовать. То, что vk.com и pikabu.ru используют cp1251 связано с пресловутым "исторически сложилось", так как слезть с кодировки, когда у проекта вся база в ней, весь код написан с учетом однобайтовой кодировки - это титанический труд, когда проект очень большой и в нем миллионы страниц и пользователей. Если новый проект - само собой только utf8 и ничего другого.
Однако в моем случае речь идет про скачиваемый файл, который и так кеширования не будет изменить никакого. Для такого сценария можно закрыть глаза на использование однобайтовой кодировки в угоду уменьшения размера файла.

phlykov
09.10.2025 07:20А если сначала deflate, а потом уже base64? Картинки, конечно, не пожмутся, а вот скрипты очень даже да.

horpia Автор
09.10.2025 07:20Так и сделано) Для некоторых типов файлов применяется gzip, и поверх Base223.
Т.е. я максимально постарался все сжать. Файлы самой анимации сжаты очень сильно моим алгоритмом компрессии анимации, а поверх сжаты Brotli + Base223.

muxa_ru
09.10.2025 07:20Из минусов:
- работает не у всех и чем дальше, тем больше не у всех
- нужно тратить ресурсы на декодированиеПолучить 170 мегабайт вместо 220 того стоит?

horpia Автор
09.10.2025 07:20Пока что да, стоит. Проект будет расти и через пару лет будет весить 220 уже в исходном виде, поэтому со старта искал вариант уменьшить размер.
А почему не у всех работает? И почему чем дальше, то не у всех? Эта кодировка никуда не планирует исчезать, повторюсь - она входит в спецификацию HTML как обязательная для поддержки браузерами.
В любом случае как только объявят, что cp1251 будет выпиливаться из поддержки основных браузеров, тогда я поменяю способ кодирования на Base85, или снова сяду за анализ вариантов и что-нибудь подберу оптимальное на замену cp1251. Но пока что нет ни намека, что какой-то браузер будет не поддерживать cp1251. Или я ошибаюсь и где-то поддержки нет?

muxa_ru
09.10.2025 07:20А что такого важного в разнице между 170 и 200 мегабайт, что нужно ставить под угрозу функционал?
Вы свой диск экономите или пользовательский?

horpia Автор
09.10.2025 07:20Вижу вот такие проблемы:
дольше качаться будет. Не у всех высокоскоростной интернет.
я плачу за трафик. Не так много, $0.005 за ГБ, но с учетом, что на проект иногда наплывами заходят в день по 80 тыс человек, то трафик порой за месяц выходит в $100+.
чем больше по размеру HTML, тем больше браузеру нужно ресурсов для его загрузки. Это сказывается на скорости чтения файла: больше обращений к диску, больше объема данных на сканирование, больше объема данных для работы с памятью.
вопрос совести. Мне, как программисту, не дает покоя, что я делаю что-то неоптимальное. В данном случае риски фантомные пока что, я реальных рисков не вижу, поэтому путь оптимизации через использование cp1251 выглядит точно диким, но рабочим и на сегодняшний день не опасным на горизонте. Но согласен, что завтра все может поменяться.

muxa_ru
09.10.2025 07:20чем больше по размеру HTML, тем больше браузеру нужно ресурсов для его загрузки. Это сказывается на скорости чтения файла: больше обращений к диску, больше объема данных на сканирование, больше объема данных для работы с памятью.
У Вас есть сравнение этих затрат с затратами на специфическое кодирование?

horpia Автор
09.10.2025 07:20Тут и замеры не нужны, обработка кодирования значительно дольше делается и более ресурсоемкая априори. Браузер как-никак сверхбыстрая штука нынче.
Но, как в статье и описано, декодирование делается ленивое, т.е. только когда требуется нужный ресурс. Не все 170 МБ сразу нужны для рендера. Поэтому декодирование делается лишь малой части от этого объема. А далее пользователь скролирует анимационную сцену и при подгрузке новых ресурсов налету делается декодирование. Чисто по визуальному и функциональному поведению все хорошо - браузер на разных устройствах успевает декодировать Base223 налету так, что глазу это практически незаметно. Результат меня удовлетворил. Однако, если даже предположить, что декодирование было бы долгим, то я все равно бы остался на выбранном варианте, просто искал способы ускорить декодирование, в частности применение wasm или webgpu.
Я понимаю ваши опасения касательно моего выбора. Внутри меня где-то чуйка тоже шепчет "куда ты лезешь, используй base64 и не выпендривайся". Но все же рациональная оценка ситуации говорит о том, что риски не такие большие, и в случае чего я всегда могу заменить способ кодирования на другой. Итого: файл значительно меньше, визуально никаких проблем с декодированием налету нет, поддержка cp1251 стабильная и нет предпосылок считать, что браузеры откажутся от этой кодировки в обозримом будущем.

thousbe
09.10.2025 07:20А вариант с архивацией не рассматривался? Можно же заархивировать файлы в архив, вставить через data-uri, а при загрузке разархивировать и вставить на стриницу через eval. При средней степени сжатия в 92% для текста и потерями base64 итоговый размер будет в 178 МБ × (1 - 0.92) × 1.25 ≈ 17.8 МБ

horpia Автор
09.10.2025 07:2098% всего объема, что добавляется в html документ - это уже сильно сжатые файлы: анимация, музыка, jpg и прочее. Эти данные практически не сжимаются больше.
2% - это всякие текстовые файлы, типа js, css, json и пр. Они предварительно сжимаются gzip, и далее кодируются через Base223.
Т.е. документ и так сильно сжат, сильнее его не сжать. Вы можете попробовать скачать его и сжать https://floor796.com/data/floor796.html
К примеру через zip он сожмется с 171 МБ до 168МБ, т.е. сократит ту избыточность, что добавляет кодировка Base223 назад в оригинальный размер данных.

fera2k
09.10.2025 07:20Добрый день! Тоже практически ежедневно заглядываю уже много лет. Автор, подскажите пожалуйста в квесте подпространственный тюнер, куда копать. Уже все передумал.

horpia Автор
09.10.2025 07:20Добрый день)
Нужно подобрать правильную настройку тюнера. Для этого на экранчике на самом тюнере показывается подсказка, что нужно позвонить в сервис, и указывается на то, что номер телефона сервиса можно получить, если выдернуть штекер какой-то. Уточните, это уже прошли и штекер нашли?

fera2k
09.10.2025 07:20Нет, штекер сейчас ищу. в корабле пробовал. теперь по всей площади пошел. Рядом не нашел. еще пирамиду искал.

horpia Автор
09.10.2025 07:20Тогда если нужна подсказка где штекер: его можно найти где комната с голограммами переключающимися. По задумке эти голограммы разработаны то же фирмой, что и тюнер.

fera2k
09.10.2025 07:20Да, теперь пытаюсь понять что такое а77) верхние переключатели вижу, не могу понять почему 77. Неужели подобрать сопротивление? Клад был проще) Спасибо)

horpia Автор
09.10.2025 07:20Ну, я снова оставлю в спойлере ответ, если никак не получится, то смотри спойлер: там 8 тумблеров, каждой переключается в 2 состояния, т.е. по сути 8 значений от 0 до 1. Думаю так как мы на Хабре сейчас, то должно сразу быть понятно, что это двоичная форма байта. Т.е. задача ввести 77 число в двоичной форме. Можно так и загуглить "число 77 в 8 битах"

fera2k
09.10.2025 07:20Спасибо! На первый квест у меня ушло три дня, на второй три месяца. Я как только увидел вашу статью в ленте бросился ее читать! И вас тут нашел)

fera2k
09.10.2025 07:20Перерыл всю карту и перетыкал все штекеры. Пока не нашел. Рядом штекер только в корабле и он не работает)

tuod
09.10.2025 07:20Хочу поблагодарить за этот огромный труд. И за проект и за развитие и за офлайн.
Есть некоторые мысли, идеи.
Первая мысль. При запаковке проекта в офлайн вовсе не обязательно использовать тот же стек технологий. Веб очень ущербен в плане производительности. Очень многое в нём плохо из-за длинного шлейфа обратной совместимости.
Я бы подумал над производством конвертера, который бы одной кнопкой создавал Unity или Unreal Engine проект. Это же просто 2D сцена и спрайты.
Вторая мысль. При локальном запуске уже будут достаточные вычислительные ресурсы при рендере, чтобы пропускать анимацию через нейронку для апскейла и увеличения FPS анимации (опционально).
С производством этих технологических решений вполне могут помочь современные AI агенты. Многое можно навайбкодить даже.
А теперь представь, что это будет анимировано. Будоражит воображение.
horpia Автор
09.10.2025 07:20Спасибо)
По поводу Unity и Unreal. Я больше веб-разработчик, поэтому мне ближе WebGL, WebGPU и библиотеки под него, типа Three.js. Для перевода моего проекта с рендара на потоках js в рендер на шейдерах видеокарты без сторонних библиотек нужно много работать. Я уже начинал это делать на WebGPU, придумал как создать рендер на шейдере но, забросил, пока что не возвращался. Но если перевести, то проблемы в производительности почти не будет. Но и сейчас их толком нет. Сейчас проекту нужен обычный 12fps, и согласно статистике проекта, большая часть пользователей успешно успевает рендерить 12fps. Т.е. проблемы с производительностью из-за использования рендера через канвас на js практически нет. Я делаю ставку на будущее, что и устройства, и браузеры - все будет ускорятся, а следом за ними мой проект. Его ещё рисовать как минимум 5 лет до 100 блоков, а там посмотрим ;)
пропускать анимацию через нейронку для апскейла и увеличения FPS анимации
Я бы хотел этого избежать. В моей анимации все криво, непрофессионально, редуцированно - все это часть образа, итог ручной работы. Именно это и должно быть во всех копиях проекта. Т.е. я специально не делаю интерполяцию (хотя могу запрограммировать её и без ИИ), специально не применяю трехмерные куклы для ускорения рисования, у меня в редакторе даже нет вставки картинок извне и нельзя печатать текст (специально), все рисую вручную, все тексты на экранах, все "копии" чего-либо (хотя 100 раз проще было бы вставить картинку, чем её перерисовывать). Я запретил все это со старта специально, чтобы у проекта был свой шарм, идея - все нарисовано от руки. Я не переделываю старые части проекта, которые рисовал ещё более криво и редуцированно, чем сейчас, также по причине того, что проект содержит историю изменения стиля и качества рисовки, это часть работы) Под редуцированием имею в виду прием в анимации для сокращения кадров, из-за этого анимация выглядит резкой, не такой естественной.
Итого, я целенаправленно не применяю ни ИИ, ни другие вещи, которые бы улучшали уже нарисованные сцены.

tuod
09.10.2025 07:20Да, безусловно этот шарм есть и я его чувствую. Если такова воля творца, то именно так и должно быть. Я далёк от искусства, а тут это именно оно, притом с большой буквы.
Прошу прощения если мои мысли резанули душу творца. Не хотел обидеть. Лично мне, как человеку с бедным графическим воображением, проще, когда детали додумывают за меня, но тут речь только про меня. Никому не навязываю свой подход к восприятию прекрасного )Я там ещё один коммент накатал до того, как пришёл твой. Просто не смотри )

horpia Автор
09.10.2025 07:20мне в любом случае приятно обсуждать это, так что спасибо за идеи)
Рисовать утомительно, всегда хочется упростить (при том, что я по профессии программист уже больше 20 лет и привык искать пути полегче), и уже не раз думал, что неплохо бы обучить нейронку в помощь в рисовании. Но потом всякий раз отрезвляло то, что я делаю - это творческая работа не для заработка, а для вау-эффекта и искусства в целом. И важно до последней анимации в этом проекте сохранить подход: нарисовано все должно быть от руки без замены человека нейронкой. Тогда ценность проекта сохранится, так как для его создания не применялся ИИ и другие сильные упрощения. Знаешь как в спорте, допинг запрещен. Вот тут применение ИИ - это чистый допинг :D

tuod
09.10.2025 07:20О, ещё мысль пришла. Можно объявлять конкурс на новые квадраты. Для этого нужно предоставить контракт и публичный API для тестирования. Публикацию не гарантировать. Оставлять последнее слово за автором проекта. Так можно привлечь авторов и наращивать вселенную коллективно. Конечно, заранее нужно подумать о юридическом оформлении, чтобы авторы безусловно и навсегда лишались всех прав на интеллектуальную собственность, кроме, собственно, авторских (они неотчуждаемы).
Ещё одна мысль. Контента во floor796 уже, кмк, достаточно чтобы попытаться обучить модель, которая сможет генерить новые сцены по текстовому описанию.
IgnatF
Столкнулся недавно с таким моментом, что хром в 11 версии не захотел эту кодировку применять. А так может лучше было бы ваш проект на старых добрых фреймах сделать. И грузить там по необходимости html документы.
horpia Автор
Спасибо за идею) С windows-1251 я много работаю на своей работе, пока что проблем с ней не ловил в браузерах. А по поводу iframe: все же цель сделать 1 файл для скачивания, который можно запустить просто кликнув по нему, а любая механика с iframe предполагает, что я буду загружать рядом лежащие файлы через него. Не уверен будет ли это вообще работать без веб-сервера, особенно доступ js к подгруженному в iframe контенту, ведь браузеры сильно ограничивают возможности подгрузки чего-либо, когда html запускается не по http(s).
here-we-go-again
А MHTML не подходит? Сто лет не видел, но он же вроде для того и сделан. Или наверное есть какая-то свежая его реинкарнация.
Еще можно гонять html в пдф файле. Там наверное проще тоже будет ресурсы внутри хранить.
horpia Автор
C MHTML много вопросов поддержки. Я его не использовал никогда, поэтому опыта 0 и нужно исследовать тщательно какие браузеры его смогут открывать локально, а какие нет. В любом случае, если не ошибаюсь, MHTML предполагает упаковки всего контента вверху документа, так, как это происходит при отправке писем. Тут сразу 2 проблемы:
размер, так как там используется base64, а он всего 75% эффективность имеет
не получится сделать прелодер.
horpia Автор
С PDF вероятно не получится никак. У меня используется достаточно современные API для рендера, которые поддерживаются только в браузерах. Не проверял, но очень сомневаюсь, что читалки PDF под капотом имеют встроенный браузер со всеми наворотами, типа Web Workers, IndexedDB, Canvas и прочее. Да и вроде лишь некоторые читалки вообще способны динамику делать в PDF, все остальные - тупо рендер статический, без динамических скриптов.
TimsTims
И хорошо что не имеют, имхо. Сколько в истории было уязвимостей в PDF, потому-что решили встроить туда очередной наворот. Пусть лучше PDF останется для сканов и немного текста.
john1
А у него есть какой-то общий базовый для всех стандарт? Хотел как-то переводить в него справки из chm, но не смог найти ни одного варианта кодирования в mhtml, чтобы последний отображался в любом браузере, любой версии.
muxa_ru
txt
nuclight
А просто, как в старые добрые времена, отдать .zip-файл, в котором надо кликнуть index.html?
horpia Автор
да можно и rar, чего уж там, у всех крякнутый где-то да найдется :)
Но если серьезно, то идея была сделать с максимальной доступностью. К примеру, zip на планшете или телефоне открыть может быть проблемой, да и рядовой пользователь может не понять что он скачал и как это открыть (уровень владения ПК у всех разный).
Также в идее с zip нет уникальности или какого-либо "открытия". Мне нравится мыслить креативно, искать новые пути. И вот показалось интересным возовом упаковать проект в один html, сделав его максимально компактным, с высокой доступностью и безопасностью.
nuclight
Зато трафик может сэкономить. Во всяком случае, можно выложить две версии - решение исторических костылей веба в виде одного .html
для тупого быдлаи .zip для тех, кто понимает, и экономит трафик.horpia Автор
zip не поможет ничего сэкономить, точнее максимум 2%, именно та избыточность, которую добавляет Base223.
Оригинальные 168МБ данных уже сжаты разными Brotli и gzip/deflate. Так что сверху накладывается где-то 2% избыточности от Base223 и получается html весом 170МБ. Так что zip ничем тут не поможет для трафика, если оригинальный контент им сжимать или итоговый HTML им сжимать .