Stabilize Layout on Navigation. (content layout shift?)
Jieiku opened this issue · comments
For most of the page its probably ok to have some content layout shift, but for the navigation menu at the top it is kinda jarring.
I included a video that hopefully shows what I mean:
You should be able to navigate between pages without that flicker, these types of problems can be tough to nail down sometimes, it often comes down to a tradeoff between render blocking resources and page load speed.
I have not tried to find the exact cause, I was just checking out your theme after seeing the pull request for the themes gallery.
Other than the flickering menu I think this theme is VERY CLEAN, and looks great!
hmmm seems it may only be an issue in Firefox? it seems fine in Chrome.
Looking into it!
Thanks for reporting this and for your positive comments about the theme.
It looks like this is unavoidable in Firefox. Luckily, it works fine on Safari and Chromium based browsers.
The issue is present on other sites such as MDN Web Docs (try switching between References/Guides on the top left).
I've tried changing the way the font loads in the CSS, but the issue persists. The only way to fix this in Firefox, as far as I know, is to use a font that's locally installed.
Since most navigation, other than testing the theme, won't involve rapidly switching between pages, and even then the issue only appears in Firefox, I'd rather keep the custom font everywhere.
Hopefully a future Firefox update fixes this issue.
Looks like I was wrong. There are ways to fix this.
My first step was to use woff2 instead of ttf to improve loading speeds (#78).
Still looking into how to fully fix it (looks like Inline Data URI is the solution).
The options
Options I've considered and their main drawback:
Option | Main Drawback |
---|---|
Not using a custom sans-serif font entirely. | Acceptable on macOS, not so good on Windows. |
Loading the entire font (inline data URI) in the header. | Too slow; <100 score on Lighthouse. |
Loading a limited subset of the font in the header, and deferring the full font loading. | The issue persists when the full font is loaded. |
Loading a limited subset of the font in and for the header. | Flashing will persist if characters outside this subset are used in the header. |
I chose the last option, since it's the least problematic.
This requires an extra render-blocking loading of ~25KB that should only happen once (since it's in a CSS file, it can be cached). With this, the flashing effect of the header text will occur only when non-subset characters are used (in the header).
Creating the font subset
Here's how I created the inter_subset_en.css
file (inspired by Zach Leatherman and Trevor Harmon):
- Create the subset for English letters, numbers and a few symbols with fonttools:
pyftsubset Inter4.0-beta8.woff2 \
--text='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 .,?\!&-_~/' \
--layout-features="" --flavor="woff2" --output-file="inter_subset_en.woff2 --with-zopfli
-
Upload
inter_subset_en.woff2
to woff2base to get the base64 string (it's also possibly to get the base64 encoding locally by runningbase64 -i inter_subset_en.woff2
). -
Simplify and minify the output and save it as CSS.
This way we can easily create a subset of glyphs for different languages. For example, for a Spanish subset (inter_subset_es.css
), I adapted the --text=
part of the command to abcdefghijklmnñopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZáéíóúüÁÉÍÓÚÜ0123456789 .,?!&-_:;¡¿~/
. This subset will be loaded if default_language = 'es'
in config.toml
.
By checking the language of page/site we can load the appropriate subsets (to be expanded with multi-language support).
Extra
It's possible to create an extra slim subset by parsing the config.toml
file for the exact characters that will be shown on the header. I wrote a bash script that takes a config file and a font and creates the custom_subset.css
with the inlined font data.
I wrote about it here.
This script can be called through a Git pre-commit hook whenever the config.toml
file is modified (see the last section of the post).
By doing this, it's possible to create a custom subset that only contains the exact glyphs needed, which results in a much smaller file (12KB instead of 25).
Btw, do you know if this solution works? https://meowni.ca/font-style-matcher/ from this article https://css-tricks.com/css-basics-fallback-font-stacks-robust-web-typography/
Basically, you select a system font (that's already on a user's computer) and configure it in a way that would look&be sized the same like your custom font so that when the custom font loads, the flash is minimal (though for headers likely using a "tree-shaken" font is the best option)
Also, out of curiosity, why not preloading the font instead of adding it to as a base64-in-css? Seems like at least conceptually a simpler and more efficient option?
I wasn't familiar with the font style matcher. I played around with it and saw it's possible to use a similar-enough font so that the layout shift is minimal for the header. However, it would still be there.
This solution might make sense for the serif font (in articles). However, the fallback font, Georgia, is already quite similar to the default font (Source Serif).
Regarding preloading: from my local testing, it (greatly) minimizes the issue, but doesn't completely eliminate it. The current implementation is the only way I found to completely get rid of this issue.
Loading the font as a base64 effectively blocks the rendering of the page, ensuring the font is loaded before it's needed. This is why it's a good idea to create the subset.
Interesting, i thought preloading was supposed to have exactly the same effect - block rendering before it's loaded. Although in some brief testing I see no difference - with caching disabled, both font preloading and css embedding flash unstyled content
For example, this is your page reloading with cache disabled via browser dev tools (more visible on the bold title)
tabi.mov
Interesting. What browser and OS? I can't reproduce on Firefox, Chromium nor Safari.
That's both on Windows 10 Chrome 112 and Mac Vivaldi 6, with Safari (though an old v15) I only see the flash for the content, not the top nav bar
(that's all with cache disabled or reload from origin in Safari)
I managed to reproduce the flashing issue on Vivaldi (with cache disabled).
In any case, this seems to be a different issue (the original report only affects Firefox). Indeed, if cache is disabled, you can reproduce flashing (except on Firefox).
You may be right (I didn't test myself) that preloading has the same flashing effect as the current css/base64 load when cache is disabled. However, the original Firefox issue, which occurs with cache enabled, is not fixed with preloading (again, from my testing).
It would be lovely to find a way to ensure all browsers behave in the same way in this regard, but I'm not very optimistic.
For now, it seems the current implementation is the least problematic, all browsers and configurations considered.
This is an issue for static sites no matter how you slice it, the reason is FART. https://css-tricks.com/flash-of-inaccurate-color-theme-fart/
The way that I practically eliminate this in Abridge was by loading theme.js as the first file in the head, so that it was processed before anything else, and as quickly as possible. I also don't bundle this with anything else.
theme.js: https://github.com/Jieiku/abridge/blob/refactor/static/js/theme.js
theme.min.js: https://github.com/Jieiku/abridge/blob/refactor/static/js/theme.min.js
Also this is not an issues for "automatic" based themes that only rely on system/browser preference, this issue only presents itself when your trying to use localstorage + js to set the theme. (theme switcher functionality) On a static site this is really your only option as far as I know, alternatively you could use js to delay loading any pages until the localstorage had finished loading, but that is kinda messy and would be a problem for people that use noscript.
So in abridge I support all 4 modes: switcher, auto, dark, light:
The issue is similar to Flash of Unstyled Text during font load, which is why I decided to use the "system font stack" for abridge by default, while optionally allowing people to load fonts.
Interesting name. I fixed FART in #77 with inspiration from this commit: codeandmedia/zola_docsascode_theme@9485c9e.
To enhance the loading speed, I made the choice to divide the JavaScript logic into two sections. The initial part, which is placed in the header (blocking loading), exclusively handles setting the theme. On the other hand, the logic responsible for theme switching is deferred, so as to not hinder the page's loading process.
yep, I remember trying quite a few solutions, LOL!
I just read over how you solved the issue with the fonts, and I love that solution.
I load all of my sites icons from the css file as well, and I use a function to determine which icons to load.