Case Study
Recently while I was browsing Instagram stories on my phone, a certain ad by Kopidate caught my eye. Despite having 'date' as part of its name, I thought it was interesting that they say it is not a matchmaking site but for humanising 1:1 connections. The "human" part was something that I felt resonating. So I clicked on it to visit the site, but oh boy, it was yet another website that is plagued by the FOUC problem. Observe the difference in styling from when the page just loaded and when the page finishes loading.
This phenomenon is known as Flash of Unstyled Content (FOUC), and it is a topic covered by a question included in the hp5b Frontend Interviewer Questions.
What is Flash of Unstyled Content?
FOUC is the short moment of truth when you catch your friend turning their head without the zoom filter applied - and often, it is aesthetically less appealing, just like the example above before the transition happened. FOUC poses a very bad user experience, especially for new visitors who are not aware of this phenomenon. They might even think that your website poses a security threat (my mom asks me that a lot for new sites she visit). Even for users who know of this phenomenon, by its existence, the lag and abruptness of the transition decreases the legitimacy and professionalism of a website.
Why does the Flash of Unstyled Content happen?
Dave Hyatt, an author from Webkit, the open-sourced web browser engine used by Safari sheds the light on the FOUC problem in great detail in this article.
In particular, the "general how":
This situation occurs whenever a Web browser ends up showing your Web page’s content without having any style information yet.
and the "exactly how":
when/how a browser ends up committing the crime of FOUCing depends heavily on how the browser’s engine is architected and on interesting assumptions made by Web site authors when designing their sites.
The rest of his article focuses on how to mitigate FOUC specifically for browsers that implement web engines, and for the typical web developer, accounting for how the web browser engine works is usually out of their usual scope of work, so we will not discuss that here.
Instead, let's see if we can dig deeper into the latter part which was previously highlighted by the author to avoid FOUC - assumptions made by web site authors when designing their sites.
Revisiting the Case Study
For emulating what I saw for Kopidate on my mobile but on my laptop's browser, at the Google Chrome Inspector Network Tab:
- Changed the network to Fast 3G mode
- Disable cache
- Start recording the process
- Observe the logs
You can try it out too 👇
In the .GIF animation below, you can see that it took roughly 11s for Fast 3G mode to load finish. The transition felt very laggy and the time it took to load is simply painful for many mobile users.
So what is the device's browser doing in that 11s? A few observations as a normal user:
- text styles changing gradually
- brand image appearing
- text and button being moved down
- a big flash of red background image and changing of the entire style shown in viewport
And if you're playing around the network logs, you would then roughly understand what is causing the above to happen. For those that are not familiar with the network inspector, although the names of the files that are being retrieved are some randomized junk and not meant to be understood, we can still see the type of the data that is retrieved e.g. base64 is usually image. Then we can click such files to see which image(s) are exactly being retrieved.
Interestingly, it was spending the majority of the first 2s - 10s fetching and loading a lot of other images that the user cannot see beyond their device viewport. It was only in the last second before it finally fetched the red background portion and show the intended layout of styles correctly in the area that the user's device viewport covers. This probably indicates the presence of some css mask property where the text will appear differently based on the background image, as the web designer probably assumed the background image is always there and didn't create a fallback for the text styling in the absence of the background image.
For reference, I was previously using 5G data network to see that the abrupt transition on my phone happening in 1s. So that means that depending on the network speed, the user's experience will be different as well. In summary, the negligence for both the FOUC problem and performance would very likely result in a major turn off of web applications, possibly turning away many potential users.
Now the finale: how to FOUC off!
For vanilla websites that only uses HTML, CSS with no shiny framework
At the start of the .html file, put a style for hiding your site
<!doctype html>
<html>
<head>
<style>html{visibility: hidden;opacity:0;}</style>
<link rel="stylesheet" href="style1.css" />
<link rel="stylesheet" href="style2.css">
<link rel="stylesheet" href="style3.css">
At the end of the last css file to be loaded i.e. style3.css
html {
visibility: visible;
opacity: 1;
}
✅ The above guarantees that your html does not show until all the css files have been loaded. So there will not be a shitty transition happening.
✅ This is also a recommended solution for supporting devices/browsers that don't have Javascript enabled.
❌ If your website that has a lot of static content, even your fast 3G users will still be staring at a blank screen for quite some time. Do consider integrating with browser level image lazy-loading for a more holistic solution!
If you are using a shiny component framework like React, Vue, Angular etc
A simple fix is to do something similar to the solution provided above for vanilla websites, but using component state management.
An example with React without fallback 📘
What it does is that the side effect hook ensures that the component is mounted before rendering. And if it is mounted, that means that sequentially in the code, the import statement for the stylesheet would have been triggered before the code block for component, ensuring that it gets the stylesheet.
import './app.css';
import { useState }, React from 'react';
const defaultState = {
loading: false
}
const AppWithoutFallback = () => {
const [loading, setLoading] = useState(defaultState.loading);
useEffect(() => { setLoading(false) }, []);
return (
<div className="app" style=style={{ visibility: this.state.loading? 'hidden' : 'loading' }}></div>
)
}
Another example for alternative fallback layout that is not dependent on CSS files. Of course for this you have to design it so that when it switches between the states, the transition doesn't look super abrupt.
import './app.css';
import { useState }, React from 'react';
const defaultState = {
loading: false
}
const AppWithFallback = () => {
const [loading, setLoading] = useState(defaultState.loading);
useEffect(() => { setLoading(false) }, []);
return (
loading
? <div>Fallback content</div>
: <div className="app-container"></div>
)
}
And again, for the above, you can incorporate the code with some lazy loading libraries too to improve performance - these will be provided in the resources section later.
Conclusion
Thank you for reading this article!
If you enjoyed reading, please leave some reactions 💌
I will be very happy to receive any feedback from you too 🌻
More resources
If you find this article to be too short, feel free to browse through the resources that I've looked at below too.
- CSS Mask: https://web.dev/css-masking/
- Native Lazy Loading: https://web.dev/browser-level-image-lazy-loading/
- React libraries that help with performance
- Ready Lazy Loading Images: https://levelup.gitconnected.com/lazy-loading-images-in-react-for-better-performance-5df73654ea05
- React loadable: https://github.com/jamiebuilds/react-loadable
- Side-server rendering with styled-components as a possible solution to FOUC https://cleverbeagle.com/blog/articles/look-for-simple-fixes-first
- StackOverflow thread on FOUC https://stackoverflow.com/questions/3221561/eliminate-flash-of-unstyled-content/43823506
Top comments (3)
Guess it should be "loading: true" in defaultState
Thanks for article.
I can't understand one thing.
CSS is render-blocking by default, as written here: web.dev/critical-rendering-path-re... .
So why the solution is to hide html through styling? If stylesheets are in head tag, this is what the browser do, isn't it?
It seems to me, that problem with example relates to images, that are expected to overlap another content.
The solution is not effective from an optimisation perspective.