This article has shared the process from encountering front-end business performance problems to analyzing, resolving, and sorting out a common Vue 2.x component-level Lazy loading solution.
Excessive initial loading resources
The problem started with one of our pages. Here is a screenshot of the page and the waterfall of the initial request.
At the initial load, a total of 155 resources were requested, and the requested waterfall was almost as long as the page 😊
Too many resources were initially loaded, so after domInteractive, the page took a lot of time to load sub-resources. As a result, the load time of the page was severely prolonged, reaching 5.6s.
Based on the type of resource requested, we found that the most common type is images, which is obvious, there are large images everywhere on the page, followed by JS files, which are composed of third-party business plug-ins and some JSONP interfaces.
Problem analysis
Back to the original page, combined with the above data, we come to the conclusion of the page’s problems:
- Pages are made up of a number of modules
- Each module is partially maintained by the home page and partially maintained by the business side through plug-ins
- All modules are loaded simultaneously
- There are many pictures in the module
- Each module has many dependent resources (including JS files, interface files, CSS files, etc.)
solution
We propose the following two main solutions:
The idea of componentization and partition
In order to facilitate subsequent optimization, we must require that the coupling between each module be reduced and the related logic (such as request interface, request related dependent resources) be encapsulated internally and implemented as components in Vue.
- Split each module into component granularity
- Encapsulate all resources that the component depends on and invoke them inside the component
Load priority
After completing the componentized split to ensure that the modules do not interact and coupling with each other, we can adjust the loading strategy from aspect to aspect. The strategy for loading is to deal with priority issues in terms of visibility.
- The first visible module is loaded preferentially
- Other invisible modules are loaded lazily until they are visible or about to be visible
With the above solutions in mind, we began to think about the concrete implementation:
How to solve the visibility problem of judgment?
In the past, we used to listen for scroll events and resize events to determine whether a module was visible. The code was not only cumbersome, but also could cause serious performance problems if the function was not shaken.
Now we have a better option: IntersectionObserver API. IntersectionObserver allows you to configure a callback function that will be executed whenever target, element and device viewport or other specified elements intersect. The API is designed to be asynchronous and to ensure that your callbacks are executed only a limited number of times, and that the callbacks are executed when the main thread is idle, resulting in better performance and ease of use.
Currently supported by modern browsers, older browsers are compatible with Polyfill.
How to render conditions as lazily as possible?
After solving the load condition judgment, we need to solve the problem of not rendering when the load condition is false, but rendering when the load condition is true. The answer here is very simple: use the V-if directive provided by vue.js, and you can do true lazy rendering.
If I render it after it is visible, how do I display it before it is visible?
If nothing is rendered when the loading condition is false, this will cause a number of problems:
- The user experience is poor, it starts with a white screen and then suddenly renders the content.
- The most deadly thing is that we need a target to observe for visibility, and if everything is not rendered, we cannot observe.
Here we introduce the concept of a skeleton screen. We make a component that is very similar in size and style to the real component. We call it a skeleton screen.
The role of the skeleton screen is:
- Improve user experience
- Ensure the consistency of switchover
- The target object that provides visibility observation
How can I improve the switching experience?
When a real component starts rendering, it needs a certain amount of time and space. Time refers to the time from creation to rendering of the real component, including requesting interfaces, requesting resources, and rendering time. Space refers to the need to leave the real component just the right position in the page layout to avoid jitter.
In this case, we can use the transition component built in vue. js to customize the entry and exit effects of the skeleton component and the real component. By laying out and positioning the components properly, we can reduce the jaffe when switching. By setting the transition effect, we can leave some time for the real component to load.
With the above questions answered, it is easy to implement a general solution to the problem of lazy component loading.
This section describes the lazy Vue component loading scheme
Github: github.com/xunleif2e/v…
This is a general solution based on the above thinking. The following is a brief introduction to the features, usage and API knowledge, followed by five specific demos to explain the more advanced usage.
features
- Supports lazy loading when components are visible or about to be visible
- Supports delayed component loading
- Component skeletons are displayed before component loading to improve user experience
- Support lazy load component subcontract asynchronous loading
Installation and use
npm i @xunlei/vue-lazy-componentCopy the code
- Method 1 Use plug-ins to register globally
- Method 2 Local registration
- Mode 3 Import an independent version and automatically register globally
usage
Props
parameter | instructions | type | An optional value | The default value |
---|---|---|---|---|
viewport | Viewport the viewport where the component resides, which is the container if the component is scrolling inside the page container | HTMLElement | true | null , stands for Windows |
direction | The rolling direction of the viewport,vertical Represents the vertical direction,horizontal Represents the horizontal direction |
String | true | vertical |
threshold | Preload threshold, CSS unit | String | true | 0px |
tagName | The label name of the outer container that wraps the component | String | true | div |
timeout | Wait time, if specified, automatically loads after the specified time whether visible or not | Number | true | – |
Events
The event name | instructions | Event parameters |
---|---|---|
before-init | The module is visible or the delay cutoff causes the lazy loaded module to be ready to start loading | – |
init | The lazy-loaded module is loaded, at which point the skeleton component begins to disappear | – |
before-enter | The lazy load module starts to enter | el |
before-leave | Skeleton components begin to leave | el |
after-leave | Skeleton components have left | el |
after-enter | Lazy loading mode fast has entered | el |
after-init | Initialization complete | – |
Excessively long pages in DEMO 1 are loaded lazily
Xunleif2e. Making. IO/vue – lazy – co…
<vue-lazy-component>
<st-series-sohu/>
<st-series-sohu-skeleton slot="skeleton"/>
</vue-lazy-component>Copy the code
Using this simple approach, you can automatically load components when they are about to be visible.
DEMO 2 is loaded delayed
Xunleif2e. Making. IO/vue – lazy – co…
<vue-lazy-component :timeout="1000">
<st-series-sohu/>
<st-series-sohu-skeleton slot="skeleton"/>
</vue-lazy-component>
`Copy the code
Sometimes you just want certain components to be rendered later, not necessarily when they are visible.
For example, our business may have some operation of the nature of the pendant, you can take the way of delayed loading.
DEMO 3 customizes transition effects
Xunleif2e. Making. IO/vue – lazy – co…
If you find the Vue Lazy Component’s own fade in and out effect too ugly, or if you need to adjust the duration of the fade in and out effect, you can change the transition effect with a custom style. This example demonstrates another transition effect. The transition lifecycle can be referenced in the Transition component documentation in vue.js.
DEMO 4 webPack subcontract
Xunleif2e. Making. IO/vue – lazy – co…
DEMO1 shows how to load a module lazily, but it only delays the rendering of the module and the loading of the resources inside the module. If we need to go further, the module code itself is also lazily loaded, as AMD does asynchronously on demand, which is also possible.
Here, we can use the asynchronous component of vue. js to register each real component as an asynchronous component. In the factory function of the asynchronous component, we can use the AMD version of Webpack require to realize that the real component can be loaded into independent bundles, separated from the bundle of page JS.
The problem is that even if the module is rendered while it is visible, when the page is opened, the bundle has been loaded before the module is visible. This does not implement on-demand loading.
This example demonstrates that the Vue Lazy Component can pass a loading property to the real Component via Scoped Slots before switching to the real Component. The real Component can avoid loading on demand as long as it is conditionally rendering based on this loading. This is related to the resolution mechanism of vue. js to the component, the example has the corresponding code, interested students can further study.
DEMO 5 is loaded lazily on a specific viewport
Xunleif2e. Making. IO/vue – lazy – co…
In some cases where we are dealing with lazy loading of components in the scrolling container, visibility is relative to the viewport. This example demonstrates how to specify the chat window as the viewing viewport.
Js’s $parent and $refs are not responsive. If you want to dynamically retrieve the $EL on these component references, you have to wait until after the Mounted event, so the code for this example is a little bit more complicated.
Application effect
First of all, although the design of Vue Lazy Component is said to be at the Component level, in fact, its granularity can be large or small, such as different areas of the page, as small as DEMO5 is just a user profile picture, so the applicability is very strong, as long as there is Lazy loading requirements can be used.
In addition, in terms of terminal, it is not only compatible with PC terminal projects, but also can be used on mobile terminal. Of course, the compatibility problem of IntersectionObserver API needs to be solved. The address of W3C polyfill is mentioned in the project Readme.
Apply business
We are currently used in the thunder of the two projects, one is the PC thunder home page project, one is the PC thunder team accelerated project, the later is expected to be promoted to more business.
Request waterfall diagram after optimization
Taking a look at the initial page, after using the component lazy loading technique, the number of requests is reduced to only 31, and the waterfall graph is shorter.
Data contrast
Let’s compare the data before and after:
- The number of requests is 1/5 of what it was before, and the optimization effect is obvious
- The request size is not significantly reduced compared to the previous one
- The load duration is also less obvious
The main analysis is that many images are not cropped and compressed according to the used size, resulting in a large request size and a long load time drag.
Follow-up performance optimization direction
We will continue to optimize this page in the following two main directions:
Image size adaptation and compression
Through image cropping and compression, the problem of long load time caused by large requested resources is solved
pre-rendered
The pre-rendering plug-in is used to inline the main CSS and JS of the page, and the skeleton frame screen is generated by pre-rendering. In this way, the problem of long critical path of the SPA first screen can be avoided, and the first screen can be guaranteed to be visible after parsing the DOM tree of the page.
Lazy loading of ROADMAP
There are some areas where the Vue Lazy Component Lazy loading scheme doesn’t work as well as it should. The following features are planned for later, smaller releases:
- SSR support v1.1.0
- UI unit test v1.2.0
- Reduced performance overhead v1.3.0
- redraw
- FPS
Afterword.
This article shares the process of going from encountering a real business performance problem to analyzing, solving, and sorting out a common solution, focusing not on the final code implementation, but on the perspective and process of solving the problem.
You are welcome to contribute by submitting an issue or PR on Github: github.com/xunleif2e/v… .