Before the introduction
Hello big Ga good, I’m pushing ah AD line front liaokun. What I want to share with you today is the performance optimization of the first screen of the landing page.
background
Three months on the job
Leader: we have a landing page system. The loading speed of the first screen is a little slow. Please optimize it
Me :(humbly nods) good boss
Skillfully vscode-> file -> open workspace
Dude, heirloom project has me optimizing it
Let me kangkang this ancestral project who is still in use
But the needle does not stamp, a day PV more than two million projects, optimization of small problems are to go.
This is very difficult, do not make their ability is not too good, big change out of the problem will have to leave.
The only way to do that is to have a safe plan to optimize your page while limiting the risk of change.
Let’s start with the project review.
The project combed
There is a website building system in the business line, which constructs the landing page in the website building background (such as adding/deleting/moving components and configuring component content), and reads the JSON configuration generated during the website building of the management background in the rendering end of the landing page to generate the corresponding page.
The rendering side of the landing page is built by VUE, and the template file (index.html) is dynamically generated by the server side and the JSON configuration configured in the background is mounted to the window object.
After the browser receives the template file, it requests resources and constructs the resource start page, including sending asynchronous requests to obtain the information of the commodity, reading the LIST of JSON configuration rendering components mounted under the window, filling the component with commodity information, and completing the construction of the entire landing page.
Refer to the entire business process:
Process analysis
Refer to the page rendering process to find: when visiting the landing page URL, the browser sends a request to the server, it will go through the resource request -> execute JS -> page construction -> image request -> page rendering (reflow/repaint).
Because resources and pictures are on THE CDN, the loading speed can be very fast in the case of strong network. Although the first screen rendering is not particularly fast, it is also the speed that can be received.
Under weak network, limited by downlink speed, the bottleneck of first screen rendering is stuck in resource transmission.
There is also a problem with vue-lazyLoad in this case: Images in the page will first use the base64 placeholder to trigger the load event of the IMG tag, which will trigger the load event of the page before the actual image is loaded, affecting the accuracy of data burying point reporting (data burying point will be reported after a page exposure burying point after the page load event). This also makes the data collected by page performance monitoring inaccurate.
Bottleneck analysis
For browser rendering/parsing code, the speed of each process tends to be:
Js > Page Render > Resource load
For landing pages, the first screen usually contains two or three images.
Based on these two judgments, we can sort out the time calculation as follows:
- Under a strong network, resources (code/images) can generally be loaded within 50ms, so the page flow time can be ideal and crude
Template file requests (150ms) -> parallel requests for various JS/CSS resources (50ms * 6 speed can be considered as serial loading) -> JS execution/page drawing (50ms, -> Image request (50ms * 3) -> page trigger redraw/backflow to complete rendering (ignored)
This process is an ideal one, intended only to rough up calculations of page time, not to represent real business scenarios.
Through this process, it can be calculated that 650ms is needed to complete the first screen rendering of the landing page under the strong network; Reference pictures
- In the weak network, the TTFB time of each resource generally needs 500 ~ 600ms, plus the transmission time of the resource itself, the transmission cost of the resource with small volume will generally be 700 ~ 800ms, and the transmission cost of the resource with large volume (more than 20kB) is for example
Vue.min. js, packaged main.js, vendor.js and style files
It will generate 1 ~ 1.5s time overhead, and it will generate more than 1.5s transmission time for basic pictures above 50kB.
Template file request (800ms) -> various JS/CSS resource request (1.5s is the most parallelized request) -> JS execution/page drawing (50ms, -> Image request (1.6s is also loaded in parallel) -> page trigger redraw/backflow to complete rendering (ignored)
Through this process, it can be calculated that 3950ms is needed to complete the first screen rendering of the landing page under weak network; Reference pictures
Optimization idea
The common idea, of course, is to do server-side rendering or pre-rendering. The server rendering was abandoned in consideration of the risk of making major changes to the historical project; Pre-render works better for static pages, and is a bit of a catch for pages that need to be dynamically generated based on page ids and product ids. Therefore, the optimization ideas are focused on the following points:
- It can be found from the observation of the page time under the condition of weak network that the first screen image is dynamically generated and loaded by vue and LazyLoad, which results in the image request being sent after the completion of JS resource loading. We expect to make use of the feature of parallel resource loading to preload the images on the first screen when loading resources for the first time, saving the cost of 1.6s in the image request process described above. This step can be achieved by using the cache mechanism of the browser. When reading local cache resources (disk cache/memory cache), the time cost of 0~3ms is negligible.
- It is mentioned in the page flow that the picture in the landing page is obtained through the request interface. The direct thought is to send out the interface request in advance to get the page picture, extract the picture of the first screen for preloading. Synchronizing XHR is not a recommended practice, see Synchronous and Asynchronous Requests
- Note: Starting with Gecko 30.0 (Firefox 30.0 / Thunderbird 30.0 / SeaMonkey 2.27), Blink 39.0 and Edge 13, synchronization requests on the main thread are deprecated due to negative impact on the user experience.
- But developers usually don’t notice this problem, because in the case of a poor network or slow server response, the suspension simply shows that the synchronous XHR is now deprecated. Developers are advised to stay away from this API.
However, the asynchronous request will be placed in the asynchronous queue waiting for the main thread to be idle before it is executed. Even if the interface request is sent in advance, it will wait until the JS execution in the main thread is completed before it is processed. In fact, it is not very different from the front-end framework.
So the interface request in the landing page needs to be placed on the server.
This step is the premise of the whole optimization, no matter how much optimization is not to get the page picture in advance, the image request will be issued after js loading frame starts to build the page, and then render the page after the picture returns.
-
In theory, the first screen rendering speed of off-grid pages should improve very fast (in seconds). After solving the bottleneck of image loading speed, further optimization can be done. For example, split page components, render the first screen components first, and asynchronously render the rest of the components.
-
In the specific framework programming level, asynchronous events in Mounted are also executed before LOAD in the case of weak network, and some requests (such as buried points sent by Image) in asynchronous events are counted into load time. The goal of optimization is to complete the first screen rendering and load as early as possible, and do additional page logic after load. By determining the document.readyState value, the listener that mounts the readyState event while the page has not completed the readyState complete event does the additional logic after the page completes.
-
By observing the network map in the weak network condition above, it can be found that, different from the situation where only one first image is loaded in the strong network, the weak network not only sends the request for the first image, but also triggers the lazy loading request for the off-screen image. The lazy-load plugin is also modified to mount the image monitor after the page is complete. This prevents lazy-load from triggering the loading of images outside the first screen and lengthening the rendering time of the first screen.
Several tools and metrics for performance measurement will be introduced in the following sections, followed by specific optimization schemes and code.
How to observe page performance and some metrics
This optimization mainly uses three tools to observe performance, which are window.performance object, Network TAB and Performace TAB in Chrome DevTools.
Window.performance
This API is mainly used to trace some page performance data, report page performance and generate performance reports. Here mainly use the two properties, one is the window. The performance. The timing, another is the window. The performance. However, ().
window.performance.timing
Window. The performance. Timing object provides the following parameters
interface PerformanceTiming {
"connectStart":number;
"navigationStart":number;
"loadEventEnd":number;
"domLoading":number;
"secureConnectionStart":number;
"fetchStart":number;
"domContentLoadedEventStart":number;
"responseStart":number;
"responseEnd":number;
"domInteractive":number;
"domainLookupEnd":number;
"redirectStart":number;
"requestStart":number;
"unloadEventEnd":number;
"unloadEventStart":number;
"domComplete":number;
"domainLookupStart":number;
"loadEventStart":number;
"domContentLoadedEventEnd":number;
"redirectEnd":number;
"connectEnd":number;
}
Copy the code
From these parameters, you can calculate some performance of the page, such as
'Redirection time' -- (time.redirectend - time.redirectstart) / 1000; 'DNS resolution time '-- (time.domainlookupend-time. DomainLookupStart) / 1000; 'TCP handshake completion time '-- (time.connectend - time.connectstart) / 1000; 'HTTP response completion time '-- (time.responseend-time.requeststart) / 1000; NavigationStart -- (time.responseend-time.navigationstart) / 1000; -- (time.domcomplete-time.domloading) / 1000; DomInteractive - time.domloading) / 1000; 'script loading time - (time. DomContentLoadedEventEnd - time. DomContentLoadedEventStart) / 1000; 'onload event time '-- (time.loadeventend-time. LoadEventStart) / 1000; 'page full load time' -- 'Redirection time' + 'DNS resolution time '+ 'TCP handshake time' + 'HTTP request response time '+ 'DOM structure resolution time' + 'DOM load time ';Copy the code
window.performance.getEntries()
Window. The performance. However, () method returns an array that contains all HTTP requests in the page of data, data type and structure of each request with window. Performance. The timing
const PerformanceEntries: Array<PerformanceTiming>;
Copy the code
Use way and also the window. The performance. Timing is consistent, through various properties calculated between the specific performance parameters of an HTTP request. Given the resource specific request parameters, we can use these parameters to make some judgments, such as the loading time of the header. If the loading time of the first image is less than 200ms, it can be considered as a strong network state. After the page is loaded, images outside the first screen can be preloaded to provide a better page experience.
Chrome DevTools – Network
The Network TAB is arguably the most important tool for observing resource load status. Generally we pay attention to the size of the resource, Load state, Load time, DOMContentLoadeds and Load time of the page, etc. In this optimization, I pay more attention to the loading time of each resource, including the time when the request is issued and the status of each resource when the request is parallel. As the above optimization ideas said
We expect to make use of the parallel resource loading feature to preload the first screen image when loading the resource for the first time
At the same time, I also used the Network Throttling function provided by Network to simulate the Network situation under weak Network for page performance test.
Chrome DevTools – Performance
The Performance TAB is very powerful. After opening Record, it continuously intercepts page data and generates Performance analysis. In the new version of Chrome, Performance provides FP(First Paint), DCL(DomContentLoaded), FCP(First Contentful Paint), L(Load), LCP (largest contentful paint).
See DCL, L, FP, FCP, FMP, and LCP in Chrome Devtool Performance
FP(First Paint): The point in time when a page First appears after navigation that is different from the content before navigation.
FCP(First Contentful Paint): The point at which any text, image, non-blank canvas or SVG is drawn for the First time.
FMP(First Meaningful Paint): The point at which the “main content” of a page is First drawn.
LCP(Largest Contentful Paint): Visual area “content” the point at which the Largest visible element begins to appear on the page.
For the first screen optimization, we pay most attention to the time between the first request from the page (index.html template request) and LCP. This is the time between the user opening the page and seeing a relatively complete first screen. And the elapsed time from FP to LCP, which is the elapsed time from the user’s perception to the start of page rendering to the completion of the first screen rendering.
Optimize the start
Referring to the above optimization ideas and performance observation methods, start to do some optimization of the project.
See the Network analysis page loading process
Figure 4: Analyzing page requests using the Network tool
Check the problem of the page before optimization — switch to 3G to simulate weak network. After visiting the page, the loading time and time of each resource can be seen in the network overview:
The first 1600ms: all spent on requests for the template (the first file preview, which is something like index.html).
1600ms~2700ms: After the template file is loaded, the browser parses the file and sends a bunch of requests for resources through the outer chain tags in the file. This step is the main thing to do between.
When main.js(the ninth resource, the main_202006221554.js file) is loaded, the browser executes the code in main.js. Call new Vue() in main.js to create an instance of the Vue and begin the scaffolding of page elements.
2700MS: A blue vertical line appears in the overview that identifies the triggering of the DOMContentLoaded event. At about the same time, the selected ajaxDetail request in the diagram is issued as an asynchronous request in main.js that contains the address of some of the images used in the page.
3500ms~4800ms: the part circled in red box in the figure sends the request for the first screen image at about 3500ms and completes the loading of the first screen image after about 1300ms.
3600ms~5500ms: the part circled by yellow box in the figure sends several requests for buried points and statistical codes. After these requests are completed, a red vertical line appears in the overview, which indicates the trigger of the Load event.
The buried point of the first screen rendering is reported.
Refer to the optimization points of page loading process
Template resource loading optimization
You can see that the first template request occupies 30% of the Load time of the page. The Waiting (TTFB) time of a weak network request is often several times the Content DownLoad time.
Waiting (TTFB) is short for Time to First Byte. When the network condition is fixed, TTFB indicates the speed of the server response. Therefore, this step of optimization is more on the server side, such as increasing the page/data cache to speed up the response time, or deploying static pages to the CDN for faster response.
Content DownLoad directly identifies the time taken to DownLoad resources, which is positively correlated with resource volume and network status. When the network is fixed, compress the page volume, for example, enable GZIP compression.
There is a paradox: if we add a new script, do we load it externally or do we execute it inline in a template file? Without considering the maintainability of the template code, inlining the script must result in faster loading and execution time, because the external script will generate additional TTFB, and it has been observed that a 37KB external resource takes 190.90ms to download. On the other hand, 4KB of external resources can sometimes take 200ms or more to download — inline code is a better choice if you’re looking for extreme load speeds.
Asynchronous request optimization
Asynchronous requests improve the user experience of web pages so much that both existing specifications and Chrome have removed support for synchronous requests; But asynchronous requests are also the enemy of the first screen rendering: dynamic pages have to wait for the asynchronous request to return before the user can see the first screen. This is also a starting point for the current server rendering and pre-rendering scheme, where the data/content that was originally obtained in the client through asynchronous request is directly rendered and returned to the server.
In the actual optimization, I adopted a compromise scheme: the server side inserted the data originally obtained by the asynchronous request into the Window object, and the front end directly fetched the data mounted under the Window to take the original business logic.
Image loading node optimization
As mentioned in the optimization idea, we can use the browser’s cache strategy to optimize the image loading node.
As you can see in Figure 4, for example, the image request was sent at 3500ms and completed at 4800ms — meaning it was at least 4800ms before the first screen was rendered.
If we can make full use of the browser’s caching strategy, combined with the browser’s support for multi-request parallelism, and even upgrade the protocol to HTTP2 to get multiplexing features; Making an image request after the template has been parsed allows you to cache images in advance for later use on the page.
Specifically in Figure 4, if the image request can be advanced to 2400ms(at this point, some resource requests have already been completed and the browser can make more requests), or if the number of concurrent requests allows, the image request can be sent at 1600ms. Then the image can be loaded and rendered at 3700ms(or, ideally, 2900ms to complete the request at 1600ms).
The asynchronous request optimization done in the previous step provides the basis for this image load optimization: you can insert a script after the data is mounted that resolves the image address in the interface data and issues the request directly instead of waiting until the front-end framework builds the page.
Image preloading optimization
In addition to creating an IMG image request, you can also use preload to get a higher load priority for the scene rendered on the first screen:
For this immediate-need resource, you may want to acquire it early in the page loading lifecycle, preloading it before the browser’s main rendering mechanism kicks in. This mechanism allows resources to be loaded and available earlier and is less likely to block the initial rendering of the page, thus improving performance.
First determine whether your browser supports Preload
/** * const isPreloadSupported = () => {const link = document.createElement("link"); const relList = link.relList; if (! relList || ! relList.supports) { return false; } return relList.supports("preload"); };Copy the code
Send images using preload
/** * const sendImgWithPreload = (url) => {const link = document.createElement("link"); link.rel = "preload"; link.as = "image"; link.href = url.ossimg(); document.head.appendChild(link); };Copy the code
Vue – the lazyload optimization
The existing page will read the component configuration uniformly whether the image configuration needs lazyload, which brings the problem that the first screen image does not need lazy loading but only triggers the real image request after lazyLoad is executed.
For the user to browse the page, it is expected that while completing the rendering of the first screen as soon as possible, the loading of pictures in the rest screen can be completed as soon as possible, so that the user can smoothly browse to the pictures in the page when sliding.
The vue-lazyLoad configuration parameter provides the preLoad attribute to set the threshold for triggering image loading. We can make this value very large in order to load the rest of the image as quickly as possible. However, there is a problem: lazy loading of the remaining images in the weak network may trigger before the Load event, which will grab the resources of the first screen rendering and prolong the Load time, which is contrary to the purpose of optimization. Therefore, the above two points need to be optimized.
The first is that the first screen image doesn’t need lazy loading. When sending the image preloading request, a Map(named preloadImgMap) can be constructed with the preloaded image URL as the key. When generating image elements in Vue, determine whether the image address exists in preloadImgMap. If so, the command of lazyload is not added. Just load it as a normal image.
The second is the optimization of vue-LazyLoad mount time. The first screen can be pushed to the page Load event without relying on the preloading of pictures in the remaining screens after the LazyLoad plug-in: no matter how many screens are loaded after the first screen rendering is completed, the user experience will not be greatly affected. Optimizing mount timing comes first to the idea of using plug-ins dynamically, but the timing of using plug-ins is fixed in the documentation:
Use the plug-in with the global method vue.use (). It needs to be done before you call new Vue() to start the application
Consider modifying the source code for the plug-in. Copy vue-lazyload source code to local directory, / SRC is the source directory, local development can be directly imported/SRC /index.js. Locate the plug-in Install method
Vue.directive('lazy', {
bind: lazy.add.bind(lazy),
update: lazy.update.bind(lazy),
componentUpdated: lazy.lazyLoadHandler.bind(lazy),
unbind: lazy.remove.bind(lazy)
})
Copy the code
In/SRC /lazy.js, you can see that the add() method of lazy.js calls ReactiveListener to initialize the listener to start lazily loading the image. You can optimize lazyLoad mount timing by simply postponing the process of initializing listeners until after the page Load event.
Once you’ve done these two steps, you can safely change the preLoad parameter you pass in with VueLazyload to 3 or greater to speed up the first screen rendering and load the rest of the page ahead of time.
conclusion
This optimization scheme is not particularly complex, the core idea is to optimize the process of resource request and loading; Requests and renderings of front-screen resources are advanced, and loading of non-front-screen resources is delayed.
Overall optimization is to do incremental changes, no impact on the existing code logic, the risk is more controllable.