Construtor de HTML de Splash Personalizado

:information_source: Resumo Plugin do Discourse que permite personalizar a tela de abertura (splash screen) usando HTML e CSS definidos pelo administrador.
:hammer_and_wrench: Link do Repositório https://github.com/VaperinaDEV/custom-splash-html-builder
:open_book: Guia de Instalação Como instalar plugins no Discourse

Olá :waving_hand:

Criei um pequeno plugin para o Discourse que permite personalizar a tela de abertura usando HTML e CSS definidos pelo administrador, sem a necessidade de manter uma cópia modificada do modelo central de abertura do Discourse.

A motivação original para criar este plugin foi, na verdade, o desempenho em dispositivos móveis.

Eu queria criar uma tela de abertura animada mais sofisticada, mas percebi que as animações SVG suportadas pela implementação central atual do Discourse podiam se tornar surpreendentemente problemáticas em dispositivos móveis.

No desktop, a animação poderia parecer perfeitamente fluida, enquanto no mobile ela poderia ficar visivelmente travada, perder quadros, apresentar lag ou até mesmo parecer parar durante a animação.

Após experimentar diferentes abordagens, descobri que mover a animação do próprio SVG para um elemento HTML circundante, como um <div>, fazia uma diferença muito significativa.

Em vez de animar continuamente o conteúdo do SVG, o SVG pode permanecer estático enquanto o navegador anima a camada HTML contêiner usando transformações CSS.

Isso dá ao navegador uma oportunidade muito melhor de lidar com a animação como uma operação de composição usando o hardware gráfico do dispositivo.

O resultado foi uma animação muito mais fluida no mobile, sem os travamentos e congelamentos que eu estava vendo com a abordagem baseada em SVG.

Essa foi a principal razão pela qual este plugin foi criado.

Antes (SVG Animado: A animação com lag e parada)

Depois (HTML Animado: Animação fluida)


O problema com animar SVGs

A implementação original de abertura é perfeitamente adequada para um logotipo simples ou uma animação relativamente leve.

No entanto, assim que a animação se torna mais complexa, a renderização SVG pode se tornar custosa.

Por exemplo, uma animação aplicada diretamente a um SVG ou aos seus elementos internos pode exigir que o navegador processe ou repinte partes do SVG repetidamente durante a animação.

Em dispositivos móveis, isso pode se tornar particularmente perceptível.

Durante os testes, vi casos em que a animação:

  • ficava visivelmente travada
  • congelava temporariamente
  • parecia parar
  • se comportava significativamente pior do que no desktop

A parte interessante era que a mesma animação visual poderia se comportar de maneira muito diferente dependendo do que estava sendo realmente animado.


Movendo a animação para uma camada HTML

A abordagem que funcionou muito melhor foi manter o próprio SVG estático e colocá-lo dentro de um elemento HTML normal.

Por exemplo:

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

Em vez de animar o SVG, a animação é aplicada ao contêiner:

.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);
  }
}

O próprio SVG não muda.

Portanto, o navegador pode lidar com a transformação da camada HTML de maneira muito mais eficiente e, nos casos suportados, promovê-la para uma camada composta gerenciada pelo hardware gráfico.

Isso produziu um resultado dramaticamente mais fluido em dispositivos móveis.

A distinção importante é, portanto:

Abordagem central:

SVG
 └── Animação SVG
      └── O conteúdo do SVG é animado

vs:

Abordagem personalizada:

Camada HTML
 └── SVG
      └── Transformação CSS na camada HTML
           └── Animação amigável ao compositor

Isso não é uma garantia de que cada animação será acelerada por GPU. O navegador decide, em última análise, como uma animação é composta, mas em meus testes a diferença foi muito perceptível.


Por que criei o Custom Splash HTML Builder

Depois que essa abordagem funcionou, também precisei de uma maneira de realmente construir a tela de abertura em torno dela.

O modelo de abertura padrão não oferece flexibilidade suficiente para esse tipo de implementação.

Para uma animação mais complexa, eu poderia precisar:

  • várias camadas SVG
  • vários contêineres HTML
  • elementos animados independentemente
  • keyframes CSS personalizados
  • tempos de animação diferentes
  • posicionamento personalizado
  • cores conscientes do tema
  • marcação completamente diferente da abertura padrão

Então, em vez de criar outra implementação de abertura codificada rigidamente, decidi expor a parte visual por meio de duas configurações do site.

O plugin adiciona:

splash_custom_html

A marcação HTML/SVG renderizada dentro da tela de abertura.

splash_custom_css

O CSS usado pela abertura personalizada, incluindo animações, keyframes, posicionamento e comportamento responsivo.

Isso torna a abertura efetivamente personalizável sem ter que modificar o código-fonte do plugin toda vez que a animação muda.


Editor de administrador integrado

O plugin também fornece um pequeno editor de administrador integrado para gerenciar a abertura personalizada.

Ele adiciona uma seção dedicada Splash HTML Builder na interface de administração do Discourse, com editores separados para:

  • HTML Personalizado
  • CSS Personalizado

As alterações podem ser salvas diretamente da interface de administração sem editar manualmente as configurações do site correspondentes.

As configurações subjacentes ainda são:

  • splash_custom_html
  • splash_custom_css

O editor é simplesmente uma interface mais conveniente para gerenciá-los.

Isso também significa que o plugin não exige a modificação dos arquivos de código-fonte do plugin sempre que a animação de abertura precisar ser alterada.


Exemplo

Uma abertura personalizada pode conter várias camadas independentes:

<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>

E cada camada pode ter sua própria animação:

.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);
  }
}

Os SVGs permanecem estáticos enquanto as camadas HTML circundantes são animadas.

Isso torna possível criar animações de abertura consideravelmente mais complexas, mantendo o trabalho de animação custoso fora do próprio SVG.


Por que não simplesmente substituir o modelo central de abertura?

Outro objetivo importante era evitar manter uma cópia do modelo central de abertura do Discourse.

Uma abordagem direta seria substituir:

app/views/common/_discourse_splash.html.erb

e copiar a implementação atual do Discourse para o plugin.

O problema é que isso cria uma carga de manutenção.

Se o Discourse alterar sua implementação de abertura em uma versão futura, o plugin ainda conteria a versão antiga.

Isso poderia potencialmente resultar em:

  • falta de novas alterações centrais
  • falta de melhorias de desempenho
  • comportamento quebrado após uma atualização do Discourse
  • necessidade de comparar manualmente o modelo do plugin com o central após cada atualização

Eu queria evitar isso completamente.


Fallback central

Portanto, o plugin suporta uma abertura personalizada com um fallback central.

HTML personalizado está configurado

Se:

SiteSetting.splash_custom_html.present?

então o plugin renderiza a abertura personalizada.

HTML personalizado está vazio

Se nenhuma abertura personalizada foi configurada, o plugin recorre ao modelo central de abertura atual do Discourse.

O plugin localiza o arquivo central real da instalação do Discourse em execução:

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

e renderiza essa implementação.

Conceitualmente:

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

Isso significa que o plugin não carrega uma segunda cópia do modelo central de abertura.


Considerações de desempenho

O plugin não está tentando alegar que cada animação CSS se tornará magicamente acelerada por GPU.

O navegador ainda decide como as animações individuais são renderizadas e compostas.

O objetivo é, em vez disso, dar ao navegador uma estrutura muito mais favorável para composição acelerada por hardware:

  • manter o conteúdo SVG estático
  • isolar elementos animados independentemente
  • animar camadas HTML
  • preferir transform para movimento/escala/rotação
  • evitar operações de repintura desnecessariamente custosas
  • usar will-change onde apropriado

Por exemplo:

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

Essa abordagem funcionou particularmente bem para meu caso de uso e eliminou os travamentos no mobile que eu estava vendo com a animação SVG original.


Habilitar ou desabilitar a abertura personalizada

O plugin também fornece uma configuração do site custom_splash_html_builder_enabled.

Quando desabilitado, a tela de abertura padrão do Discourse é usada, independentemente de HTML ou CSS personalizados terem sido configurados.

Isso fornece um interruptor de segurança adicional para desabilitar temporariamente a abertura personalizada sem excluir o HTML/CSS salvo.

A abertura personalizada é renderizada apenas quando:

custom_splash_html_builder_enabled = true
splash_custom_html não está vazio

Caso contrário, a abertura central atual do Discourse é usada.


O mais importante é que isso fornece uma maneira de criar uma abertura animada personalizada que tem um desempenho muito melhor no mobile, animando camadas HTML em torno de conteúdo SVG estático em vez de animar o próprio SVG diretamente.

1 curtida