Lazy loading и responsive images

Основы интеграции изображений

При работе с Markdown через Remark и последующей обработке HTML через Rehype ключевым аспектом является управление изображениями. В стандартном Markdown синтаксисе изображения определяются через ![alt](src "title"). При трансформации через Remark и Rehype можно внедрять дополнительные атрибуты, необходимые для современных веб-приложений: loading="lazy", srcset, sizes и alt.

Remark преобразует Markdown в AST (Abstract Syntax Tree), где каждая сущность документа представлена узлом. Узел типа image имеет следующие свойства:

{
  type: 'image',
  title: 'Название изображения',
  url: '/images/picture.jpg',
  alt: 'Описание изображения'
}

Эти свойства можно использовать для генерации оптимизированного HTML с поддержкой lazy loading и responsive images.

Lazy loading изображений

Lazy loading — это отложенная загрузка изображений, когда они появляются в области видимости пользователя. В HTML это реализуется атрибутом:

<img src="/images/picture.jpg" loading="lazy" alt="Описание изображения">

Для интеграции в Remark/Rehype можно использовать плагин Rehype, который обходит все узлы типа image и добавляет атрибут loading="lazy":

const rehypeLazyImages = () => {
  return (tree) => {
    visit(tree, 'element', (node) => {
      if (node.tagName === 'img') {
        node.properties = node.properties || {};
        node.properties.loading = 'lazy';
      }
    });
  };
};

В этом коде:

  • visit — функция из библиотеки unist-util-visit для обхода дерева.
  • Узлы с tagName === 'img' получают атрибут loading.

Responsive images через srcset и sizes

Для адаптивной загрузки изображений браузеры используют атрибуты srcset и sizes. Пример структуры:

<img 
  src="/images/picture-800.jpg" 
  srcset="/images/picture-400.jpg 400w, /images/picture-800.jpg 800w, /images/picture-1200.jpg 1200w" 
  sizes="(max-width: 600px) 400px, (max-width: 900px) 800px, 1200px"
  alt="Описание изображения" 
  loading="lazy">

В Remark можно создавать плагин для генерации srcset на основе определённого правила именования файлов:

const rehypeResponsiveImages = () => {
  return (tree) => {
    visit(tree, 'element', (node) => {
      if (node.tagName === 'img') {
        const baseUrl = node.properties.src.replace(/\.\w+$/, '');
        const ext = node.properties.src.match(/\.\w+$/)[0];
        const srcset = [400, 800, 1200]
          .map(width => `${baseUrl}-${width}${ext} ${width}w`)
          .join(', ');
        node.properties.srcset = srcset;
        node.properties.sizes = '(max-width: 600px) 400px, (max-width: 900px) 800px, 1200px';
      }
    });
  };
};

Этот плагин автоматически генерирует набор изображений разных размеров, позволяя браузеру выбирать оптимальное изображение для текущего экрана.

Комбинированный подход: lazy loading + responsive images

Для полноценной оптимизации изображения можно объединить оба подхода:

const rehypeOptimizeImages = () => {
  return (tree) => {
    visit(tree, 'element', (node) => {
      if (node.tagName === 'img') {
        const baseUrl = node.properties.src.replace(/\.\w+$/, '');
        const ext = node.properties.src.match(/\.\w+$/)[0];
        node.properties.loading = 'lazy';
        node.properties.srcset = [400, 800, 1200]
          .map(width => `${baseUrl}-${width}${ext} ${width}w`)
          .join(', ');
        node.properties.sizes = '(max-width: 600px) 400px, (max-width: 900px) 800px, 1200px';
      }
    });
  };
};

Особенности работы с Markdown

При обработке Markdown через Remark важно помнить:

  1. Markdown может содержать как встроенные изображения, так и HTML-элементы <img>. Плагин Rehype должен корректно работать с обоими вариантами.
  2. Для большого числа изображений имеет смысл использовать кеширование и генерацию миниатюр заранее, чтобы не нагружать сервер динамической обработкой.
  3. В сочетании с MDX можно применять реактовские компоненты для изображений, поддерживающие lazy loading и responsive behavior, используя те же принципы srcset и sizes.

Пример цепочки Remark → Rehype

import { remark } from 'remark';
import remarkHtml from 'remark-html';
import { rehype } from 'rehype';
import rehypeRaw from 'rehype-raw';
import { visit } from 'unist-util-visit';

remark()
  .use(remarkHtml)
  .process('# Заголовок\n![Описание](/images/picture.jpg)')
  .then((file) => {
    rehype()
      .data('settings', { fragment: true })
      .use(rehypeRaw)
      .use(rehypeOptimizeImages)
      .process(file.toString())
      .then((html) => {
        console.log(String(html));
      });
  });

Эта цепочка демонстрирует:

  • Конвертацию Markdown в HTML.
  • Обход дерева и модификацию узлов <img> для добавления lazy loading и responsive атрибутов.

Рекомендации по производительности

  • Минимизировать размеры изображений через компрессию.
  • Использовать форматы WebP или AVIF, где это возможно.
  • Lazy loading особенно эффективен для страниц с большим количеством изображений.
  • srcset снижает загрузку ненужных ресурсов на мобильных устройствах.

В результате интеграция Remark и Rehype с оптимизацией изображений позволяет создавать современные, быстрые и адаптивные веб-страницы, сохраняя при этом гибкость Markdown и удобство AST-обработки.