# Редактируемая система SVG-иконок

# Простой пример

Есть множество способов создания системы SVG-иконок (SVG Icon System), но один из способов, который использует возможности Vue — создание редактируемых встроенных иконок в виде компонентов. Некоторые преимущества подобного подхода:

  • Их легко редактировать «на лету»
  • Они анимируются
  • Можно использовать обычные входные параметры, со значениями по умолчанию, для сохранения стандартного размера или изменения по необходимости
  • Они встраиваемые, поэтому HTTP-запросы не требуются
  • Они могут быть доступны динамически

Создадим сначала каталог для всех иконок и назовём каждую в едином стиле, чтобы облегчить их поиск:

  • components/icons/IconBox.vue
  • components/icons/IconCalendar.vue
  • components/icons/IconEnvelope.vue

Вот репозиторий с примером для начала работы, где можно увидеть готовую настройку: https://github.com/sdras/vue-sample-svg-icons/ (opens new window)

Сайт документации

Теперь создадим компонент базовой иконки (IconBase.vue), который использует слот:

<template>
  <svg xmlns="http://www.w3.org/2000/svg"
    :width="width"
    :height="height"
    viewBox="0 0 18 18"
    :aria-labelledby="iconName"
    role="presentation"
  >
    <title
      :id="iconName"
      lang="en"
    >
      {{ iconName }} icon
    </title>
    <g :fill="iconColor">
      <slot />
    </g>
  </svg>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Можно использовать эту базовую иконку «как есть», но необходимо обновлять viewBox в зависимости от viewBox показываемой иконки. В базовом компоненте определяем входными параметрами width, height, iconColor и имя иконки, чтобы динамически их обновлять. Имя используем для содержимого <title> и для id для лучшей доступности.

Секция script станет выглядеть следующим образом: некоторые значения останутся по умолчанию, поэтому иконка будет отрисовываться всегда одинаково, пока не изменим её:

export default {
  props: {
    iconName: {
      type: String,
      default: 'box'
    },
    width: {
      type: [Number, String],
      default: 18
    },
    height: {
      type: [Number, String],
      default: 18
    },
    iconColor: {
      type: String,
      default: 'currentColor'
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Заданное по умолчанию свойство currentColor используется для цвета иконки и заставит её наследовать цвет текста. Но можно передать и другой цвет входным параметром, если захотим.

Компонент можно использовать следующим образом, передавая в слот единственным содержимым IconWrite.vue, содержащим пути внутри иконок:

<icon-base icon-name="write">
  <icon-write />
</icon-base>
1
2
3

Теперь если потребуется несколько иконок с разными размерами — всё очень просто:

<p>
  <!-- можно передавать меньшую `width` и `height` -->
  <icon-base
    width="12"
    height="12"
    icon-name="write"
  >
    <icon-write />
  </icon-base>

  <!-- или использовать значение по умолчанию, равное 18 -->
  <icon-base icon-name="write"><icon-write /></icon-base>

  <!-- или сделать её немного больше :) -->
  <icon-base
    width="30"
    height="30"
    icon-name="write"
  >
    <icon-write />
  </icon-base>
</p>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# Анимируемые иконки

Хранение иконок в компонентах очень удобно если потребуется их анимировать, особенно при взаимодействиях. Встроенные SVG-иконки имеют самую высокую поддержку для какого-либо взаимодействия. Вот простой пример иконки, которая анимируется при клике:

<template>
  <svg
    @click="startScissors"
    xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0 100 100"
    width="100"
    height="100"
    aria-labelledby="scissors"
    role="presentation"
  >
    <title
      id="scissors"
      lang="en"
    >
      Анимированная иконка с ножницами
    </title>
    <path
      id="bk"
      fill="#fff"
      d="M0 0h100v100H0z"
    />
    <g ref="leftscissor">
      <path d="M..."/>
      ...
    </g>
    <g ref="rightscissor">
      <path d="M..."/>
      ...
    </g>
  </svg>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { TweenMax, Sine } from 'gsap'

export default {
  methods: {
    startScissors() {
      this.scissorAnim(this.$refs.rightscissor, 30)
      this.scissorAnim(this.$refs.leftscissor, -30)
    },
    scissorAnim(el, rot) {
      TweenMax.to(el, 0.25, {
        rotation: rot,
        repeat: 3,
        yoyo: true,
        svgOrigin: '50 45',
        ease: Sine.easeInOut
      })
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Используем refs для групп элементов, которые будем перемещать. Также, так как обе стороны ножницы должны перемещаться вместе, создаём функцию, которую повторно используем при обращении к refs. Использование библиотеки GreenSock помогает разрешить поддержку анимации и проблемы с transform-origin во всех браузерах.

See the Pen Editable SVG Icon System: Animated icon by Vue (@Vue) on CodePen.

Довольно легко сделано! И легко обновлять «на лету».

Больше анимационных примеров можно посмотреть в репозитории (opens new window)

# Дополнительные замечания

Дизайнеры могут поменять своё мнение, требования к продукту измениться. Сохранение всей логики системы иконок в одном базовом компоненте обеспечит возможность быстрого обновления всех иконок по всему приложению. Даже при использовании загрузчика для иконок, некоторые ситуации могут потребовать пересоздания или редактирования каждой SVG-иконки при глобальных изменениях. Этот метод поможет сэкономить время и уменьшить боль.

# Когда не следует этого делать

Подобная система SVG-иконок действительно полезна, когда есть несколько иконок, которые используются по-разному на всём сайте. Но если дублируете одну и ту же иконку много раз на одной странице (например иконку удаления во всех строках гигантской таблицы), может имеет больше смысла сделать спрайты, скомпилированные в лист, а затем использовать теги <use> для их загрузки.

# Альтернативные варианты

Другие инструменты для помощи в управлении SVG-иконками включают:

Эти инструменты собирают SVG-иконки на этапе компиляции, что добавляет сложности для редактирования во время выполнения, потому что теги <use> могут иметь странные баги с кроссбраузерностью при выполнении чего-то сложного. Они также оставляют с двумя вложенными свойствами viewBox, а значит и двумя системами координат. Это делает реализацию несколько сложнее.