本文永久鏈接在 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,到這裡就完成了。