如何在 NextJS 中创建带有模糊加载效果的图片

Closeup photo of white petaled flowers
Published on
/5 mins read/---

NextJS 提供了一个内置的图片组件,它具有许多有用的功能,我们可以利用这些功能并添加一些自定义样式来创建一个带有模糊加载效果的漂亮图片。

NOTE

以下所有示例都使用 React 和 TypeScript 编写,样式采用 Tailwind CSS

模糊图片

这里的简单思路是先让图片模糊(使用 blur-xl 类),然后当图片加载完成时通过移除模糊效果(使用 blur-0)来实现淡入效果。

image.tsx
'use client'
 
import { clsx } from 'clsx'
import type { ImageProps as NextImageProps } from 'next/image'
import NextImage from 'next/image'
import { useState } from 'react'
 
export interface ImageProps extends Omit<NextImageProps, 'src' | 'priority'> {
  src: string
}
 
export function Image(props: ImageProps) {
  let { alt, src, loading = 'lazy', style, className, ...rest } = props
  let [loaded, setLoaded] = useState(false)
 
  return (
    <div
      className={clsx(
        // Add a `image-container` class to the parent element
        // to make it easier to adjust the styles in mdx file content
        'image-container overflow-hidden',
        !loaded && 'animate-pulse [animation-duration:4s]',
        className
      )}
    >
      <NextImage
        className={clsx(
          '[transition:filter_500ms_cubic-bezier(.4,0,.2,1)]',
          'h-full max-h-full w-full object-center',
          loaded ? 'blur-0' : 'blur-xl'
        )}
        src={src}
        alt={alt}
        style={{ objectFit: 'cover', ...style }}
        loading={loading}
        priority={loading === 'eager'}
        quality={100}
        onLoad={() => setLoaded(true)}
        {...rest}
      />
    </div>
  )
}

我使用了 Tailwind 的 模糊滤镜 工具类来创建模糊效果。 你可以通过将 blur 工具类与其他类如 灰度缩放 等组合使用来创建自己的变体。 (记得同时更新 transition 属性)。

调整尺寸

该组件会自动根据子图片调整大小,你可以通过传入 className 来自定义其尺寸。 例如:

<Image
  src={logo}
  alt={org}
  className="h-12 w-12 shrink-0 rounded-md"
  style={{ objectFit: 'contain' }}
  width={200}
  height={200}
/>

MDX 支持

如果你想在 MDX 文件中使用这个组件来渲染图片,你需要更新 tailwind typography 配置来让图片具有响应式效果。

tailwind.config.js
module.exports = {
  theme: {
    extend: {
      typography: ({ theme }) => ({
        DEFAULT: {
          css: {
            '.image-container': {
              width: 'fit-content',
              marginLeft: 'auto',
              marginRight: 'auto',
              img: {
                marginTop: 0,
                marginBottom: 0,
              },
            },
            // ... more typography styles
          },
        },
      }),
    },
  },
}

避免每次渲染时都触发模糊效果

每次渲染 Image 组件时都会触发模糊效果(即使图片已经加载完成)。 如果你想避免这种情况,你需要手动控制 loaded 状态:

image.tsx
'use client'
 
import { clsx } from 'clsx'
import type { ImageProps as NextImageProps } from 'next/image'
import NextImage from 'next/image'
import { usePathname } from 'next/navigation'
import { useState } from 'react'
 
let loadedImages: string[] = []
 
// 检测图片是否已经加载过,以避免在每次组件渲染时
// 基于路由路径重复触发模糊效果
function useImageLoadedState(src: string) {
  let pathname = usePathname()
  let uniqueImagePath = pathname + '__' + src
  let [loaded, setLoaded] = useState(() => loadedImages.includes(uniqueImagePath))
  return [
    loaded,
    () => {
      if (loaded) return
      loadedImages.push(uniqueImagePath)
      setLoaded(true)
    },
  ] as const
}
 
export interface ImageProps extends Omit<NextImageProps, 'src' | 'priority'> {
  src: string
}
 
export function Image(props: ImageProps) {
  let { alt, src, loading = 'lazy', style, className, ...rest } = props
  let [loaded, onLoad] = useImageLoadedState(src)
 
  return (
    <div
      className={clsx(
        'image-container overflow-hidden',
        !loaded && 'animate-pulse [animation-duration:4s]',
        className
      )}
    >
      <NextImage
        className={clsx(
          '[transition:filter_500ms_cubic-bezier(.4,0,.2,1)]',
          'h-full max-h-full w-full object-center',
          loaded ? 'blur-0' : 'blur-xl'
        )}
        src={src}
        alt={alt}
        style={{ objectFit: 'cover', ...style }}
        loading={loading}
        priority={loading === 'eager'}
        quality={100}
        onLoad={onLoad}
        {...rest}
      />
    </div>
  )
}

现在图片只会在每个页面第一次加载时显示模糊效果。

TIP

如果你想优先加载首屏可见的图片,可以将 loading 属性设置为 eager

我的博客就使用了这个组件来渲染图片,你可以在网站上浏览不同页面来体验这个优雅的加载效果。

Happy blurring!