Location>code7788 >text

A Practical Guide to Optimizing Progressive Loading of Images

Popularity:180 ℃/2024-11-21 15:13:00

preamble

  • Hey, it's Immerse.
  • Article first published on personal blog [], more on individual blogs
  • Reprint Note: For reprints, please include the original source and copyright notice in the header of the article!

a factor (leading an effect)

  • Recently went livepersonal blogThe fragment page has a large number of images, the experience of loading images is very poor, can be said to be a cliff, from 0-1 there is no transition at all (this affects the layout of the page and the user experience, for the images that have set the image width and height of the image is fine, if not, there will be a process of the image to support the height of the process)

coincide

  • In preparation for writing this article on the same day the front-end South Nine big brother published an article, I straight call big data bull 👍🏻Article: Click to view
  • We'll discuss a few other options in this post, so without further ado, let's get back to the point.
    • For regular image optimization will not be repeated here, roughly as follows:
      • Compressing images, using CSS sprites, lazy loading, preloading, CDN caching, proper image formats, Seven Bulls CDN image parameters, and more!

explorations

  • Here are a few of the scenarios mentioned in this post (some of the sample code is React since the personal project is based on Next)
    • (1) Use the main color of the picture
    • (2) Using a certain color
    • (3) Using thumbnails of images
    • (4) Use Blur + Compression
    • (5) Image Placeholders

Option 1: Use the main color of the picture

  • In our day-to-day development, our imagesrc It may be dynamic, i.e. a stringstring url, when we specifyplaceholder="blur" When you add theblurDataURL Attributes.
import Image from 'next/image';

// Pixel GIF code adapted from /a/33919020/266535
const keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';

const triplet = (e1: number, e2: number, e3: number) =>
    (e1 >> 2) +
    (((e1 & 3) << 4) | (e2 >> 4)) +
    (((e2 & 15) << 2) | (e3 >> 6)) +
    (e3 & 63);

const rgbDataURL = (r: number, g: number, b: number) =>
    `data:image/gif;base64,R0lGODlhAQABAPAA${
        triplet(0, r, g) + triplet(b, 255, 255)
    }/yH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==`;

const Color = () => (
    <div>
        <h1>Image Component With Color Data URL</h1>
        <Image
            alt="Dog"
            src="/"
            placeholder="blur"
            blurDataURL={rgbDataURL(237, 181, 6)}
            width={750}
            height={1000}
            style={{
                maxWidth: '100%',
                height: 'auto'
            }}
        />
        <Image
            alt="Cat"
            src="/"
            placeholder="blur"
            blurDataURL={rgbDataURL(2, 129, 210)}
            width={750}
            height={1000}
            style={{
                maxWidth: '100%',
                height: 'auto'
            }}
        />
    </div>
);

export default Color;

Option 2: Use a certain color

  • exist Medium Configurationplaceholder because ofcolorand then use thebackgroundColor causality
// 
 = {
    images: {
        placeholder: 'color',
        backgroundColor: '#121212'
    }
};
// utilization
<Image src="/path/to/" alt="image title" width={500} height={500} placeholder="color" />

Option 3: Use thumbnails of images

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Progressive image loading</title>
        <style>
            .placeholder {
                background-color: #f6f6f6;
                background-size: cover;
                background-repeat: no-repeat;
                position: relative;
                overflow: hidden;
            }

            .placeholder img {
                position: absolute;
                opacity: 0;
                top: 0;
                left: 0;
                width: 100%;
                transition: opacity 1s linear;
            }

            .placeholder {
                opacity: 1;
            }

            .img-small {
                filter: blur(50px);
                transform: scale(1);
            }
        </style>
    </head>
    <body>
        <div
            class="placeholder"
            data-large="/work/143/24/42b204ae3ade4f38/1_sg"
        >
            <img
                src="/work/143/24/5307e9778a944f93/1_sg"
                class="img-small"
            />
            <div style="padding-bottom: 66.6%"></div>
        </div>
    </body>
</html>
<script>
     = function () {
        var placeholder = ('.placeholder'),
            small = ('.img-small');

        // 1. Show small image and load
        var img = new Image();
         = ;
         = function () {
            ('loaded');
        };

        // 2. Load large image
        var imgLarge = new Image();
         = ;
         = function () {
            ('loaded');
        };
        (imgLarge);
    };
</script>

Option 4: Use blurring + compression of images

// 
'use client';

import React, { useState, useEffect } from 'react';
import imageCompression from 'browser-image-compression';

interface ProgressiveImageProps {
    src: string;
    alt?: string;
    width?: number;
    height?: number;
    layout?: 'fixed' | 'responsive' | 'fill' | 'intrinsic';
    className?: string;
    style?: ;
}

export const ProgressiveImage: <ProgressiveImageProps> = ({
    src,
    alt = '',
    width,
    height,
    layout = 'responsive',
    className = '',
    style = {}
}) => {
    const [currentSrc, setCurrentSrc] = useState<string>(src);
    const [isLoading, setIsLoading] = useState<boolean>(true);
    const [blurLevel, setBlurLevel] = useState<number>(20);

    useEffect(() => {
        let isMounted = true;

        const loadImage = async () => {
            try {
                // Load and compress the original image
                const response = await fetch(src);
                const blob = await ();

                // Generate low quality preview images
                const tinyOptions = {
                    maxSizeMB: 0.0002,
                    maxWidthOrHeight: 16,
                    useWebWorker: true,
                    initialQuality: 0.1
                };

                const tinyBlob = await imageCompression(blob, tinyOptions);
                if (isMounted) {
                    const tinyUrl = (tinyBlob);
                    setCurrentSrc(tinyUrl);
                    // Starting to gradually reduce the blurriness
                    startSmoothTransition();
                }

                // Load original image
                const highQualityImage = new Image();
                 = src;
                 = () => {
                    if (isMounted) {
                        setCurrentSrc(src);
                        // When the high quality image has finished loading,Continued smooth transition
                        setTimeout(() => {
                            setIsLoading(false);
                        }, 100);
                    }
                };
            } catch (error) {
                ('Error loading image:', error);
                if (isMounted) {
                    setCurrentSrc(src);
                    setIsLoading(false);
                }
            }
        };

        const startSmoothTransition = () => {
            // through (a gap)20pxThe blurring of the gradual transition to10px
            const startBlur = 20;
            const endBlur = 10;
            const duration = 1000; // 1unit of angle or arc equivalent one sixtieth of a degree
            const steps = 20;
            const stepDuration = duration / steps;
            const blurStep = (startBlur - endBlur) / steps;

            let currentStep = 0;

            const interval = setInterval(() => {
                if (currentStep < steps && isMounted) {
                    setBlurLevel(startBlur - blurStep * currentStep);
                    currentStep++;
                } else {
                    clearInterval(interval);
                }
            }, stepDuration);
        };

        setIsLoading(true);
        setBlurLevel(20);
        loadImage();

        return () => {
            isMounted = false;
            if (currentSrc && ('blob:')) {
                (currentSrc);
            }
        };
    }, [src]);

    const getContainerStyle = ():  => {
        const baseStyle:  = {
            position: 'relative',
            overflow: 'hidden'
        };

        switch (layout) {
            case 'responsive':
                return {
                    ...baseStyle,
                    maxWidth: width || '100%',
                    width: '100%'
                };
            case 'fixed':
                return {
                    ...baseStyle,
                    width: width,
                    height: height
                };
            case 'fill':
                return {
                    ...baseStyle,
                    width: '100%',
                    height: '100%',
                    position: 'absolute',
                    top: 0,
                    left: 0
                };
            case 'intrinsic':
                return {
                    ...baseStyle,
                    maxWidth: width,
                    width: '100%'
                };
            default:
                return baseStyle;
        }
    };

    const getImageStyle = ():  => {
        const baseStyle:  = {
            filter: isLoading ? `blur(${blurLevel}px)` : 'none',
            transition: 'filter 0.8s ease-in-out', // Increased transition time
            transform: 'scale(1.1)', // Zoom in slightly to prevent edges from appearing when blurred
            ...style
        };

        switch (layout) {
            case 'responsive':
                return {
                    ...baseStyle,
                    width: '100%',
                    height: 'auto',
                    display: 'block'
                };
            case 'fixed':
                return {
                    ...baseStyle,
                    width: width,
                    height: height
                };
            case 'fill':
                return {
                    ...baseStyle,
                    width: '100%',
                    height: '100%',
                    objectFit: 'cover'
                };
            case 'intrinsic':
                return {
                    ...baseStyle,
                    width: '100%',
                    height: 'auto'
                };
            default:
                return baseStyle;
        }
    };

    return (
        <div className={`${className}`} style={getContainerStyle()}>
            {currentSrc && <img src={currentSrc} alt={alt} style={getImageStyle()} />}
        </div>
    );
};
// utilization
<ProgressiveImage
    src={photo}
    alt={}
    width={300}
    height={250}
    layout="responsive"
    className="h-full min-h-[150px]"
/>

Option 5: Picture Placeholders

  • (used form a nominal expression)next/image subassembliesplaceholder property provides an optionblurThe default isempty
    • blur A blurry preview image will be generated (but this option increases the initial loading practice as it takes time to generate the blurry image)
    • Note: If theplaceholder="blur" You must use theimport The way images are statically introduced so that they can be pre-processed for progressive loading
import Image from 'next/image';
import mountains from '/public/';

const PlaceholderBlur = () => (
    <div>
        <h1>Image Component With Placeholder Blur</h1>
        <Image
            alt="Mountains"
            src={mountains}
            placeholder="blur"
            width={700}
            height={475}
            style={{
                maxWidth: '100%',
                height: 'auto'
            }}
        />
    </div>
);

export default PlaceholderBlur;

summarize

  • The first impression of a product is important and a good user experience is necessary for the product.
  • Thanks for reading and we'll see you next time!