⚠️ this article for nuggets community first contract article, not authorized to forbid reprint
preface
The million PV mall series mainly includes my practical experience in mall projects. I will start from visual experience, performance optimization, architecture design and other three dimensions to explain the front-end problems in mall projects step by step, the ideas and solutions to the problems, and finally carry out code practice. Let you encounter similar problems in the work, can be more handy.
This article is the first article of the mall practice series. The main content is to optimize the image resource loading of common scenes in mall projects, and improve the visual experience and page performance.
background
In daily life, Yoyo people must have used some e-commerce companies’ APPS, Web, small programs and other applications. In the process of browsing, there will be a lot of pictures, such as common commodity card, commodity details, rotation diagram, advertising diagram and other components need to use some background management configuration uploaded picture resources.
In addition, the pages where these components are located are also the pages with very large traffic burden. If the user experience sense is poor, it will inevitably affect the retention of users. So, in this article, we will learn some of the image resource optimization techniques, I will respectively from ordinary pictures, high fidelity pictures, long screen rendering of these three scenes, in turn to tell you about the actual operation of the work. If you come across an optimized scenario, you’ll be able to take it easy.
General image optimization
For general image optimization, I use lazyLoad as the main implementation method. Lazy loading refers to the priority of loading the image resources in the visual window, and the content outside the visual window will only be loaded after scrolling into the visual window.
So, why do I use lazy loading, and what is the implementation of lazy loading? Below, I’ll walk you through a simple implementation of React’s image lazy loading scheme.
Why use lazy loading?
Generally speaking, a large number of image resources will be loaded as soon as you enter a web page, which will indirectly affect the page loading, increase the loading time of white screen, and affect the user experience. Therefore, our appeal is not to load the images in the visualization window, as far as possible to reduce the waste of local bandwidth and the number of requested resources.
So why do I recommend lazy loading as the primary implementation for general image resources? Because it has two big advantages.
- Reduce bandwidth consumption and unnecessary resource loading consumption.
- Prevent concurrent loading of image resources caused by resource load blocking, thus reducing white screen time.
Implement simple lazy loading
So how does lazy loading work? There are two ways to do this.
- through
scroll
Event to listen to the window scroll area implementation. This method is compatible with most browsers andWebView
Are compatible and supported. - through
IntersectionObserver API
To observe theDOM
Whether to appear in the window, this method has the advantage of simple call, only part of the mobile terminal compatibility is not as good as the previous method.
In both cases, you observe whether the current DOM is present in the visual window, if so, assign the image address in data-src to SRC, and start loading the current image.
So let’s start implementing an example of lazy loading based on Scroll events.
The page layout
Let’s draw a basic page layout, mainly loading Windows and images.
const ImageLazy = () = > {
const [list, setList] = useState([
1.2.3.4.5.6.7.8
])
const ref = useRef<HTMLDivElement | null> (null)
return (
<div className="scroll-view" ref={ ref} >
{list.map((id) => {
return (
<div key={id} className="scroll-item">
<img
style={{ width: '100% ',height: '100%' }}
data-src={` ${prefix }split-The ${id}.jpg` }
/>
</div>
);
})}
</div>)}Copy the code
.scroll-item {
height: 200px;
}
.scroll-view {
height: 400px;
overflow: auto;
}
Copy the code
You can look at the renderings, which show only two images on the page, but all of them have been loaded.
Registering scroll Events
After the scroll view is bound to the ref, the Scroll event needs to be bound and logged out in the useEffect.
As follows, I first get all the img elements of the current component (it is better to specify className for the real operation), add the event listener for ref.current with addEventListener, and then execute the corresponding method in the callback.
At the same time, when the return, also need to remove the event, to avoid some unexpected situation.
useEffect(() = > {
const imgs = document.getElementsByTagName('img');
console.log(ref.current, 'current') ref.current? .addEventListener('scroll'.() = > {
console.log('listens run')})return( ref.current? .removeEventListener('scroll'.() = > {
console.log('listens end')})}, [])Copy the code
As shown in the figure below, while I was scrolling, the ScrollCallback was executed at the same time, and the console printed out a number of execution results, indicating that our event had been added successfully.
Scroll callback & throttling function
Through the analysis diagram below, we can see that clientHeight is the height of our window, and in ScrollView each scroll will trigger the Scroll method callback, you can get the current page window scroll distance scrollTop.
So how do we tell if an element is on the page?
The offsetTop attribute of the element tells you how far the current element is offset from the top. So, when we get the window’s height clientHeight, scrollTop, and the element’s distance from the top offsetTop, we can infer the following conditional formula: To determine whether the current element appears in the visible range of the page, select the window height (dom.clientheight) + scrollTop (dom.scrollTop) > element distance to the top (image.offsetTop).
The result of converting it into a function method is as follows:
function scrollViewEvent (images: HTMLCollectionOf<HTMLImageElement>) {
// Visualized area height
constclientHeight = ref.current? .clientHeight ||0
// Roll distance
constscrollTop = ref.current? .scrollTop ||0
// Iterates through imgs elements
for (let image of images) {
if(! image.dataset.src)continue
// Check whether SRC is loaded
if (image.src) continue
// The distance between the image and the top
let top = image.offsetTop
/ / formula
if (clientHeight + scrollTop > top) {
// Set the image source address to complete the target image loading
image.src = image.dataset.src || ' '
image.removeAttribute('data-src')}}}Copy the code
Here, I also did a little throttling optimization with useThrottleFn in ahook to avoid frequent function callbacks. Events are executed only once over 500ms to avoid the performance cost of additional execution.
import { useThrottleFn } from 'ahooks'
// Closure function hook
const { run } = useThrottleFn(scrollViewEvent, {
wait: 500
})
Copy the code
At the same time, we put it into the Scroll event callback to execute. However, the component does not trigger the Scroll event in the beginning, so we need to manually initialize the image data in the current first page.
useEffect(() = > {
const imgs = document.getElementsByTagName('img');
console.log(ref.current, 'current') ref.current? .addEventListener('scroll'.() = > {
run(imgs)
})
run(imgs)
return( ref.current? .removeEventListener('scroll'.() = > {
console.log('listens end')})}, [])Copy the code
At this point, our lazy loading of an image is almost complete, let’s see what it looks like.
For lazy loading, it is best to set a height for each item to prevent the component from being exposed to the window when there is no image to start with.
High fidelity image optimization
For such pictures of high fidelity, many are activity maps configured by relevant operation personnel. Generally, there will be many micro pages or picture links during the big promotion, which are published to users in the form of pictures + hot areas.
Therefore, the vast majority of operational demands are to show the corresponding picture as clearly as possible. So lazy loading apparently is not very good to solve the problem, so I’m lazy loading on the basis of the original, added some loading status to users of visual sensory experience, the current products are mainly used on market frame screen Or the incremental load such as solution to let the pictures show the transition more smooth, avoid the embarrassment of loading failure or loading images caton.
How to implement
Again, let’s go through the implementation logic by looking at the diagram below. In the picture component, there are two pictures to be replaced in turn. When the HD resource picture is loaded, it is necessary to hide the skeleton picture or thumbnail picture and show the loaded HD picture.
Image components
After the analysis, you can follow me to implement a simple image component, through the onLoad event in IMG to determine whether the image that needs to be displayed has been loaded. The display and hiding of the miniatures are controlled by the corresponding status. Using progressive loading as an example, let’s implement a simple state switching component using the following diagram.
import React from "react";
import "./index.css";
interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
thumb: string;
}
type ImageStatus = 'pending' | 'success' | 'error'
const Image: React.FC<ImageProps> = (props) = > {
const [status, setImageStatus] = React.useState<ImageStatus>('pending');
/** * Modify image status *@param Status Status */
const onChangeImageStatus = (status: ImageStatus) = > {
/** TODO setTime */
setTimeout(() = > setImageStatus(status), 2000)}return (
<>
<img
className="image image__thumb"
alt={props.alt}
src={props.thumb}
style={{ visibility: status= = ='success' ? "hidden" : "visible}} "/ >
<img
onLoad={()= > onChangeImageStatus('success')}
onError={() =>onChangeImageStatus('error')}
className="image image__source"
alt={props.alt}
src={props.src}
/>
</>
);
};
export default Image;
Copy the code
Through the filter, the blur (25 px); Properties to add a partial blur effect to the miniatures, so that you can avoid the awkwardness of some mosaics, to achieve partial ground glass, or gaussian blur some small effects.
We do a simple progressive image loading with the onLoad completed event, so other loading states such as the skeleton screen can be implemented with the same state judgment. I just replaced the thumbnail with another component, that’s all.
Long rendering optimization
In the product details page, operation will be configured with a number of detailed description of the product pictures and pictures, not only the quality of the picture will be relatively high, but also the picture will be very long. Obviously, we can’t just take the image and display it on the page. If the user has a slow Internet connection, there will be a long white bar on the page, or an error image that fails to load. These are obviously not the results we want.
So what to do?
Let’s first take a look at taobao and other e-commerce platforms of a product details, when you click to see the big picture, you will find that only part of the picture is shown, will be divided into a lot of pictures of the same size to us.
In accordance with this idea, we also did a corresponding graph cutting optimization, dividing a long image into multiple blocks of equal size to perform a batch rendering tuning, reducing the stress of rendering a single long image.
Cut into pieces
So, next, I will cut the long image from the simulation back end to the short image, and then show the cut images on the page. Without further ado, let’s cut to the chase.
First of all, combined with the following analysis diagram, our cutting diagram principle is actually very simple, a long graph is divided into small graphs with equal length and width, if the last one does not meet the height of the cutting block, the remaining height is directly cut into a single picture.
So, let me write a simple node code to take you through the process of cutting.
The Node cutting figure
In the figure below, I will simulate a long image uploaded by the operation, and then cut it into several short images with a height of 200 on the right (the size is assessed according to the needs). Next, let’s take a look at the tutorial and code to achieve the effect.
First, install Sharp for image processing and image-size for obtaining image size information.
# shell
yarn add sharp image-size
Copy the code
When I get the height and width of the image, that means I can use a while loop to calculate the equal height of the cut.
/** @name */ let clientHeight = currentimageinfo.height /** @name */ const SPLIT_HEIGHT = 200 /** @name */ /** @if (clientHeight >= SPLIT_HEIGHT) {height.push (SPLIT_HEIGHT) */ height.push (clientHeight) clientHeight = 0}} clientHeight -= SPLIT_HEIGHT} else {/** @elseCopy the code
So, once I know the size of the cut image, I can walk through the cut heights, cut the actual image by cropping and offsetting, and generate a new file and save it.
In the code below, I create a marginTop offset and each cut will add its height to the downward offset until I cut the image to the end of the last page. MariginTop is the height of the image.
/ * *@name The offset * /
let marginTop = 0
heights.forEach((h, index) = > {
sharp('./input.jpg')
.extract({ left: 0.top: marginTop, width: currentImageInfo.width, height: h })
.toFile(`./img/split_${index + 1}_block.jpg`).then(info= > {
console.log(`split_${index + 1}_block.jpg cut successfully ')
}).catch(err= > {
console.log(JSON.stringify(err), 'error')
})
marginTop += h
})
Copy the code
As shown in the following picture, there are some fragments of images in the IMG folder, and then check whether the images are fully assembled. If there is no problem, then we have completed a simple long image cutting requirement, the next step is to put it on the front end for rendering.
The front-end display
After the front end gets the corresponding slices, it can be put together directly on the front page to display. After the middle gaps or rough edges are removed, it is no different from the long image rendering. I still used lazy loading to do simple load optimization, and the overall effect is as follows:
Once you’ve seen the loading effect, let’s look at the response time of the loading.
Because the images I cut were not optimized on the file, the size of the single image was too large, but it did not affect the loading of the first screen at all. Here is a comparison of the loading time under Flow3G.
For a single long picture, it means that the user may see about 4s white screen or skeleton screen, and the load after slicing can preferentially show the user part of the content.
Fix burrs or gaps
In the final implementation of this scheme, there may be some flaws. The specific cutting plan is related to the device model. If you encounter a problem, you can refer to some of my solutions:
- The first is through
vertical-center
Set the vertical center value to solve the baseline alignment problem. - The second is to
img
Set it to a realblock
Element solution. - The third one that I usually use is through
flex-direction
Set tocolumn
Vertical arrangement for child elements solves the problem. - The fourth is through
background
Picture way to solve the problem. But in doing so, if you want to use lazy loading, you need to change the partscss
Style offset to reach visual window display.
The resources
- # Progressively Loading Images In React
- # Front-end performance optimization — Picture section
- # Lazy loading and preloading
- Vue Lazy Component: An introduction to Vue Lazy Component
- # Sample image &Demo address
conclusion
In this article, I’ve covered three different image optimization strategies. There are many open source libraries on the market that can easily implement lazy loading and progressive loading. At the same time, for skeleton screens, many component libraries have corresponding components, which are relatively inexpensive to encapsulate.
Therefore, if a lot of image resources are involved in the project, then the optimization scheme mentioned in this article is my recommendation.
- Try not to load fully if you can do lazy loading
- Give the user a certain state hint, skeleton screen or transition diagram can do as far as possible do not pull down.
- Long graph can cut graph as much as possible cut graph, it is very convenient to take it apart to optimize.
- All pictures can be on
CDN
As much as possibleCDN
. - Images that can be compressed should be compressed as much as possible.
For a mall project, its challenge lies not in the realization of function logic, but in the optimization of part of the visual experience and experience. If you think the article is helpful to you, you can point to 👍, give me a gas. If you want to know more about the front-end e-commerce project Yoyo can pay attention to this column.
The recent oliver
- WebStorage Cache Usage Guide
- # ME & Nuggets, one year after graduation, I was signed by the Nuggets | Mid-2021 summary
- Summarize the application experience of TypeScript in project development
endnotes
This article was first published in: Gold mining technology community type: signed article author: Wangly19 collection in column: # million PV mall practice series public number: ItCodes program life