What is a skeleton screen?

Simply put, skeleton screen is to use some graphics for placeholder before JS code parsing is completed, and then replace it with real pages after the content is loaded.

As shown in figure:


Why use skeleton screens?

The most popular front-end frameworks (Vue, React, Angular) have a common feature, which is JS driven. The page does not display any content until the JS code is parsed.This is called a white screen. Skeleton screen can give people a feeling that part of the page content has been rendered. Compared with traditional loading effect, it can improve user experience to a certain extent. Especially in the slow network, more graphic information, the load of large data flow.


  • PS: There are other ways to solve this white screen
    • Pre-render: Launch a browser, generate HTML, display it and replace it when the page loads.
      • The downside: If the data is very real-time, such as a news list, it may be yesterday’s before the page is replaced. More suitable for static pages
    • Server rendering (SSR) : it is used to get the latest data from the server, such as doing some blogs and news
      • Defect:
        • It takes up a lot of server memory,
        • Since server-side rendering is just a string, it doesn’t know when the DOM will be put on the page. Some of the browser apis, such as those that manipulate the DOM, are not working
        • It would be a tragedy if the interface was down

Several schemes to achieve skeleton screen

  • Through the skeleton screen picture given by the designer
  • Write skeleton screen code manually using HTML+CSS
  • Automatically generate skeleton screen code

The realization idea of automatic skeleton screen generation


compiler.hooks.done.tap(PLUGIN_NAME, async() = > {await this.startServer(); // Start an HTTP server
   this.skeleton = new Skeleton(this.options);
   await this.skeleton.initialize(); // Start a headless browser

   const skeletonHTML = await this.skeleton.genHTML(this.options.origin); // Generate a DOM string for the skeleton screen
   const originPath = resolve(this.options.staticDir, 'index.html');      // The path of the packaged file
   const originHTML = await readFileSync(originPath, 'utf8');             // Read the contents of the packed file
   const finalHTML = originHTML.replace('<! --shell-->', skeletonHTML);    // Replace the contents of the packaged file with the generated skeleton screen
   await writeFileSync(originPath, finalHTML);                            // Writes the contents of the skeleton screen to the packaged file
   await this.skeleton.destroy();                                         // Destroy the headless browser
   await this.server.close();                                             // Disable the service
 })
Copy the code

Starting the HTTP Service

async startServer () {
  this.server = new Server(this.options); // Create a service
  await this.server.listen();             // Start the server
}
Copy the code

Start the puppeteer

 async initialize () {
   this.brower = await puppeteer.launch({ headless: true });
 }
Copy the code

Open a new page

async newPage () {
  let { device } = this.options;
  let page = await this.brower.newPage();
  // puppeteer. Devices [device]: puppeteer
  await page.emulate(puppeteer.devices[device]);
  return page;
}
Copy the code

Injecting the script that extracts the skeleton screen generates the skeleton screen code and corresponding styles

async genHTML (url) {
  let page = await this.newPage();
  let response = await page.goto(url, { waitUntil: 'networkidle2' }); // Wait for the network to load
  // If the access fails, the Internet is disconnected or something
  if(response && ! response.ok()) {throw new Error(`${response.status} on ${url}`);
  }
  // Create skeleton screen
  await this.makeSkeleton(page);
  const { html, styles } = await page.evaluate((options) = > {
    return Skeleton.getHtmlAndStyle(options)
  }, this.options);
  let result = `
    <style>${styles.join('\n')}</style>
    ${html}
  `;
  return result;
}
Copy the code

Replace the index. HTML in dist with the generated skeleton screen content

const skeletonHTML = await this.skeleton.genHTML(this.options.origin); // Generate a DOM string for the skeleton screen
const originPath = resolve(this.options.staticDir, 'index.html');      // The path of the packaged file
const originHTML = await readFileSync(originPath, 'utf8');             // Read the contents of the packed file
const finalHTML = originHTML.replace('<! --shell-->', skeletonHTML);    // Replace the contents of the packaged file with the generated skeleton screen
await writeFileSync(originPath, finalHTML);                            // Writes the contents of the skeleton screen to the packaged file
Copy the code

Close the headless browser and service

await this.skeleton.destroy();                                         // Destroy the headless browser
await this.server.close();                                             // Disable the service
Copy the code