NextJS提供了一个内置的图片组件, 它具有许多有用的功能,我们可以利用它们结合一些自定义样式来创建一个带有模糊加载效果的漂亮图片。
NOTE
以下所有示例都是使用React和TypeScript编写的,样式使用Tailwind CSS
模糊图片
这里的简单思路是首先让图片模糊(使用blur-xl
类),然后在图片加载完成时通过移除模糊效果(使用blur-0
)来淡入显示。
'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(
// 给父元素添加一个`image-container`类
// 使其更容易在mdx文件内容中调整样式
'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滤镜工具来创建模糊效果。 你可以通过将blur
工具与其他工具如grayscale、scale等混合使用来创建自己的变体。 (记得同时更新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配置 以使图片具有响应式特性。
module.exports = {
theme: {
extend: {
typography: ({ theme }) => ({
DEFAULT: {
css: {
'.image-container': {
width: 'fit-content',
marginLeft: 'auto',
marginRight: 'auto',
img: {
marginTop: 0,
marginBottom: 0,
},
},
// ... 更多排版样式
},
},
}),
},
},
}
避免在每次渲染时都模糊
模糊效果会在每次渲染Image
组件时发生(即使图片已经加载完成)。 如果你想避免这种情况,你需要手动控制loaded
状态:
'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
。
我的博客使用这个组件来渲染图片,你可以在网站上浏览并看到这个漂亮的加载效果。
快乐模糊!
On this page
← Previous post如何让Namecheap私人邮箱与Vercel DNS一起工作
Next post →全新 macOS 安装后的 Web 开发者最小化设置清单