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
- Defect:
- Pre-render: Launch a browser, generate HTML, display it and replace it when the page loads.
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