| Summary | Discourse plugin that allows the splash screen to be customized using admin-defined HTML and CSS. | |
| Repository Link | https://github.com/VaperinaDEV/custom-splash-html-builder | |
| Install Guide | How to install plugins in Discourse |
Hello ![]()
I’ve created a small Discourse plugin that allows the splash screen to be customized using admin-defined HTML and CSS, without having to maintain a modified copy of Discourse’s core splash template.
The original motivation for creating this plugin was actually mobile performance.
I wanted to create a more sophisticated animated splash screen, but I found that the SVG animations supported by the current Discourse core splash implementation could become surprisingly problematic on mobile devices.
On desktop, the animation could look perfectly smooth, while on mobile it could become noticeably choppy, drop frames, lag, or even appear to stop during the animation.
After experimenting with different approaches, I found that moving the animation from the SVG itself to a surrounding HTML element, such as a <div>, made a very significant difference.
Instead of continuously animating the SVG contents, the SVG can remain static while the browser animates the containing HTML layer using CSS transforms.
This gives the browser a much better opportunity to handle the animation as a compositing operation using the device’s graphics hardware.
The result was a much smoother animation on mobile, without the stuttering and freezing I was seeing with the SVG-based approach.
That was the main reason this plugin was created.
Before (Animated SVG: The animation lagging and stopped)
After (Animated HTML: Smooth animation)
The problem with animating SVGs
The original splash implementation is perfectly fine for a simple logo or relatively lightweight animation.
However, once the animation becomes more complex, SVG rendering can become expensive.
For example, an animation directly applied to an SVG or its internal elements can require the browser to repeatedly process or repaint parts of the SVG during the animation.
On mobile devices, this can become particularly noticeable.
During testing, I saw cases where the animation would:
- become visibly choppy
- temporarily freeze
- appear to stop
- behave significantly worse than on desktop
The interesting part was that the same visual animation could behave very differently depending on what was actually being animated.
Moving the animation to an HTML layer
The approach that worked much better was to keep the SVG itself static and put it inside a normal HTML element.
For example:
<div class="logo-layer">
<svg viewBox="0 0 500 500">
...
</svg>
</div>
Instead of animating the SVG, the animation is applied to the container:
.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);
}
}
The SVG itself doesn’t change.
The browser can therefore handle the transformation of the HTML layer much more efficiently, and in supported cases promote it to a composited layer handled by the graphics hardware.
This produced a dramatically smoother result on mobile devices.
The important distinction is therefore:
Core approach:
SVG
└── SVG animation
└── SVG contents are animated
vs:
Custom approach:
HTML layer
└── SVG
└── CSS transform on HTML layer
└── compositor-friendly animation
This isn’t a guarantee that every animation will be GPU-accelerated. The browser ultimately decides how an animation is composited, but in my testing the difference was very noticeable.
Why I created the Custom Splash HTML Builder
Once I had this approach working, I also needed a way to actually build the splash screen around it.
The standard splash template doesn’t provide enough flexibility for this type of implementation.
For a more complex animation, I might need:
- multiple SVG layers
- multiple HTML containers
- independently animated elements
- custom CSS keyframes
- different animation timings
- custom positioning
- theme-aware colors
- completely different markup from the default splash
So instead of creating another hard-coded splash implementation, I decided to expose the visual part through two site settings.
The plugin adds:
splash_custom_html
The HTML/SVG markup rendered inside the splash screen.
splash_custom_css
The CSS used by the custom splash, including animations, keyframes, positioning and responsive behavior.
This makes the splash effectively customizable without having to modify the plugin source code every time the animation changes.
Built-in admin editor
The plugin also provides a small built-in admin editor for managing the custom splash.
It adds a dedicated Splash HTML Builder section in the Discourse admin interface, with separate editors for:
- Custom HTML
- Custom CSS
The changes can be saved directly from the admin interface without manually editing the corresponding site settings.
The underlying settings are still:
splash_custom_htmlsplash_custom_css
The editor is simply a more convenient interface for managing them.
This also means that the plugin doesn’t require modifying plugin source files whenever the splash animation needs to be changed.
Example
A custom splash can contain multiple independent layers:
<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>
And each layer can have its own animation:
.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);
}
}
The SVGs remain static while the surrounding HTML layers are animated.
This makes it possible to create considerably more complex splash animations while keeping the expensive animation work outside the SVG itself.
Why not simply override the core splash template?
Another important goal was to avoid maintaining a copy of Discourse’s core splash template.
A straightforward approach would be to override:
app/views/common/_discourse_splash.html.erb
and copy the current Discourse implementation into the plugin.
The problem is that this creates a maintenance burden.
If Discourse changes its splash implementation in a future release, the plugin would still contain the old version.
That could potentially result in:
- missing new core changes
- missing performance improvements
- broken behavior after a Discourse update
- having to manually compare the plugin template with core after every update
I wanted to avoid that entirely.
Core fallback
The plugin therefore supports a custom splash with a core fallback.
Custom HTML is configured
If:
SiteSetting.splash_custom_html.present?
then the plugin renders the custom splash.
Custom HTML is empty
If no custom splash has been configured, the plugin falls back to the current Discourse core splash template.
The plugin locates the actual core file from the running Discourse installation:
Rails.root/app/views/common/_discourse_splash.html.erb
and renders that implementation.
Conceptually:
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
This means the plugin does not carry a second copy of the core splash template.
Performance considerations
The plugin isn’t trying to claim that every CSS animation will magically become GPU accelerated.
The browser still decides how individual animations are rendered and composited.
The goal is instead to give the browser a much more favorable structure for hardware-accelerated compositing:
- keep SVG content static
- isolate independently animated elements
- animate HTML layers
- prefer
transformfor movement/scaling/rotation - avoid unnecessarily expensive repaint operations
- use
will-changewhere appropriate
For example:
.ring-layer {
will-change: transform;
animation: rotate 2.2s linear infinite;
}
This approach worked particularly well for my use case and eliminated the mobile stuttering I was seeing with the original SVG animation.
Enable or disable the custom splash
The plugin also provides a custom_splash_html_builder_enabled site setting.
When disabled, the standard Discourse splash screen is used regardless of whether custom HTML or CSS has been configured.
This provides an additional safety switch for temporarily disabling the custom splash without deleting the saved HTML/CSS.
The custom splash is only rendered when both:
custom_splash_html_builder_enabled = true
splash_custom_html is not empty
Otherwise, the current Discourse core splash is used.
Most importantly, it provides a way to build a custom animated splash that performs much better on mobile by animating HTML layers around static SVG content instead of directly animating the SVG itself.