镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Preload saved theme before first paint to avoid theme flash on reload - #315

Open
fbartels wants to merge 1 commit into
gamosoft:mainfrom
fbartels:fix/theme-flash-on-reload
Open

fbartels wants to merge 1 commit into
gamosoft:mainfrom
fbartels:fix/theme-flash-on-reload

Conversation

@fbartels

Copy link
Copy Markdown

Problem

On every page load the app paints with the default (light) theme for a moment before the user's saved theme is applied — a visible flash on reload.

The cause: the theme CSS isn't present in <head>. initTheme() runs during boot and applyTheme() fetches /api/themes/<id> asynchronously, then injects <style id="dynamic-theme">. Until that round-trip completes, the first paint uses the base styles.

Fix

Preload the saved theme synchronously in <head>, before Alpine renders — the same pattern the file already uses for the locale preload just above it.

  • Reads localStorage['noteDiscoveryTheme']; if there's no saved preference it does nothing, so default-theme users are unaffected.
  • Sets data-theme and injects the CSS into a <style id="dynamic-theme"> element. applyTheme() later reuses that same element, so nothing is applied twice.
  • Wrapped in try/catch; on any error it falls back to the existing async applyTheme() path.

One file, one concern, no new dependencies.

Testing

  • Built the Docker image and loaded it in a browser with a saved dark theme.
  • Measured getComputedStyle(document.body).backgroundColor at the first animation frame:
    • Before: first frame rgba(0, 0, 0, 0) (default) → final rgb(31, 41, 55) (dark) — i.e. the flash.
    • After: first frame rgb(31, 41, 55) == final rgb(31, 41, 55) — no flash.
  • Verified default (no saved theme) still loads normally via the early return.

@gamosoft

Copy link
Copy Markdown
Owner

Thank you @fbartels , I tried this locally and the flash is gone, honestly that never bothered too much, but then I complain about the SAME issue when I'm at my phone and using dark theme, a new tab flashes and blinds me completely. 🤣

A few things would be nice if you can implenmet them before merging this PR though:

  • In the new script in frontend/index.html, set data-theme only after /api/themes/ returns 200 and the CSS parses. It is set before the request today, and the theme CSS only applies to :root[data-theme="…"]. A missing or invalid saved id leaves that attribute in place with no CSS, so the page stays on the light fallback.
  • When localStorage has no noteDiscoveryTheme, preload the configured default theme instead of returning immediately. initTheme() uses that default (from DEFAULT_THEME or config.yaml), so a Dark default still flashes for anyone who never picked a theme. login.html already gets this value as DEFAULT_THEME. Do the same for index.html and use it when nothing is saved.
  • Use the same preload on login.html. That page still loads the theme with an async fetch after paint, so the login screen flashes. Its comment says it matches index.html. Set data-theme only after the request succeeds there too.
  • Download the theme once. applyTheme() in frontend/app.js fetches /api/themes/ again even when #dynamic-theme is already in place for that same id. Skip that second fetch and the CSS write in that case. Still update the #highlight-theme stylesheet, because the preload does not do that.

If you think you can do that in this PR then I'll wait to hear from you, otherwise I can merge this PR and tackle the pending things myself when I have some time. ;-)

Thank you very much.
Kind regards.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants