Eigener Splash-HTML-Builder

:information_source: Zusammenfassung Discourse-Plugin, das die Anpassung des Splash-Screens mithilfe von vom Administrator definierten HTML- und CSS-Codes ermöglicht.
:hammer_and_wrench: Repository-Link https://github.com/VaperinaDEV/custom-splash-html-builder
:heart: Gefällt es dir? > ./support --coffee
:open_book: Installationsanleitung So installierst du Plugins in Discourse

Hallo :waving_hand:

Ich habe ein kleines Discourse-Plugin erstellt, mit dem der Splash-Screen mithilfe von vom Administrator definierten HTML- und CSS-Codes angepasst werden kann, ohne dass eine modifizierte Kopie der zentralen Splash-Vorlage von Discourse gepflegt werden muss.

Die ursprüngliche Motivation für die Erstellung dieses Plugins war tatsächlich die Mobile Performance.

Ich wollte einen ausgefeilteren animierten Splash-Screen erstellen, stellte jedoch fest, dass die SVG-Animationen, die von der aktuellen zentralen Splash-Implementierung von Discourse unterstützt werden, auf mobilen Geräten überraschende Probleme verursachen können.

Auf dem Desktop kann die Animation völlig flüssig aussehen, während sie auf Mobilgeräten deutlich ruckeln, Frames verlieren, verzögert ablaufen oder sogar während der Animation stehen bleiben kann.

Nachdem ich verschiedene Ansätze ausprobiert hatte, stellte ich fest, dass das Verschieben der Animation vom SVG selbst auf ein umgebendes HTML-Element, wie z. B. ein <div>, einen sehr großen Unterschied machte.

Anstatt die SVG-Inhalte kontinuierlich zu animieren, kann das SVG statisch bleiben, während der Browser die umgebende HTML-Ebene mit CSS-Transforms animiert.

So hat der Browser eine viel bessere Möglichkeit, die Animation als Kompositing-Operation mit der Grafikhardware des Geräts zu verarbeiten.

Das Ergebnis war eine deutlich flüssigere Animation auf Mobilgeräten, ohne die Ruckler und Einfrieren, die ich bei dem SVG-basierten Ansatz beobachtet hatte.

Das war der Hauptgrund für die Erstellung dieses Plugins.

Vorher (Animiertes SVG: Die Animation ruckelt und bleibt stehen)

Nachher (Animiertes HTML: Fließende Animation)


Das Problem mit der Animation von SVGs

Die ursprüngliche Splash-Implementierung ist für ein einfaches Logo oder eine relativ leichte Animation völlig in Ordnung.

Sobald die Animation jedoch komplexer wird, kann die SVG-Darstellung aufwendig werden.

Zum Beispiel kann eine Animation, die direkt auf ein SVG oder seine internen Elemente angewendet wird, dazu führen, dass der Browser während der Animation Teile des SVGs wiederholt verarbeiten oder neu zeichnen muss.

Auf mobilen Geräten kann dies besonders deutlich spürbar sein.

Während der Tests sah ich Fälle, in denen die Animation:

  • sichtbar ruckelte
  • vorübergehend einfroren
  • stehen blieb
  • sich deutlich schlechter verhielt als auf dem Desktop

Der interessante Teil war, dass sich dieselbe visuelle Animation je nachdem, was tatsächlich animiert wurde, sehr unterschiedlich verhalten konnte.


Die Animation auf eine HTML-Ebene verschieben

Der Ansatz, der viel besser funktionierte, bestand darin, das SVG selbst statisch zu lassen und es in ein normales HTML-Element zu setzen.

Zum Beispiel:

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

Anstatt das SVG zu animieren, wird die Animation auf den Container angewendet:

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

Das SVG selbst ändert sich nicht.

Der Browser kann die Transformation der HTML-Ebene daher viel effizienter verarbeiten und sie in unterstützten Fällen zu einer kompositierten Ebene hochstufen, die von der Grafikhardware bearbeitet wird.

Dies führte zu einem dramatisch flüssigeren Ergebnis auf mobilen Geräten.

Der wichtige Unterschied ist daher:

Zentraler Ansatz:

SVG
 └── SVG-Animation
      └── SVG-Inhalte werden animiert

vs.:

Eigener Ansatz:

HTML-Ebene
 └── SVG
      └── CSS-Transform auf der HTML-Ebene
           └── Für den Kompositor geeignete Animation

Das ist keine Garantie dafür, dass jede Animation GPU-beschleunigt wird. Der Browser entscheidet letztendlich, wie eine Animation kompositiert wird, aber in meinen Tests war der Unterschied sehr deutlich.


Warum ich den Custom Splash HTML Builder erstellt habe

Sobald dieser Ansatz funktionierte, brauchte ich auch eine Möglichkeit, den Splash-Screen darum herum zu gestalten.

Die Standard-Splash-Vorlage bietet für diese Art von Implementierung nicht genügend Flexibilität.

Für eine komplexere Animation könnte ich Folgendes benötigen:

  • mehrere SVG-Ebenen
  • mehrere HTML-Container
  • unabhängig animierte Elemente
  • benutzerdefinierte CSS-Keyframes
  • unterschiedliche Animationszeiten
  • benutzerdefinierte Positionierung
  • eine völlig andere Markup-Struktur als die Standard-Splash-Vorlage

Also entschied ich mich, anstatt einer weiteren hartkodierten Splash-Implementierung, den visuellen Teil über zwei Seiteneinstellungen bereitzustellen.

Das Plugin fügt hinzu:

splash_custom_html

Das HTML/SVG-Markup, das innerhalb des Splash-Screens gerendert wird.

splash_custom_css

Das CSS, das vom benutzerdefinierten Splash verwendet wird, einschließlich Animationen, Keyframes, Positionierung und reaktivem Verhalten.

Dadurch ist der Splash-Screen effektiv anpassbar, ohne dass der Plugin-Quellcode jedes Mal geändert werden muss, wenn sich die Animation ändert.


Eingebauter Admin-Editor

Das Plugin bietet auch einen kleinen eingebauten Admin-Editor zum Verwalten des benutzerdefinierten Splash-Screens.

Er fügt eine dedizierte Splash HTML Builder-Sektion in der Discourse-Admin-Oberfläche hinzu, mit separaten Editoren für:

  • Benutzerdefiniertes HTML
  • Benutzerdefiniertes CSS

Die Änderungen können direkt aus der Admin-Oberfläche gespeichert werden, ohne die entsprechenden Seiteneinstellungen manuell bearbeiten zu müssen.

Die zugrunde liegenden Einstellungen sind nach wie vor:

  • splash_custom_html
  • splash_custom_css

Der Editor ist einfach eine bequemere Oberfläche zum Verwalten dieser Einstellungen.

Das bedeutet auch, dass das Plugin keine Änderungen an den Plugin-Quelldateien erfordert, wann immer die Splash-Animation geändert werden muss.


Beispiel

Ein benutzerdefinierter Splash-Screen kann mehrere unabhängige Ebenen enthalten:

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

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

Und jede Ebene kann ihre eigene Animation haben:

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

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

Die SVGs bleiben statisch, während die umgebenden HTML-Ebenen animiert werden.

So ist es möglich, deutlich komplexere Splash-Animationen zu erstellen, während die aufwendigen Animationsarbeiten außerhalb des SVGs selbst bleiben.


Warum nicht einfach die zentrale Splash-Vorlage überschreiben?

Ein weiteres wichtiges Ziel war es, die Pflege einer Kopie der zentralen Splash-Vorlage von Discourse zu vermeiden.

Ein naheliegender Ansatz wäre es, Folgendes zu überschreiben:

app/views/common/_discourse_splash.html.erb

und die aktuelle Discourse-Implementierung in das Plugin zu kopieren.

Das Problem ist, dass dies einen Wartungsaufwand mit sich bringt.

Wenn Discourse seine Splash-Implementierung in einer zukünftigen Version ändert, würde das Plugin immer noch die alte Version enthalten.

Das könnte potenziell zu Folgendem führen:

  • verpasste neue zentrale Änderungen
  • verpasste Performance-Verbesserungen
  • fehlerhaftes Verhalten nach einem Discourse-Update
  • manuelles Vergleichen der Plugin-Vorlage mit der zentralen Version nach jedem Update

Ich wollte das vollständig vermeiden.


Fallback auf die zentrale Implementierung

Das Plugin unterstützt daher einen benutzerdefinierten Splash mit Fallback auf die zentrale Implementierung.

Benutzerdefiniertes HTML ist konfiguriert

Wenn:

SiteSetting.splash_custom_html.present?

dann rendert das Plugin den benutzerdefinierten Splash.

Benutzerdefiniertes HTML ist leer

Wenn kein benutzerdefinierter Splash konfiguriert wurde, fällt das Plugin auf die aktuelle zentrale Splash-Vorlage von Discourse zurück.

Das Plugin lokalisiert die eigentliche zentrale Datei aus der laufenden Discourse-Installation:

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

und rendert diese Implementierung.

Konzeptionell:

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

Das bedeutet, dass das Plugin keine zweite Kopie der zentralen Splash-Vorlage mitführt.


Performance-Überlegungen

Das Plugin behauptet nicht, dass jede CSS-Animation magisch GPU-beschleunigt wird.

Der Browser entscheidet immer noch, wie einzelne Animationen gerendert und kompositiert werden.

Das Ziel besteht stattdessen darin, dem Browser eine viel günstigere Struktur für hardwarebeschleunigtes Kompositing zu bieten:

  • SVG-Inhalte statisch halten
  • unabhängig animierte Elemente isolieren
  • HTML-Ebenen animieren
  • transform für Bewegung/Vergrößerung/Rotation bevorzugen
  • unnötig aufwendige Neuzeichnungsoperationen vermeiden
  • will-change bei Bedarf verwenden

Zum Beispiel:

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

Dieser Ansatz funktionierte in meinem Anwendungsfall besonders gut und beseitigte das mobile Ruckeln, das ich bei der ursprünglichen SVG-Animation beobachtet hatte.


Den benutzerdefinierten Splash aktivieren oder deaktivieren

Das Plugin bietet auch eine Seiteneinstellung custom_splash_html_builder_enabled.

Wenn deaktiviert, wird der Standard-Splash-Screen von Discourse verwendet, unabhängig davon, ob benutzerdefiniertes HTML oder CSS konfiguriert wurde.

Dies bietet einen zusätzlichen Sicherheitsschalter, um den benutzerdefinierten Splash temporär zu deaktivieren, ohne das gespeicherte HTML/CSS zu löschen.

Der benutzerdefinierte Splash wird nur gerendert, wenn beide Bedingungen erfüllt sind:

custom_splash_html_builder_enabled = true
splash_custom_html ist nicht leer

Andernfalls wird die aktuelle zentrale Splash-Implementierung von Discourse verwendet.


Am wichtigsten ist, dass es eine Möglichkeit bietet, einen benutzerdefinierten animierten Splash zu erstellen, der auf Mobilgeräten viel besser funktioniert, indem er HTML-Ebenen um statische SVG-Inhalte herum animiert, anstatt das SVG selbst direkt zu animieren.

8 „Gefällt mir“

Hallo :waving_hand:

Ich habe data-color-scheme zum Abschnitt #d-splash hinzugefügt, sodass du Splash-Logos für helle und dunkle Designs leicht einrichten kannst. DEV: Implement dynamic color scheme for splash section · VaperinaDEV/custom-splash-html-builder@ba6641b · GitHub

<%- splash_forced_scheme = (dark_color_scheme? || forced_dark_mode?) ? "dark" : (forced_light_mode? ? "light" : nil) %>

<section id="d-splash"<%= " data-color-scheme=\"#{splash_forced_scheme}\"".html_safe if splash_forced_scheme %>>

Somit erhält #d-splash ein data-color-scheme="dark" / "light"-Attribut, wenn ein helles oder dunkles Design explizit erzwungen wird, und bleibt nur dann ohne Attribut, wenn beide Designs aktiviert sind und das Betriebssystem die Entscheidung treffen soll.

Die Lösung besteht darin, diesem Attribut in den benutzerdefinierten CSS-Regeln Vorrang vor der Media-Abfrage zu geben und nur dann auf prefers-color-scheme zurückzugreifen, wenn das Attribut fehlt.

Beispiel:

Benutzerdefiniertes HTML

<!-- HELLES DESIGN -->
<div class="custom-splash-light">
  <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>

<!-- DUNKLES DESIGN -->
<div class="custom-splash-dark">
  <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>

Benutzerdefiniertes CSS

#d-splash .custom-splash-dark {
  display: none;
}

/* Betriebssystem entscheidet — nur wenn kein Design erzwungen ist */
@media (prefers-color-scheme: dark) {
  #d-splash:not([data-color-scheme]) .custom-splash-light {
    display: none;
  }
  #d-splash:not([data-color-scheme]) .custom-splash-dark {
    display: block;
  }
}

/* Erzwungenes Design hat immer Vorrang, unabhängig vom Betriebssystem */
#d-splash[data-color-scheme="light"] .custom-splash-dark {
  display: none;
}
#d-splash[data-color-scheme="dark"] .custom-splash-light {
  display: none;
}
#d-splash[data-color-scheme="dark"] .custom-splash-dark {
  display: block;
}

Da :not([data-color-scheme]) einfach nicht mehr passt, sobald das Attribut vorhanden ist, gibt es keinen Konflikt in der Spezifität zwischen den beiden Regelsets, und das erzwungene Design hat immer Vorrang.

2 „Gefällt mir“

Cool, das ist etwas, was ich vor Jahren vorgeschlagen/habe suchen lassen, um Branding/Bild zu steuern, wenn die Verbindung langsam ist, damit der Benutzer orientiert und verankert ist.

Auf den ersten Blick ist dies noch mehr, als ich mir vorgestellt habe. Ich freue mich darauf, es irgendwann auszuprobieren. Großartige Arbeit!

2 „Gefällt mir“