本文永久链接在 nextjs 中添加图像的模糊 placeholder | Travis' Blog
本文内容来自 How to: Blurred images on load in Next.js,修改代码在我的这个提交中
介绍#
Next.js 的 Image 组件支持 blurDataURL 参数,可以在图片完全加载之前先使用 base64 的图片来充当占位符。
为了可以在 mdx 中用 markdown 语法控制图片位置,并且模糊图片的生成过程应该是在服务器 build 时生成的,而不是前端 Get 到图片之后再生成,我们不能把生成过程写在图片组件当中。
而应该在 mdx 渲染到 tsx 对象时进行,为了控制这个过程,我使用了 rehype 插件1。
我的 mdx 文件,由 contentlayer 负责管理,先用 remark 从 mdx 转换为 html,再使用 rehype 进行一些后处理,转换为 tsx 进行渲染,这一系列流程都是在next build
阶段完成的,恰好满足了需求。
开始#
- 首先安装下所需要用的包,
plaiceholder
是一个可以利用图片生成对应模糊占位图的包,可以直接生成 base64 格式。
pnpm i plaiceholder unist-util-visit
- 接下来只需要找到对应的 img 标签,读取对应的 src 文件,然后通过 pliceholder 生成对应的 base64 字符串,放入 img 的 blurDataURL 属性当中,接下来用我们自己的 Component 处理一下参数就可以了。
import { getPlaiceholder } from "plaiceholder";
import { visit } from "unist-util-visit";
// Just to check if the node is an image node
function isImageNode(node) {
const img = node;
return (
img.type === "element" &&
img.tagName === "img" &&
img.properties &&
typeof img.properties.src === "string"
);
}
// Returns the props of given `src` to use for blurred images
export async function returnProps(src) {
const { base64: blurDataURL, img } = await getPlaiceholder(src);
// If an error happened calculating the resolution, throw an error
if (!img) throw Error(`Invalid image with src "${src}"`);
return {
blurDataURL,
};
}
async function addProps(node) {
// return the new props we'll need for our image
const { blurDataURL } = await returnProps(node.properties.src);
node.properties.blurDataURL = blurDataURL;
}
const imageMetadata = () => {
return async function transformer(tree) {
// Create an array to hold all of the images from the markdown file
const images = [];
visit(tree, "element", (node) => {
// Visit every node in the tree, check if it's an image and push it in the images array
if (isImageNode(node)) {
images.push(node);
}
});
for (const image of images) {
// Loop through all of the images and add their props
await addProps(image);
}
return tree;
};
};
export default imageMetadata;
- 处理一下 img 标签,把 img 转换成 Next.js 当中的
Image
import Image from "next/image";
export default function MyImage({
src,
width,
height,
alt,
blurDataURL,
}: any) {
return (
<div className="not-prose mx-2 mt-4 mb-0 break-inside-avoid-page">
<div>
<Image
src={`${src}`}
width={width}
height={height}
alt={alt}
blurDataURL={blurDataURL}
placeholder="blur"
/>
</div>
</div>
);
}
- 在 contentlayer 设置中加入刚刚写好的 rehype 插件
import imageMetadata from "./lib/imageMetadata";
...
export default makeSource({
contentDirPath: "data",
documentTypes: [Blog, Movie, About, Idea],
mdx: {
rehypePlugins: [
[rehypeImgSize, { dir: "public" }],
rehypeCodeTitles,
imageMetadata,
[rehypePrism, { showLineNumbers: true }],
rehypeKatex,
rehypeSlug,
],
},
});
OK,到这里就完成了。