思考#
關於圖片懶加載的方法,基於Intersection Observer API實現起來很簡單。
但是在 Astro 中實現起來卻不同,因為這裡要實現的不是網站上的圖片懶加載,而是部落格中圖片的懶加載。
Astro 中的部落格是使用 Markdown 寫的,在 Frontmatter 中添加layout
來指定渲染的組件,我們在打開部落格時,組件已經加載。部落格中的 image 標籤都有了 src 屬性,已經去發送請求獲取數據了。
我一開始的想法是,在打開頁面之後,圖片發送請求之前,找到一個鉤子函數,把 image 的 src 屬性刪除,存到 data-src 中。一個比window.onload
更早的鉤子函數,但是沒有這樣的方法。
所以我就想在把 markdown 渲染成 html 的時候,把 image 標籤進行處理。這個思路應該是可行的,我就去翻看 Astro 的文檔,還真找到了配置方法markdown.remarkPlugins。
順著官網的線索在 github 中找,我找到了這個包:remarkjs/remark,裡面介紹了一段代碼示例可以將 h 標籤的層級縮小一級,即 h2 標籤會變成 h3 標籤。
import { visit } from 'unist-util-visit'
function myRemarkPluginToIncreaseHeadings() {
return (tree) => {
visit(tree, (node) => {
if (node.type === 'heading') {
node.depth++
}
})
}
}
原始 md 文檔:
# Hi, Saturn!
頁面 html 為:
<h1>Hi, Saturn</h1>
格式化後,頁面 html 為:
<h2>Hi, Saturn</h2>
從上面的示例中,我感覺到這個插件能夠滿足需求,於是開始動手實現。
實現#
在astro.config.mjs
中添加如下代碼:
import { visit } from 'unist-util-visit'
function myRemarkPluginToLazyLoadImage() {
return (tree) => {
visit(tree, (node) => {
if (node.type === 'image') {
// 將url屬性給alt,另外清空url,這樣頁面加載的時候,圖片沒有url屬性就不會加載
node.alt = node.url
node.url = ''
}
})
}
}
export default defineConfig({
..., // 其他配置
markdown: {
remarkPlugins: [myRemarkPluginToLazyLoadImage],
// 一定要加上這個,否則不會把md處理為html,而只處理插件內的代碼
extendDefaultPlugins: true,
}
})
在引入 md 的 astro 組件中,添加如下代碼:
<script>
/* 查找到部落格中的所有img標籤 */
var markdownBody = document.querySelector(".markdown-body");
let images = markdownBody.querySelectorAll("img");
const callback = (entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const image = entry.target;
const data_src = image.getAttribute("alt");
image.setAttribute("src", data_src);
observer.unobserve(image);
}
});
};
/* 給每個img標籤添加監聽方法 */
const observer = new IntersectionObserver(callback);
images.forEach((image) => {
observer.observe(image);
});
}
</script>
到這裡,我們的圖片懶加載就完成啦!