Image Rendering optimization

I’ve talked about pictures many times before. Several schemes are also given. In practical use, these are undoubtedly feasible and convenient:

  1. loading
  2. connection API + promise.all()Asynchronously loading images
  3. Skeleton screen
  4. Lazy loading + placeholder map

But in emporiumscape, the first option is not possible: we can’t give up the whole content for the sake of a picture. (especially if it’s just a background image)

The second option works wonders on the home page, but I find it too restrictive — if it’s a “universal” solution, skeleton screens seem to be more suitable for medium and large projects. The scenario proposed at that time was that there would be a large rotation chart on the home page of our school’s experimental system, but its function was only to display and publicize. Obviously, not all images need to be displayed in this rotation chart, but if left unchecked, there will be a blank or even a flash of white when the network is slightly worse. This is not good! So the Connection API is used to detect the current network condition to determine whether to display a cast image or an image.

The last option is probably the one that is more commonly used today. The placeholder can also be replaced with a fixed width and height div with a background color, reducing the need for an image (as was done on the previous Home page). However, its disadvantage lies in that if the picture is too large, the user will feel annoyed if it is loaded for a long time.

It’s impossible not to deal with it. A long white screen or flash will probably drive the product crazy…


Sprite figure

CSS Spirte is a good thing, and it dramatically reduces HTTP overhead. The combination of spirte + Prefetch allows HTTP requests to be optimized while being loaded earlier:

<link rel="prefetch" href="xxx" />
Copy the code

Prefetch is a “hint” to the browser that certain resources may be needed in the future, but the browser decides if and when to load them. It has a lower priority.

The principle of Spirte is to put all the images needed by the whole page into one image and move them to the place to be displayed through transform. But again: if you have a lot of images that are too big, it can make Sprite images very difficult to load, which can lead to a bad user experience.

Lazy loading

As soon as you enter the web page, there will be a large number of image resources loaded, which will indirectly affect the page loading, increase the white screen loading time, and affect the user experience. Therefore, our appeal is that images not in the visual window do not actually load, minimizing the waste of local bandwidth and the amount of requested resources.

The advantages of lazy loading are obvious:

  • Reduce bandwidth resource consumption and reduce unnecessary resource loading consumption.
  • Prevent resource loading blocks caused by concurrent loading of image resources, thus reducing the white screen time.

Implement simple lazy loading

This can be done in two ways:

  1. throughscrollEvent to listen to the scrollarea implementation. This method is compatible with most browsers and WebViews.
  2. throughIntersectionObserverAPI to observe whether the DOM appears in the window, the advantage of this method is simple to call, but partial mobile compatibility is not as good as the previous method.

Both forms observe whether the CURRENT DOM appears in the visible window, assign the image address in data-src to SRC if it does, and then start loading the current image.

I’ve previously looked specifically at preloading and lazy loading (see article), encapsulating a function to call. But that’s by listening in on Scroll. In fact, the IntersectionObserver API provided by the browser is more convenient:

constRoot = get parent element;const options = {
    root: root,
    // Here is an array that can specify multiple ratios like [0.25, 0.5, 0.75, 1]
    threshold: [0]./ / intersection
    rootMargin:"0px"// Shrink and expand the viewport
}
const lazyIntersection = new IntersectionObserver(entires= > {
    Entires is an array of nodes to listen on
    entires.forEach((item,index) = > {
        console.log(item)
        // console.log(item.target, item.isIntersecting? 'Visible ':' not visible ')
        // isIntersecting is a Boolean value that currently monitors whether the intersecting area of the element is within the specified threshold of the visible area
        if(item.isIntersecting) {
            console.log('visible')
            item.target.src = item.target.getAttribute('data-src')
            // The resource is loaded and then stopped to observe
            lazyIntersection.unobserve(item.target)
        }
        // console.log(item)
    })
}, options)

let data = Array.from(document.querySelectorAll('img'))
data.forEach(item= > {
    // Observe the specified DOM node
    lazyIntersection.observe(item)
})
Copy the code

Large image rendering optimization

You may have seen something like this:Ordinary image loading methods and later proposedJPG progressive loading,PNG interlaced loadingIt’s nothing compared to that. In fact, progressive loading is more common in practice – I recommend jpeg progressive loading: you don’t have to download the entire image to see the content. Yes, it’s a way of loading from fuzzy to clear.

See a lot of articles using JS to imitate this way to load pictures. But… You convert HTTP consumption of images into single-threaded JS blocking. The result is longer page/interactive times for users. Is that good?

When generating images in Photoshop, there is a “save for web format”, and then, one of those successive checks is progressive JPEG images: remember! You need to check the convert to sRGB option. In some browsers, setting images to CMYK will cause some problems! Image software such as FireWorks has similar output Settings.

Pros and cons of progressive graphics

  1. Progressive images start with a set size frame. There is no backflow due to unset size — improved rendering performance, as with basic images.
  2. Progressive graphics also have shortcomings, that is, eat CPU eat memory.

Long map rendering optimization

In the scene of the electric shopping mall, the most uncomfortable is actually the product details page, here the operation may be configured with a detailed description of some goods. Not only will the quality of the picture be relatively high, but the picture will also be very long. Obviously, we can’t just get the picture and display it on the page. If the user’s Internet speed is slow, the page will directly appear a long white bar or an error graph with loading failure. These are obviously not the results we want.

What to do? The author studied our image processing tool: when you click on a large image, you will find that only a part of the image is displayed, or only a small part of the content is displayed at the top of the large image area for the first time.

According to this idea, we can do the corresponding cutting image optimization, divide a long image into multiple image blocks of equal size, to perform a batch rendering optimization, reduce the pressure of rendering a long image in a single time.

This step is obviously back-end processing — in the case of Node, you can download GM1 or ImageMagic2:

npm install --save gm
Copy the code

Functions for clipping are provided in GM:

gm("img.png").crop(width, height, x, y)
Copy the code

ImageMagic is more powerful, not only can crop images, but also supports image format conversion and image recognition functions!

After the processing is complete, it can be stored in a special directory and continuously request “next section” images by listening to the Scroll or IntersectionObserver API.

Image exception Handling

Image exceptions fall into two categories: loading exceptions and rendering exceptions.

Loading exception

A load exception is an image request that failed due to various reasons. Because the image belongs to a resource, an exception to the resource raises an error Event in the Event interface that does not bubble up to the window, but can be caught. Window.onerror cannot be detected. But can be caught by addEventListener:

// Image, script, CSS loading errors can be caught
<script>
  window.addEventListener('error'.(error) = > {
  	console.log('Exception caught:', error);
	}, true)
</script>
<img src="https://yun.tuia.cn/image/kkk.png">
<script src="https://yun.tuia.cn/foundnull.js"></script>
<link href="https://yun.tuia.cn/foundnull.css" rel="stylesheet"/>// New Image error, cannot capture<script>
  window.addEventListener('error'.(error) = > {
    console.log('Exception caught:', error);
  }, true)
</script>
<script>
  new Image().src = 'https://yun.tuia.cn/image/lll.png'
</script>// Fetch error, cannot capture<script>
  window.addEventListener('error'.(error) = > {
    console.log('Exception caught:', error);
  }, true)
</script>
<script>
  fetch('https://tuia.cn/test')
</script>
Copy the code

Rendering anomalies

Render exception is what we call “no image on the page, blank”. What we usually need to do is “degrade” — such as giving a failure warning, or giving degraded images (such as Z-index enhanced background, pseudo-element enhanced image /SVG).

Remember that! If you use loading optimization, don’t let loading persist!


  1. Gm official website: www.graphicsmagick.org/↩
  2. ImageMagic website: imagemagick.org/↩