What are Skeleton pages? And how can you implement them in React?
We’ve all been there.
You click a button expecting the next page to instantly fill your screen with content, only to be met with a dreaded loading icon — seconds become minutes as you frantically reload the page in a bid to get what you asked for.
My point being, nobody likes to wait…
In an aim to mitigate this torture emerged a clever visual illusion that seems to reduce the perceived loading time. Introducing **skeleton loading pages — **which are essentially a blank version of the page where information is gradually loaded.
Skeleton loading screens are widely used across pretty much all your favourite websites:
Facebook & YouTube
The user is instantly greeted with the skeleton as it pulses with grey shapes which mimic the page layout. The content requested replaces the placeholdersas they become available, until the skeleton’s job is done — creating the illusion of an instant transition.
There are some packages out there that you can use, for example React Loading Skeleton. However, this will add dependencies to your codebase and are not as customisable as if you were to develop your own. The rest of this guide will show you how to implement skeleton loading with styled-components, but the same principles can be applied with vanilla CSS.
The approach we went with for implementing skeleton loading pages is not to create a separate, dedicated skeleton page but rather a built-in ‘skeleton state’ of a component. This is a more robust and scalable solution that will allow for more flexible loading patterns across different pages.
Let’s start by making a simple skeleton component that can be reused across pages. First, let’s add some styling for the pulse of the skeleton. The reason we separate the pulse out, is so I can use it for more shapes (e.g. circles, rectangles, etc.) if needed.
const SSkeletonPulse = styled.div`
display: inline-block;
height: 100%;
width: 100%;
background: linear-gradient(-90deg, #F0F0F0 0%, #F8F8F8 50%, #F0F0F0 100%);
background-size: 400% 400%;
animation: pulse 1.2s ease-in-out infinite;
@keyframes pulse {
0% {
background-position: 0% 0%;
}
100% {
background-position: -135% 0%;
}
}
`;
Next, add some styling for the skeleton line. Skeleton components become more robust if they can inherit some styling from their parent element. A really effective trick is to use the line height of parent element to set the size of the skeleton component — this is done by using the ::before selector and filling it with empty content *(*a string, “\00a0”).
const SSkeletonLine = styled(SSkeletonPulse)`
width: 5.5em;
border-radius: 5px;
&::before {
content: "\00a0";
}
`;
As you can see above, instead of having to create three skeleton lines with different sizes, the same skeleton line inherits three different sizes from the respective parent elements.
We can then export our skeleton line to be used across pages.
export const SkeletonLine = () => (
<SSkeletonLine />
);
Now we have our skeleton, we need to implement it. As mentioned, the approach we use is to create a ‘skeleton state’ for the desired component. The implementation will depend on what data your page is using but we want the skeleton to render when the data we need is undefined and render the page once the data has been loaded. For example:
if (data === undefined) {
const profileContent = (
<Profile>
<ProfileName><SkeletonLine /></ProfileName>
<ProfileBio><SkeletonLine /></ProfileBio>
</Profile>
);
} else {
const profileContent = (
<Profile>
<ProfileName>{username}</ProfileName>
<ProfileBio>{userBio}</ProfileBio>
</Profile>
);
}
return profileContent;
You could also use a ternary but I think it’s clearer to use if statements when components become more complex. This method allows you to import your skeleton to any component and it seamlessly build entire skeleton pages quickly.
If you want to build a more customisable skeleton, you can allow it to take in props that change the styling of the skeleton. For example, these skeletons work well on white/neutral backgrounds but not so well when the background is darker. I want to be able to make my skeleton line translucent for some pages:
const SSkeletonPulse = styled.div`
display: inline-block;
height: 100%;
width: 100%;
background: ${props =>
props.translucent
? css`linear-gradient(-90deg, #C1C1C1 0%, #F8F8F8 50%, #C1C1C1 100%)`
: css`linear-gradient(-90deg, #F0F0F0 0%, #F8F8F8 50%, #F0F0F0 100%)`};
background-size: 400% 400%;
animation: pulse 1.2s ease-in-out infinite;
@keyframes pulse {
0% {
background-position: 0% 0%;
}
100% {
background-position: -135% 0%;
}
}
`;
export const SkeletonLine = () => (
<SSkeletonLine translucent={translucent} />
);
So, if I want to make the skeleton line translucent, all I have to do in the SkeletonLine element is:
<SkeletonLine translucent={true} />
Skeleton loading pages have become the go-to solution for all your loading transition needs. Some research validates the idea that the perceived loading time is shorter for skeletons as opposed to spinners, for example. But before you dive in to skeletons, there are some caveats:
You are increasing the amount of code you have — your components will need an additional layer of logic to handle what should be rendered and when. This will add a significant amount of code to what was originally a simple React component and could make things harder to debug. Also, as mentioned above, if you decide to use a package, you are adding more dependencies to your codebase, which increases security risks and is less customisable than implementing a simple solution yourself.
Changes to components inevitably mean changes to the skeleton. The method outlined above is robust, however, if you change the layout or add features, you also need to make sure the skeletons are changed to reflect that.
Although some research validates skeleton loading pages, there has also been skepticism around their effectiveness; citing small sample size and lack of variation as concerns about studies conducted — there have even been conflicting reports stating that skeleton pages may perform worse for perceived loading time.
Regardless, the use of skeleton loading pages is everywhere and is quickly becoming the expectation of what the ‘loading experience’ is. As you can see, it’s not hard to do, so give it a go, experiment with different approaches and decide if they are just a trend or if they really do work…
#reactjs #javascript #web-development