Другое

Исправление проблем отображения SVG‑спрайтов в React

Решите проблемы отображения SVG‑спрайтов в React с правильной настройкой Webpack, параметрами publicPath и кроссбраузерными исправлениями для проектов на TypeScript.

Как исправить проблемы отображения SVG‑иконок в React‑приложении с использованием спрайтов?

Я работаю с SVG‑иконками в приложении на TypeScript/React, где иконки хранятся в одном SVG‑файле с тегами id. Я пытаюсь ссылаться на иконки через тег <use>, но они не отображаются корректно.

Мой текущий код:

jsx
<svg className="icon icon-lg" aria-hidden="true" width="24" height="24">
  <use href={`/assets/svg/app-sprite.svg#snow`}></use>
</svg>

Файл SVG находится по пути src/assets/svg/app-sprite.svg.

Если я напрямую встраиваю код SVG в компонент, всё работает:

jsx
<svg style={{ display: 'none' }}>
  <symbol id="snow" viewBox="-28.6 -28.7 57.1 57.3">
    <g>
      <polygon fill="#0971B8" points="28.6,2.8 25.7,12.7 7.1,7.4 11.8,26.2 1.9,28.7 -2.8,9.9 -16.8,23.3 -23.9,15.9 -10,2.5 
        -28.6,-2.8 -25.7,-12.7 -7.1,-7.4 -11.8,-26.2 -1.8,-28.7 2.8,-9.9 16.8,-23.3 23.9,-15.9 10,-2.5      "/>
    </g>
  </symbol>
</svg>

Я заметил, что если переместить файл SVG в каталог public/assets/svg/app-sprite.svg, проблема исчезает, что указывает на связь с процессом сборки.

Что мне нужно сделать, чтобы правильно настроить сборку и обрабатывать спрайты SVG в React‑приложении?

SVG‑спрайты в React часто не отображаются из‑за проблем в конфигурации сборки, особенно того, как Webpack обрабатывает пути к файлам и обслуживает статические ресурсы. Ключевая проблема в том, что SVG‑файлы в каталоге src обрабатываются загрузчиками Webpack, тогда как тег <use> требует, чтобы спрайт был доступен как статический ресурс с корректным URL‑путём.

Содержание

Понимание причины

Проблема, с которой вы сталкиваетесь, связана с тем, как React и Webpack обрабатывают импорты файлов и обслуживают ресурсы. Когда вы размещаете свой SVG‑спрайт в src/assets/svg/app-sprite.svg, Webpack обрабатывает его через различные загрузчики, возможно, меняя путь или не обслуживая его так, чтобы тег <use> мог корректно ссылаться на него.

Как отмечено в исследовании, «обычно требуется ссылка/URL на файл спрайта, либо SVG‑элементы должны существовать в DOM» [Stack Overflow]. Тег <use> требует, чтобы спрайт был доступен по URL, который браузер может разрешить, поэтому перемещение его в каталог public работает – эти файлы обслуживаются напрямую веб‑сервером без обработки Webpack.

Встраивание SVG напрямую в компонент работает, потому что элементы <symbol> присутствуют в DOM, когда <use> пытается их использовать. Однако такой подход лишает преимущества использования спрайтов для оптимизации производительности.

Решения конфигурации Webpack

Решение 1: Настройка svg-sprite-loader

Самый надёжный способ – настроить Webpack для правильной обработки SVG‑спрайтов с помощью специализированных загрузчиков. Согласно исследованию, вам нужно настроить svg-sprite-loader с правильным параметром publicPath:

javascript
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        use: [
          {
            loader: 'svg-sprite-loader',
            options: {
              extract: true,
              publicPath: '/assets/svg/',
              spriteFilename: 'app-sprite.svg'
            }
          }
        ]
      }
    ]
  }
}

Как объясняется в документации svg‑sprite‑loader, «по умолчанию он использует publicPath», поэтому вам нужно убедиться, что это значение соответствует реальному пути к файлу.

Решение 2: Использование external-svg-sprite-loader

Для более сложных сценариев рассмотрите external-svg-sprite-loader:

javascript
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        use: [
          {
            loader: 'external-svg-sprite-loader',
            options: {
              publicPath: '/assets/svg/',
              spriteFilename: 'app-sprite.svg'
            }
          }
        ]
      }
    ]
  }
}

Этот загрузчик предоставляет «пользовательский publicPath, который будет использоваться вместо output.publicPath» согласно его npm‑документации.

Решение 3: Создание компонента SVG‑спрайта

Создайте отдельный компонент для работы со спрайт‑иконками:

jsx
// SvgIcon.jsx
import React from 'react';

const SvgIcon = ({ name, className = '', width = 24, height = 24, ariaHidden = true }) => {
  return (
    <svg
      className={`icon ${className}`}
      aria-hidden={ariaHidden}
      width={width}
      height={height}
      viewBox="0 0 24 24"
    >
      <use href={`/assets/svg/app-sprite.svg#${name}`} />
    </svg>
  );
};

export default SvgIcon;

Затем используйте его в ваших компонентах:

jsx
import SvgIcon from './SvgIcon';

// Пример использования
<SvgIcon name="snow" className="icon-lg" />

Альтернативные подходы к реализации

Подход 1: Использование process.env.PUBLIC_URL

Для проектов, созданных с помощью Create React App, можно использовать встроенную переменную окружения PUBLIC_URL:

jsx
<svg className="icon icon-lg" aria-hidden="true" width="24" height="24">
  <use href={`${process.env.PUBLIC_URL}/assets/svg/app-sprite.svg#snow`}></use>
</svg>

Такой подход гарантирует, что путь корректно разрешается как в режиме разработки, так и в продакшене.

Подход 2: Динамический импорт с React.lazy

Для лучшей производительности при работе с большими спрайт‑таблицами:

jsx
const SvgIcon = React.lazy(() => import('./SvgIcon'));

// Использование
<Suspense fallback={<div>Loading icon...</div>}>
  <SvgIcon name="snow" />
</Suspense>

Подход 3: Использование SVG как React‑компонентов

Преобразуйте отдельные SVG‑иконки в React‑компоненты:

jsx
// icons/SnowIcon.jsx
import React from 'react';

const SnowIcon = ({ className = '', width = 24, height = 24, ariaHidden = true }) => {
  return (
    <svg
      className={`icon ${className}`}
      aria-hidden={ariaHidden}
      width={width}
      height={height}
      viewBox="-28.6 -28.7 57.1 57.3"
    >
      <polygon fill="#0971B8" points="28.6,2.8 25.7,12.7 7.1,7.4 11.8,26.2 1.9,28.7 -2.8,9.9 -16.8,23.3 -23.9,15.9 -10,2.5 -28.6,-2.8 -25.7,-12.7 -7.1,-7.4 -11.8,-26.2 -1.8,-28.7 2.8,-9.9 16.8,-23.3 23.9,-15.9 10,-2.5"/>
    </svg>
  );
};

export default SnowIcon;

Такой подход обеспечивает лучшую tree‑shaking и избавляет от необходимости использовать спрайты.

Исправления для Safari

Некоторые браузеры, особенно Safari, имеют специфические требования к использованию SVG‑спрайтов. Как упоминалось в исследовании, изображения из спрайтов могут не отображаться в iOS Safari при использовании React. Ниже приведены исправления:

Исправление 1: Добавление необходимых атрибутов

Убедитесь, что ваши SVG‑элементы имеют все необходимые атрибуты:

jsx
<svg
  className="icon icon-lg"
  aria-hidden="true"
  width="24"
  height="24"
  xmlns="http://www.w3.org/2000/svg"
  xmlnsXlink="http://www.w3.org/1999/xlink"
>
  <use href={`/assets/svg/app-sprite.svg#snow`} />
</svg>

Исправление 2: Предварительная загрузка файла спрайта

Добавьте ссылку preload в <head> вашего HTML:

html
<link rel="preload" href="/assets/svg/app-sprite.svg" as="image" crossorigin="anonymous">

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

Исправление 3: Fallback для Safari

Реализуйте fallback для браузеров, которые не поддерживают SVG‑спрайты:

jsx
const SvgIcon = ({ name, className = '', width = 24, height = 24, ariaHidden = true }) => {
  return (
    <>
      <svg
        className={`icon ${className}`}
        aria-hidden={ariaHidden}
        width={width}
        height={height}
        style={{ display: 'none' }}
      >
        <use href={`/assets/svg/app-sprite.svg#${name}`} />
      </svg>
      <svg
        className={`icon ${className}`}
        aria-hidden={ariaHidden}
        width={width}
        height={height}
        dangerouslySetInnerHTML={{
          __html: `<polygon fill="#0971B8" points="..."/>`
        }}
      />
    </>
  );
};

Лучшие практики использования SVG‑спрайтов в React

  1. Организуйте структуру файлов: храните спрайты в отдельном каталоге и придерживайтесь единых соглашений по именованию.
  2. Используйте сборочные инструменты: применяйте загрузчики Webpack, специально предназначенные для SVG‑спрайтов, чтобы автоматизировать процесс.
  3. Оптимизируйте производительность: используйте инструменты вроде SVGO для оптимизации SVG‑файлов перед добавлением в спрайт.
  4. Поддержка TypeScript: создайте корректные типы для компонентов иконок:
typescript
// types/index.ts
export interface SvgIconProps {
  name: string;
  className?: string;
  width?: number;
  height?: number;
  ariaHidden?: boolean;
  viewBox?: string;
}
  1. Тестирование: проверяйте отображение иконок в разных браузерах и устройствах.
  2. Доступность: всегда добавляйте подходящие ARIA‑метки и атрибуты.
  3. Кеширование: реализуйте правильные стратегии кеширования для файлов спрайтов, чтобы улучшить время загрузки.

Реализовав эти рекомендации, вы сможете решить проблемы с отображением SVG‑спрайтов в вашем React‑приложении. Ключевой момент – убедиться, что процесс сборки корректно обрабатывает SVG‑файлы и делает их доступными для <use> с правильными URL‑путями.

Источники

  1. Use svg sprite icons in React - jacobparis.com
  2. Why this SVG image from sprite‑sheet is not getting rendered in HTML but not in React? - Stack Overflow
  3. How to configure webpack to use a prebuilt svg sprite? - Stack Overflow
  4. svg‑sprite‑loader - npm
  5. external‑svg‑sprite‑loader - npm
  6. How to implement and draw external SVG sprites - DEV Community
  7. SVG sprite images not showing on iOS & Safari using React - Stack Overflow

Заключение

Проблемы с отображением SVG‑спрайтов в React‑приложениях обычно связаны с настройками сборки. Понимание того, как Webpack обрабатывает SVG‑файлы, и правильная конфигурация позволяют решить эти проблемы. Основные решения включают:

  1. Настройку загрузчиков Webpack (svg-sprite-loader с корректным publicPath).
  2. Использование process.env.PUBLIC_URL для единообразного разрешения путей.
  3. Создание специализированных компонентов SVG‑иконок с надёжной обработкой ошибок.
  4. Добавление атрибутов и preload‑ссылок для Safari.
  5. Рассмотрение альтернативных подходов (конвертация SVG в React‑компоненты) для лучшего tree‑shaking.

Выберите подход, который лучше всего подходит для вашего проекта и требований к производительности. Для большинства приложений оптимальная конфигурация Webpack с svg-sprite-loader обеспечивает лучший баланс между производительностью и поддерживаемостью.

Авторы
Проверено модерацией
Модерация