To make web pages appear faster, it’s officially called First Paint, First Paint, or FP, or simply white screen Time, which is the Time between entering a URL and actually seeing the content (TTI, Time to Interactive, doesn’t have to be). Of course, the shorter the time, the better.

Note, however, that there are two metrics related to the First screen other than FP, called FCP (First Contentful Paint, Meaningful content painting) and FMP (First Meaningful Content Painting). While these concepts can be confusing, there’s only one thing we need to know: first screen time FP does not require content to be real, effective, meaningful, and interactive. In other words, show users anything they want.

That’s the mystery of the title: “It looks”. Yeah, it just looks faster, but it’s still the same. So this article is not about performance optimizations, it’s about gimmicks, but it can actually improve the experience. Performance optimization, for example, is about improving your internal function; Here are some moves you can use to bluff your opponent in the first place.

And that’s what I’m going to talk about next, which is called the Skeleton screen, or Skeleton. You may not have heard of the name, but you can’t fail to see it.

What does the skeleton screen look like

This is probably the most common form, using grey rectangles of various shapes to simulate images and text. Some apps will also use circles, but the emphasis is to be similar to the actual content structure, not too different.

If you want to pursue effect, you can also add animation (such as ripples) on the surface of the color block to display a dynamic effect, which is a tribute to Loading.

On image-heavy sites, this can be a good experience, as images tend to load slowly. The placeholder image in the illustration above uses a low pixel image, meaning that the general color scheme and variation are consistent with the actual content.

If such a low pixel image cannot be generated, a slightly degraded solution is to use an algorithm to get the main color of the image and use a pure color block for placeholder.

Further down, you can also use the same station image throughout the station, or directly a uniform color block. It’s certainly not as effective as the above two, but it’s better than nothing.

The skeleton screen is completely customizable, so it’s up to your imagination. You can do circular, triangular, three-dimensional, but the “space” determines its characteristics: it can’t be too complicated, it has to be the first time, the fastest to show.

What are the advantages of skeleton screen

In general, the advantages of skeleton screens are:

  1. Pre-render content at the beginning of page loading to enhance the sensory experience.

  2. In general, the skeleton screen and the actual content are similar in structure, so the subsequent switch is not too abrupt. This is different from the traditional Loading GIF, which can be regarded as an upgraded version.

  3. Simple CSS support is required (lazy loading of images may also require JS), no HTTPS protocol is required, no additional learning and maintenance costs.

  4. If the page is componentized, each component can define its own skeleton screen and switch timing according to its own state, while maintaining the independence between components.

Where can skeleton screens be used

Today’s WEB sites have roughly two rendering modes:

The front-end rendering

With the introduction and popularity of Angular/React/Vue in recent years, front-end rendering has become dominant. This model is also known as a Single Page Application (SPA).

The mode of front-end rendering is that the server (mostly static) returns a fixed HTML. Usually this HTML contains an empty container node and nothing else. After the internal JS contains routing management, page rendering, page switching, binding events and other logic, so it is called front-end rendering.

Because there is so much to manage on the front end, JS is often large and complex, and takes a lot of time to execute. Skeleton screens are a great backup until JS renders the actual content.

The back-end rendering

Before this wave of front-end rendering became popular, early traditional websites used a model called back-end rendering, in which the server returned the HTML page directly to the site, already containing all (or most) of the DOM elements of the home page. Most of the JS functions are to bind events and define the behavior after user interaction. A small amount adds/modifies some DOM, but it doesn’t hurt.

In addition, the front-end rendering mode is seO-unfriendly because the HTML it returns is an empty container. If a search engine doesn’t have the ability to execute JS (called Deep Render), it doesn’t know what your site is, and it won’t be able to rank it in search results. This is unacceptable to most sites, so the front end framework has introduced SSR (Server Side Rendering) mode. This pattern is similar to traditional websites in that the HTML returned contains all of the DOM instead of the front-end rendering. In addition to binding events, the front-end JS does one more thing called ‘activation’, which I won’t go over here.

In both traditional mode and SSR, you don’t need a skeleton screen for backend rendering. Because the content of a page exists directly in HTML, there is no room for skeleton screens.

How to use skeleton screen

Having discussed a wave of backgrounds, let’s see how to use them. First, ignore the implementation details and look at the idea first.

Implementation approach

It can be roughly divided into several steps:

  1. Inject skeleton screen HTML into what should be empty container nodes.

    Skeleton screen in order to show as soon as possible, fast and simple, so skeleton screen mostly use static pictures. And compiling images into Base64 encoding saves network requests, making skeleton screens faster and more efficient.

    <html>
        <head>
            <style>.skeleton-wrapper { // styles }</style>
            <! Declare meta or introduce other CSS -->
        </head>
        <body>
            <div id="app">
                <div class="skeleton-wrapper">
                    <img src="data:image/svg+xml; base64,XXXXXX">
                </div>
            </div>
            <! -- reference JS -->
        </body>
    </html>
    Copy the code
  2. Clear the skeleton screen HTML before executing JS to start rendering the actual content

    In the case of Vue, the content can be emptied before mount.

    let app = newVue({... })let container = document.querySelector('#app')
    if (container) {
        container.innerHTML = ' '
    }
    app.$mount(container)
    Copy the code

These two steps don’t involve complicated mechanics or high-end apis, so they’re easy to use. Get started!

The sample

I wrote an example to quickly show the skeleton screen, the code is here.

  • index.html

    The skeleton screen is included by default, with styles inline (added to the header with a

  • render.js

    It is responsible for creating DOM elements and adding them to , rendering the actual content of the page, to simulate common front-end rendering modes.

  • index.css

    A style sheet for the actual content of the page, without the skeleton screen style.

The three files of the code each do their job, with the above implementation ideas, should be very well understood. You can see the effect here.

Because the logic of this example is too simple, the actual front-end rendering framework is much more complex and includes more than just rendering, including state management, route management, virtual DOM, etc., so the file size and execution time are much larger and longer. Turning the network to “Fast 3G” or “Slow 3G” can be a little more realistic when we look at examples.

Oddly enough, the skeleton screen (which is a centered blue square image with a highlighted white line sliding repeatedly) was barely visible after a few attempts to refresh the address. Is there something wrong with our implementation?

Browser mystery: Reduce reordering

In order to eliminate the omission and interference of the naked eye, we use the Performance tool of Chrome Dev Tools to record what just happened. The screenshot is as follows :(the network setting is “Fast 3G” in the screenshot)

We can clearly see three time points:

  1. The HTML load is complete. While parsing the HTML, the browser found that it needed to reference two external resources, index.js and index. CSS, and sent a network request to obtain them.

  2. Once successful, execute the JS and register the CSS rules.

  3. JS 1 renders the actual content naturally and applies style rules (random color bars).

What about our skeleton screen? As expected, the skeleton screen should appear between 1 and 2, which means that the skeleton screen should be rendered at the same time as the JS and CSS are fetched. It was a good idea to inject skeleton HTML into index.html and separate CSS from index.CSS, but browsers didn’t buy it.

It really depends on the order in which the browser renders.

I’m sure you’ve all packed your suitcases. When we are packing suitcases, we will arrange the luggage reasonably according to the size of each bag. The big one and the small one will be combined to fill one layer and then put the one on top. Now all of a sudden someone comes to you and says, you don’t need to bring your computer, you need to bring two more clothes, you can’t bring so many bottles of mineral water. In addition to wanting to hit him, repacking inevitably requires unpacking and repacking. In browsers this process is called reflow, and that bad idea is a newly loaded CSS. Obviously, the cost of rearranging is high.

Practice makes perfect, and you can figure out a solution once you’ve sorted out the boxes. Since every CSS file loads can trigger a redraw, can I wait until all CSS loads and render together? Because of this, browsers wait for all the CSS in the HTML to load, register, and apply styles together, trying to do the work at one time, rather than repeatedly rearranging. It looks like the browser designer traveled a lot, because it was a good optimization idea, but it didn’t work on the skeleton screen.

To show the skeleton screen as soon as possible, we separated the skeleton screen style from index.css. Unbeknownst to the browser, it thinks the skeleton screen’s HTML still relies on index.css, so it must wait for it to load. After it is loaded, render. Js is almost finished loading and started to execute, so the skeleton screen HTML has been replaced, naturally can not be seen. And waiting for JS and CSS to load is still a blank screen, skeleton screen effect is greatly reduced.

So what we’re going to do is tell the browser, go ahead and draw the skeleton screen, it doesn’t have anything to do with index.css. So how do I tell it?

Tell the browser to render the skeleton screen first

When we refer to CSS, we use the syntax It is pre-loaded and cached when the browser is idle. Later use can save a network request.

This seemingly unrelated technique will come in handy here, because preloaded resources will not affect the current page.

This way we can tell the browser to forget about index.css and draw the skeleton screen. After the index.css is loaded, apply this style. Specifically, the code is as follows:

<link rel="preload" href="index.css" as="style" onload="this.rel='stylesheet'">
Copy the code

The core of the method is that changing the REL allows the browser to redefine the role of the tag from preloading to page-style. (In addition, there are also articles using the method of modifying media, but the browser support is low, not to expand here. In this case, the browser will render the skeleton screen before the CSS is completed (because the CSS is still preload, which is used later and doesn’t interfere with the current page). After the CSS has loaded and modified its rel, the browser reapplies the styling.

Have to consider the points of attention

In fact, it’s not like you change rel=”stylesheet” to rel=”preload” and you’re done. We still have a lot of things to think about before we can actually put it into production.

Compatibility considerations

First, inside we use onload, which is JS. In case the user’s browser does not have scripting enabled, we need to add a fallback. (Although this may not matter for single-page applications, since the actual content of the page cannot be rendered without a script)

<noscript><link rel="stylesheet" href="index.css"></noscript>
Copy the code

Second, rel=”preload” is not without compatibility issues. For browsers that do not support Preload, we can add some polyfill code (to get a consistent effect across all browsers).

<script>
/ *! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */
(function(a){ ... }());
</script>
Copy the code

The compression code for Polyfill can be found in Line 29 of the Lavas SPA template.

Load order

Unlike traditional pages, our actual DOM is generated through render.js. So if JS executes before CSS, it will jump. So we need to be very strict with CSS before rendering.

<link rel="preload" href="index.css" as="style" onload="this.rel='stylesheet'; window.STYLE_READY=true; window.mountApp && window.mountApp()">
Copy the code

JS exposes a mountApp method for rendering the page.

// render.js

function mountApp() {
    // Inside the method is to add the actual content to 
}

// Call the method directly to render
// mountApp()

// Switch to window to be called by CSS
window.mountApp = mountApp()
// If the JS load is later than the CSS load, perform the rendering directly.
if (window.STYLE_READY) {
    mountApp()
}
Copy the code

If CSS loads faster, set window.STYLE_READY to allow JS to be executed immediately after loading. If JS is faster, don’t execute it yourself and leave it to CSS onLoad.

Empty the onload

The developers of loadCSS suggest that some browsers will restart onLoad when the rel changes, causing the following logic to go twice. In order to eliminate this effect, we will add this. Onload =null to onload.

The final CSS reference

<link rel="preload" href="index.css" as="style" onload="this.onload=null; this.rel='stylesheet'; window.STYLE_READY=true; window.mountApp && window.mountApp()">

<! Repeat for easy reading -->
<! -- this.onload=null -->
<! -- this.rel='stylesheet' -->
<! -- window.STYLE_READY=true -->
<! -- window.mountApp && window.mountApp() -->
Copy the code

Effect after modification

The modified code is here, and the access address is here. (For simplicity, I’ve left out code that handles compatibility, namely

Performance :(still using the network Settings of “Fast 3G”)

This time the skeleton screen is visible to the naked eye while render. Js and index.css are still loading. In the case of screenshots, the skeleton screen was displayed for about 300ms, accounting for about half of the total network request time.

As for why the skeleton screen is not displayed as soon as the HTML is loaded, it takes about 300ms to display the skeleton screen. From the graph, it takes about 300ms for the browser ParseHTML to display. (Maybe simplifying the skeleton screen would help.)

Multi-skeleton screen support

In general, it is unlikely that all pages on a site will be of the same presentation type. For example, the home page and internal page will be very different in terms of presentation style, and for example, the list page will be similar to the search page (may have lists), but will be very different from the details page (may be products, services, personal information, blog posts, etc.). But there is only one index.html for a single page application, and all the changes are made by the front-end rendering framework inside the container node. Therefore, if the skeleton screen is directly injected into index.html, all pages will use the same skeleton screen, which makes it difficult to achieve the goal of “similar to the actual content structure”, and the skeleton screen degenerates into Loading.

To support multiple skeleton screens, we need to do judgment logic in index.html (separate from body JS), specifically:

  1. Write all the HTML and styles for all types of skeleton screens to index.html

  2. Add an inline script

This makes index.html a little bigger, but it still feels like the benefits outweigh the costs, and I think it’s worth it.

Afterword.

This optimization point was first discovered and completed by my former colleague Xiaop in the development of Lavas SPA template, and the Issue is recorded here. I built on his work and made a more straightforward example of separating the Lavas and Vue environments to make the screenshots as easy to understand and read as possible. Thank you very much for his work!

In addition, I used pure handwritten HTML and CSS for the compilation of skeleton screen, not only to show logic, but also to apply other conventional pages independently of a single page. Of course, this may cause a little inconvenience to developers, so we need to introduce the vue-skeleton-webpack-plugin for xiaop’s students. Its function is to take the skeleton screen itself as a Vue component, with separate routing rules to unify the development experience in the Vue project, and finally use Webpack to distinguish and inject when packaging and building, for the students who use Vue + Webpack development can have a try.

Refer to the article

  • Make skeleton screen render faster

  • Loading CSS without blocking render – Using media modification to do so.

  • Filamentgroup /loadCSS – also uses the method of modifying rel and provides preload polyfill