Конструктор пользовательского HTML для заставки

:information_source: Краткое описание Плагин для Discourse, позволяющий настраивать заставку (splash screen) с помощью пользовательского HTML и CSS, заданного администратором.
:hammer_and_wrench: Ссылка на репозиторий https://github.com/VaperinaDEV/custom-splash-html-builder
:open_book: Руководство по установке Как устанавливать плагины в Discourse

Привет :waving_hand:

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

Изначальной мотивацией для создания этого плагина стала производительность на мобильных устройствах.

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

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

После экспериментов с различными подходами я обнаружил, что перенос анимации из самого SVG в окружающий его HTML-элемент, например <div>, даёт очень значительный эффект.

Вместо непрерывной анимации содержимого SVG, сам SVG остаётся статичным, а браузер анимирует содержащий его HTML-слой с помощью CSS-трансформаций.

Это даёт браузеру гораздо больше возможностей для обработки анимации как операции композитинга с использованием графического процессора устройства.

Результатом стала гораздо более плавная анимация на мобильных устройствах, без рывков и зависаний, которые я наблюдал при использовании подхода на основе SVG.

Это была главная причина создания этого плагина.

До (Анимированный SVG: анимация лагает и останавливается)

После (Анимированный HTML: плавная анимация)


Проблема анимации SVG

Исходная реализация заставки отлично подходит для простой логотипа или относительно лёгкой анимации.

Однако, как только анимация становится более сложной, рендеринг SVG может стать ресурсоёмким.

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

На мобильных устройствах это может стать особенно заметным.

Во время тестирования я наблюдал случаи, когда анимация:

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

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


Перенос анимации на HTML-слой

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

Например:

<div class="logo-layer">
  <svg viewBox="0 0 500 500">
    ...
  </svg>
</div>

Вместо анимации SVG, анимация применяется к контейнеру:

.logo-layer {
  animation: pulse 1.8s ease-in-out infinite;
  will-change: transform;
}

@keyframes pulse {
  0%,
  100% {
    transform: scale(0.8);
  }

  50% {
    transform: scale(0.85);
  }
}

Сам SVG не меняется.

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

Это дало значительно более плавный результат на мобильных устройствах.

Таким образом, важное различие заключается в следующем:

Основной подход:

SVG
 └── Анимация SVG
      └── Анимация содержимого SVG

против:

Пользовательский подход:

HTML-слой
 └── SVG
      └── CSS-трансформация HTML-слоя
           └── анимация, дружественная к композитингу

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


Почему я создал Custom Splash HTML Builder

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

Стандартный шаблон заставки не предоставляет достаточной гибкости для такого типа реализации.

Для более сложной анимации мне может потребоваться:

  • несколько слоёв SVG
  • несколько HTML-контейнеров
  • независимо анимируемые элементы
  • пользовательские CSS-кадровые анимации (keyframes)
  • различные тайминги анимации
  • пользовательское позиционирование
  • цвета, зависящие от темы
  • разметка, полностью отличная от стандартной заставки

Поэтому, вместо создания ещё одной жёстко закодированной реализации заставки, я решил вынести визуальную часть через две настройки сайта.

Плагин добавляет:

splash_custom_html

HTML/SVG-разметка, отображаемая внутри заставки.

splash_custom_css

CSS, используемый пользовательской заставкой, включая анимации, ключевые кадры, позиционирование и адаптивное поведение.

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


Встроенный административный редактор

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

Он добавляет специальный раздел Splash HTML Builder в интерфейс администратора Discourse, с отдельными редакторами для:

  • Пользовательского HTML
  • Пользовательского CSS

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

Базовые настройки остаются прежними:

  • splash_custom_html
  • splash_custom_css

Редактор — это просто более удобный интерфейс для их управления.

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


Пример

n
Пользовательская заставка может содержать несколько независимых слоёв:

<div class="splash-logo-container">

  <div class="ring-layer">
    <svg viewBox="0 0 500 500">
      ...
    </svg>
  </div>

  <div class="logo-layer">
    <svg viewBox="0 0 500 500">
      ...
    </svg>
  </div>

</div>

И каждый слой может иметь свою собственную анимацию:

.ring-layer {
  animation: rotate 2.2s linear infinite;
  will-change: transform;
}

.logo-layer {
  animation: pulse 1.8s ease-in-out infinite;
  will-change: transform;
}

@keyframes rotate {
  from {
    transform: rotate(0deg);
  }

  to {
    transform: rotate(360deg);
  }
}

@keyframes pulse {
  0%,
  100% {
    transform: scale(0.8);
  }

  50% {
    transform: scale(0.85);
  }
}

SVG остаются статичными, в то время как окружающие их HTML-слои анимируются.

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


Почему бы просто не переопределить основной шаблон заставки?

Ещё одной важной целью было избежать необходимости поддерживать копию основного шаблона заставки Discourse.

Очевидный подход заключался бы в переопределении:

app/views/common/_discourse_splash.html.erb

и копировании текущей реализации Discourse в плагин.

Проблема в том, что это создаёт нагрузку на поддержку.

Если Discourse изменит свою реализацию заставки в будущем выпуске, плагин будет по-прежнему содержать старую версию.

Это потенциально может привести к:

  • отсутствию новых изменений ядра
  • отсутствию улучшений производительности
  • сбоям в работе после обновления Discourse
  • необходимости вручную сравнивать шаблон плагина с ядром после каждого обновления

Я хотел полностью этого избежать.


Резервный вариант ядра

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

Пользовательский HTML настроен

Если:

SiteSetting.splash_custom_html.present?

то плагин отображает пользовательскую заставку.

Пользовательский HTML пуст

Если пользовательская заставка не настроена, плагин использует текущий основной шаблон заставки Discourse.

Плагин находит фактический файл ядра в работающей установке Discourse:

Rails.root/app/views/common/_discourse_splash.html.erb

и отображает эту реализацию.

Концептуально:

core_splash_path = Rails.root.join("app", "views", "common", "_discourse_splash.html.erb")

if File.exist?(core_splash_path)
  render inline: File.read(core_splash_path), type: :erb
end

Это означает, что плагин не содержит вторую копию основного шаблона заставки.


Вопросы производительности

Плагин не претендует на то, что каждая CSS-анимация волшебным образом будет ускорена GPU.

Браузер по-прежнему решает, как рендерить и композитировать отдельные анимации.

Цель состоит в том, чтобы предоставить браузеру гораздо более благоприятную структуру для аппаратно-ускоренного композитинга:

  • сохранять содержимое SVG статичным
  • изолировать независимо анимируемые элементы
  • анимировать HTML-слои
  • предпочитать transform для перемещения/масштабирования/вращения
  • избегать ненужных дорогостоящих операций перерисовки
  • использовать will-change там, где это уместно

Например:

.ring-layer {
  will-change: transform;
  animation: rotate 2.2s linear infinite;
}

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


Включение или отключение пользовательской заставки

Плагин также предоставляет настройку сайта custom_splash_html_builder_enabled.

Когда она отключена, используется стандартная заставка Discourse, независимо от того, настроены ли пользовательский HTML или CSS.

Это обеспечивает дополнительный предохранитель для временного отключения пользовательской заставки без удаления сохранённого HTML/CSS.

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

custom_splash_html_builder_enabled = true
splash_custom_html не пуст

В противном случае используется текущая основная заставка Discourse.


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

1 лайк