cirry

cirry

我的原生博客地址:https://cirry.cn

Implementing image lazy loading in Astro

Considerations#

When it comes to implementing image lazy loading, it is quite simple to do so using the Intersection Observer API.

However, implementing it in Astro is different because we are not dealing with lazy loading images on a website, but rather lazy loading images in a blog.

In Astro, blogs are written using Markdown, and the layout is specified in the Frontmatter to determine the rendering component. When we open a blog post, the component is already loaded. The image tags in the blog post already have the src attribute, and the data has already been fetched.

Initially, my idea was to find a hook function that runs before the images send requests, remove the src attribute from the image tags, and store it in data-src. I was looking for a hook function that runs earlier than window.onload, but there is no such method available.

So, I thought about handling the image tags while rendering Markdown to HTML. This approach should work, so I went through the Astro documentation and found the configuration method markdown.remarkPlugins.

Following the clues on the official website, I searched on GitHub and found this package: remarkjs/remark. It provides a code example that reduces the heading level by one, meaning that an h2 tag becomes an h3 tag.

import { visit } from 'unist-util-visit'

function myRemarkPluginToIncreaseHeadings() {
  return (tree) => {
    visit(tree, (node) => {
      if (node.type === 'heading') {
        node.depth++
      }
    })
  }
}

Original Markdown document:

# Hi, Saturn!

The resulting HTML page:

<h1>Hi, Saturn</h1>

After formatting, the resulting HTML page becomes:

<h2>Hi, Saturn</h2>

From the above example, I felt that this plugin could meet the requirements, so I started implementing it.

Implementation#

Add the following code to astro.config.mjs:

import { visit } from 'unist-util-visit'

function myRemarkPluginToLazyLoadImage() {
  return (tree) => {
    visit(tree, (node) => {
      if (node.type === 'image') {
        // Assign the url attribute to alt and clear the url attribute so that the image won't load when the page loads
        node.alt = node.url 
        node.url = ''
      }
    })
  }
}

export default defineConfig({
  ..., // Other configurations
  markdown: {
    remarkPlugins: [myRemarkPluginToLazyLoadImage],
    // Make sure to include this, otherwise the md won't be processed into html, only the code within the plugin will be processed
    extendDefaultPlugins: true, 
  }
})

Add the following code to the Astro component where the Markdown is imported:

<script>
/* Find all img tags in the blog */
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);
      }
    });
  };
/* Add the listener method to each img tag */
  const observer = new IntersectionObserver(callback);
  images.forEach((image) => {
    observer.observe(image);
  });
}
</script>

And that's it! Our image lazy loading is now complete!

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.