前端开发 / React 懒加载组件 Lazy-Load Next.js Image

这篇文章详细介绍了使用 Next.js 中的 Image 组件来实现图片懒加载的方法。

前端开发 / React 懒加载组件 Lazy-Load Next.js Image

Lazy Loading 懒加载介绍

什么是 Lazy Loading 懒加载

Lazy Loading ,也叫延迟加载,是一种性能优化技术,用于优化内容的加载时间。

这种技术允许你按需加载内容,而不是在页面首次加载时加载所有内容。这样,用户可以更快地看到并与页面交互,而不需要等待所有内容都加载完毕。

lazyloading-in-react20230826

Lazy Loading 的优点

在 Web 开发中, Lazy Loading 最常用于图片和视频资源:网页上的图片和视频往往占用了大部分的带宽。通过 lazy loading ,你可以在用户滚动到图片或视频位置时才加载它们,而不是在页面加载时立即加载所有资源。

Lazy Loading 的优点:

  • 提高首次页面加载速度:用户不需要等待所有内容都加载完毕就可以开始与页面交互。
  • 节省带宽:只有用户实际查看或需要的资源才会被下载。
  • 减少服务器负载:服务器只需要为实际请求的资源提供服务,而不是为每个访问者提供所有资源。

Lazy Loading 懒加载技术相关组件

现在有很多非常优秀的 Lazy Loading 实现的工具/库,其中专门为 React 或 Next.js 设计的:

工具/库核心特点主要劣
next/image 👍– Next.js 内置
– 支持自动优化和响应式设计
– 限制于 Next.js 项目
react-lazyload– 简单易用
– 事件驱动的延迟加载
– 可能需要额外的配置和样式调整
react-lazy-load-image-component– 支持占位符
– 支持图片和背景
– 不如其他库灵活

这篇文章我会详细介绍,通过使用 Next.js 中的 Image 组件来实现懒加载的方法。

另外两个组件,我会另起一篇文章介绍。

Next.js Image 组件使用方法

Image 组件介绍

Next.js 的 Image 组件是 Next.js 10 及更高版本中引入的新功能,旨在优化图片加载的性能。使用它可以轻松地为你的应用添加现代 Web 图片优化功能。

Image 组件具有非常多的优势,并且 API 非常简洁:

  1. 图片优化Image 组件会自动为你的图片应用各种优化,如压缩、改变大小和服务不同格式(例如 WebP ),而无需任何额外配置。
  2. Lazy Loading:默认情况下,Image 组件使用了 lazy loading ,这意味着图片只有在视口中时才会开始加载,从而提高页面的首屏加载速度。
  3. 缓存功能:优化后的图片会缓存到 Next.js 服务器,减小后端服务器的带宽压力,配合 Vercel 相当于实现了一个 CDN 效果。
  4. 自适应大小:你可以为组件提供多个大小,它会根据设备的屏幕大小和分辨率自动选择最合适的大小。
  5. 占位符:可以显示一个模糊的图片作为占位符,直到实际的图片加载完毕,并且可以实现 Blur 效果。
Next.js Image 组件官方文档地址: https://nextjs.org/docs/pages/api-reference/components/image

安装 Image 组件

如果你使用 Next.js 框架开发项目,无需格外的安装,可以直接导入使用:

import Image from 'next/image'

Image 组件基础使用语法

Image 组件 API 非常简洁易用,你可以像使用常规的 <img> 标签一样使用它:

<Image src="/path/to/image.webp" alt="描述" width={500} height={500} />

参数解释:

  • src 和 alt 参数为必需的参数。
  • width 和 height 表示图片渲染的宽度和高度,也是必需的参数(除非使用 import 导入的方式加载本地图片),这些值用于防止加载图像时出现累积布局偏移
width 和 height 属性用于推断图像的正确纵横比,避免加载图像时布局发生变化。

使用 Image 组件加载本地图片

使用 Image 加载本地图片(通常就是 Next.js 项目的 /public 文件夹中的图片),有两种方式:

方式一:使用相对路经

你可以直接将 src 设置为图片的相对路径来加载:

<Image src="/path/in/public/directory.webp" alt="description" width={500} height={500} />

这种加载方式, width 和 height 也是必须赋予的值。

方式二: import 导入

你可以使用import 来预先导入你的图像文件,并在 Image 组件中加载它:

import Image from 'next/image'
import profilePic from './me.webp'

export default function Page() {
  return (
    <Image
      src={profilePic}
      alt="Picture of the author"
    />
  )
}

这种加载方式,你可以不用提供 width 和 height 参数, Next 项目在构建时会自动对图片进行分析,并自动完成配置。

使用 Image 组件加载远程图片

使用 Image 加载远程图片的方式,和使用相对路经加载本地图片的方式类似。由于 Next.js 在构建过程中无法访问远程文件,因此也需要手动提供 width 、 height :

<Image src="https://example.com/path/to/image.webp" alt="description" width={500} height={500} />

Next.js 内置了图片优化 API :当你从外部域名加载图片时, Next.js 将通过其内置的 Image Optimizer API 对这些图片进行优化(转换成 WebP 格式、设置缓存)。

由于这个优化的过程会消耗服务器资源,为了避免恶意用户利用 Image Optimizer 进行某些攻击(如 DDoS ),因此必须在next.config.js中配置允许加载的图片来源域名白名单

设置方法如下:

# next.config.js 文件中设置

module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 's3.amazonaws.com',
        port: '',
        pathname: '/my-bucket/**',
      },
    ],
  },
}

注意:虽然也可以使用 domains 参数来设置,但在新版中, Next.js 官方推荐使用 remotePatterns 而不是 domains ,因为我建议大家都是用 remotePatterns 来配置。

更详细的说明可参考: https://nextjs.org/docs/pages/api-reference/components/image#remotepatterns

loading :设置懒加载

在 Next.js 的 Image 组件中, placeholder 参数用于控制图像的加载行为。

placeholder 属性有两个选项:

  • lazy :默认值,懒加载。推迟加载图像,直到它到达距视口的计算距离。
  • eager :立即加载图像。

Image 组件默认启用 lazy 属性,无需格外配置即可使用懒加载效果。

placeholder :设置图片占位符( Blur )

在 Next.js 的 Image 组件中, placeholder 参数用于定义图片加载之前的占位符,这样可以在用户感知上更加流畅。

placeholder 属性有两个选项:

  • empty : 默认值,这会显示一个透明的占位符,直到图片加载。
  • blur : 这会在图片加载之前显示一个模糊的小版本。

如果你设置为 blur,那么你还需要提供一个 blurDataURL 属性,它是一个包含小图片数据的字符串:

<Image
      src="/path/to/your/image.webp"
      alt="Description of Image"
      width={500}
      height={500}
      placeholder="blur"
      blurDataURL={myBlurDataURL}
    />

注意:在实际应用中,通常会在服务器端进行此操作,而不是在客户端。

plaiceholder

如果你的后端是使用 NodeJS 开发,可以考虑使用 plaiceholder 库来生成模糊的 DataURL 。

更多信息可参考这个 Youtuber 的视频: https://www.youtube.com/watch?v=Bz3No1RFXWY

官方演示 Demo : https://with-next.plaiceholder.co/

Pillow

如果你后端是使用 Python 来开发,可以考虑使用 Pillow 这样的库来生成模糊的 DataURL ,并将他保存到数据库中,避免每次都重复创建。

使用 Pillow 实现纯色效果:

from PIL import Image
import base64

def image_to_color_base64(image_path: str) -> str:
    with Image.open(image_path) as img:
        # 计算图像的平均颜色
        avg_color = img.resize((1, 1)).getpixel((0, 0))

        # 使用平均颜色创建一个纯色的图像
        color_img = Image.new('RGB', (1, 1), avg_color)

        # 将纯色图像保存为 base64 格式
        buffered = BytesIO()
        color_img.save(buffered, format="PNG") # 使用 PNG 格式可以确保没有任何质量损失
        img_base64 = base64.b64encode(buffered.getvalue()).decode()

    return f"data:image/png;base64,{img_base64}"

这个函数首先计算图像的平均颜色,然后使用这个颜色创建一个 1×1 的纯色图像,并将其保存为 base64 编码的 PNG 格式。这样,您得到的就是图像的平均颜色值。

image-20230827142002216

使用 Pillow 实现模糊效果:

from PIL import Image
import base64

def image_to_blurred_base64(image_path: str) -> str:
    with Image.open(image_path) as img:
        img = img.resize((20, 20), Image.ANTIALIAS)
        img = img.filter(ImageFilter.GaussianBlur(radius=2))

        buffered = BytesIO()
        img.save(buffered, format="JPEG")
        img_base64 = base64.b64encode(buffered.getvalue()).decode()

    return f"data:image/jpeg;base64,{img_base64}"
image-20230827142246203

quality :设置图片显示质量

Next.js 默认会使用图像优化 API 对图片进行优化,并转换为更现在的图片格式(默认为 webp ,也可以设置 avif )。

Quality 参数可以控制优化后的图像质量,默认为 75 。

可选值是 1 和 100 之间的整数,其中 100 是最佳质量,因此文件大小最大。

format :设置图片响应格式

默认的图像优化 API 将通过请求的 Accept 标头自动检测浏览器支持的图像格式。

如果 Accept 头与多个配置格式匹配,则使用数组中的第一个匹配项。因此,数组顺序很重要。如果不匹配(或者源图像是动画的),图像优化 API 将回退到原始图像的格式。

如果未提供配置,则使用以下默认值:

module.exports = {
  images: {
    formats: ['image/webp'],
  },
}

您可以通过以下配置启用 AVIF 支持:

module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],
  },
}

与 WebP 相比, AVIF 的编码时间通常要长 20%,但压缩率要小 20%。这意味着第一次请求图像时,通常会较慢,然后缓存的后续请求会更快。

关于 Next.js Image 组件的一些疑问

WebP 格式图片质量比较

我测试了 PNG 原图、 WebP ( 75 质量)以及 WebP ( 100 质量):

image-20230826220741404

结论如下:

格式图片大小图片质量描述
PNG 原图1.4MB在放大到原图模式下与 WebP ( 100 质量)看不出区别
WebP ( 100 质量)263KB在放大到原图模式下与 PNG 原图看不出区别
WebP ( 75 质量)44KB在缩略图模式下与其他格式看不出区别,放大后明显丢失细节

说真的, WebP 的表现让我惊叹!!!

如果在缩略图或小图模式下,建议使用 WebP 的 75 质量(也就是 Image 组件默认压缩率),图片大小只有 1/30~1/40 ,仍然可以得到非常好的效果。

如果是大图模式,可以使用 WebP 的 100 质量。

原图模式,可以在用户下载原图时再提供。

Next.js 图片优化与缓存机制

Q : Next.js 的 Image 组件会创建图片的 WebP 副本,并将副本存储在 Next 服务端,而不是后端服务器(例如 FastAPI )上,我的理解对吗?

A :是的,完全正确。

Q :那是否意味着:一张图片从第二次访问开始,就不会再和 FastAPI 后端交互?请求都是直接从 Next.js 服务器上 直接响应吗?

A :是的。在缓存图片有效期内,是这样的。当缓存图片过期之后,会重新加载优化一次。图像根据请求动态优化并存储在 <distDir>/cache/images 目录中。

Q :缓存多久更新一次?

A :缓存过期时间由配置的 minimumCacheTTL (默认值: 60 秒)或 Cache-Control 响应标头定义,以较大者为准。

Q :在两个不同的地方使用 Image 组件加载同一张图片,但是图片的压缩质量设置为不同的值。会同时缓存两个格式的图片吗?还是会覆盖?

A :在 Next.js 的 Image 组件中加载同一张图片,但使用不同的压缩质量或其他参数时, Next.js 会视这两种情况为两个不同的图片请求,并为每种情况分别生成和缓存优化后的图片。两个不同压缩质量的版本都会被缓存,而不会互相覆盖。这是因为每个优化后的图片版本都与其原始 URL 和其他属性(如质量、尺寸等)紧密相关。这种设计确保了您的页面总是从缓存中获取到与请求匹配的正确版本。

开发中遇到的问题及解决方案

Error: Invalid src prop x on next/image

问题详情:

出现以下报错:

image-20230826212632429

解决方法:

是由于没有正确设置 next.config.js 中的 remotePatterns 参数。

请参考【使用 Image 组件加载远程图片】 这一部分中的说明进行配置。

图片无法加载,服务器 500 错误

问题详情:

已经在 next.config.js 文件中正确设置 remotePatterns 参数,但是图片仍然无法加载,图片链接都是 500 错误:

image-20230826212316161

原因分析:

查看 Next.js 后台的错误信息,如果出现以下错误:

TypeError: fetch failed
    at Object.fetch (node:internal/deps/undici/undici:11457:11)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async imageOptimizer (/Users/ivensliao/PycharmProjects/shenbi-frontend-v2/node_modules/next/dist/server/image-optimizer.js:521:29)
    at async cacheEntry.imageResponseCache.get.incrementalCache (/Users/ivensliao/PycharmProjects/shenbi-frontend-v2/node_modules/next/dist/server/next-server.js:598:60)
    at async /Users/ivensliao/PycharmProjects/shenbi-frontend-v2/node_modules/next/dist/server/response-cache/index.js:99:36 {
  cause: Error: connect ECONNREFUSED ::1:8001
      at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1494:16) {
    errno: -61,
    code: 'ECONNREFUSED',
    syscall: 'connect',
    address: '::1',
    port: 8001
  }
}
image-20230826213750656

通常是因为在开发环境中调试时,图片 SRC 使用 localhost 作为域名的原因。

如果图片链接域名使用 localshot , Next.js 会默认优先使用 IPv6 中的::1来访问,如果主机本身不支持 IPv6 或 IPv6 配置不正确,就会出现这个错误。

::1 是 IPv6 中的本地地址,等同于 IPv4 中的 127.0.0.1

解决方案:

将 localhost 修改为 127.0.0.1 即可解决这个问题。