Icon.  Оверинжиниринг      или необходимость?

  Кто я такой?

ex-HeadHunter

Работал в команде архитектуры

— Не только перекладываю файлы по папкам

— Помогаю разработчикам писать код

  Кто я такой?

ex-HeadHunter

Работал в команде архитектуры

— Не только перекладываю файлы по папкам

— Помогаю разработчикам писать код

Да я же FrontУпс!

  Кто я такой?

ex-HeadHunter

Работал в команде архитектуры

— Не только перекладываю файлы по папкам

— Помогаю разработчикам писать код

Да я же FrontУпс!

Кстати, HeadHunter ищет разработчиков в продуктовую  команду

*

Дизайнеры

  Базовые иконки

  Базовые иконки

  Базовые иконки

  Базовые иконки

  Базовые иконки

  (не)Базовые иконки

  (не)Базовые иконки

  (не)Базовые иконки

  Надо понимать 2 вещи

  Надо понимать 2 вещи

Дизайнеры — хорошие

  Это все лирика

1. Есть базовые иконки в UIKit

 

 

1. Есть базовые иконки в UIKit

 

2. Есть кастомные иконки

 

 

1. Есть базовые иконки в UIKit

 

2. Есть кастомные иконки

 

3. Размер-цвет отличаются

 

 

1. Есть базовые иконки в UIKit

 

2. Есть кастомные иконки

 

3. Размер-цвет отличаются

 

4. Большинство иконкок одноцветные

  Что будем делать?

1. Разберем разные способы

 

 

1. Разберем разные способы

 

2. Возьмем один модный и популярный

 

 

1. Разберем разные способы

 

2. Возьмем один модный и популярный

 

3. Проанализируем

 

 

1. Разберем разные способы

 

2. Возьмем один модный и популярный

 

3. Проанализируем

 

4. Постараемся избавиться от минусов

 

У всех способов есть плюсы и минусы, особенный только один

Скрин-ридеры

Скрин-ридеры

Скрин-ридеры

Их отображение зависит от:

Системы, браузера, настроек системы, настроек видеокарты и  положения луны относительно солнца

Можно ненадо

  Что еще?

1. Спрайты

 

 

<svg width="124" height="16" viewBox="0 0 124 16" 
	 xmlns="http://www.w3.org/2000/svg" 
     xmlns:xlink="http://www.w3.org/1999/xlink">
<symbol id="filters" width="16" height="16">
    <path fill-rule="evenodd" clip-rule="evenodd" d="M1 4.667c0-.392.317-.709.708-.709h6a.708.708 0 1 1 0 1.417h-6A.708.708 0 0 1 1 4.667zM12.334 4.667c0-.392.317-.709.708-.709h1.333a.708.708 0 0 1 0 1.417h-1.333a.708.708 0 0 1-.708-.708z"/>
</symbol>
  <use x="0" y="0" xlink:href="#filters" fill="#3d3e42"/>
  <use x="18" y="0" xlink:href="#filters" fill="#fff"/>
</svg>

  Спрайты

  Спрайты

👎 Анимации https://codepen.io/collection/DYwzZe

  Спрайты

👎 Анимации https://codepen.io/collection/DYwzZe

👎 Количество вариаций ограниченно текущими CSS классами. Приходится либо расширять все, либо костылять нужные иконки

  Спрайты

👎 Анимации https://codepen.io/collection/DYwzZe

👎 Количество вариаций ограниченно текущими CSS классами. Приходится либо расширять все, либо костылять нужные иконки

👍 Кэш

  Спрайты

👎 Анимации https://codepen.io/collection/DYwzZe

👎 Количество вариаций ограниченно текущими CSS классами. Приходится либо расширять все, либо костылять нужные иконки

👍 Кэш

👍 Цвета

  Что еще?

1. Спрайты

 

2. CSS-Filters

 

 

  CSS-Filters

.A {
  filter: invert(30%) sepia(93%) saturate(7246%) hue-rotate(356deg)
    brightness(102%) contrast(120%);
}

.B {
  filter: invert(10%) sepia(99%) saturate(4712%) hue-rotate(243deg)
    brightness(108%) contrast(145%);
}

.C {
  filter: invert(54%) sepia(48%) saturate(1714%) hue-rotate(79deg)
    brightness(113%) contrast(126%);
}

  CSS-Filters

.red {
  filter: invert(30%) sepia(93%) saturate(7246%) hue-rotate(356deg)
    brightness(102%) contrast(120%);
}

.blue {
  filter: invert(10%) sepia(99%) saturate(4712%) hue-rotate(243deg)
    brightness(108%) contrast(145%);
}

.green {
  filter: invert(54%) sepia(48%) saturate(1714%) hue-rotate(79deg)
    brightness(113%) contrast(126%);
}

  CSS-Filters

.red {
  filter: invert(30%) sepia(93%) saturate(7246%) hue-rotate(356deg)
    brightness(102%) contrast(120%);
}

.blue {
  filter: invert(10%) sepia(99%) saturate(4712%) hue-rotate(243deg)
    brightness(108%) contrast(145%);
}

.green {
  filter: invert(54%) sepia(48%) saturate(1714%) hue-rotate(79deg)
    brightness(113%) contrast(126%);
}

Согласны на ревью такого?

  CSS-Filters

👍 Анимация

  CSS-Filters

👍 Анимация

👍 Кэш

  CSS-Filters

👍 Анимация

👍 Кэш

👎 Одноцветные иконки

  CSS-Filters

👍 Анимация

👍 Кэш

👎 Одноцветные иконки

👎 Не ревью-френдли

  CSS-Filters

  Что еще?

1. Спрайты

 

2. CSS-Filters

 

3. CSS-Mask

 

  CSS-Mask

.clipped {
  clip-path: polygon(50% 0%, 35% 63%, 82% 100%, 18% 100%, 0% 38%);
}

  CSS-Mask

.clipped {
  clip-path: polygon(50% 0%, 35% 63%, 82% 100%, 18% 100%, 0% 38%);
}

  CSS-Mask

.clipped {
  clip-path: polygon(50% 0%, 35% 63%, 82% 100%, 18% 100%, 0% 38%);
}

.masked {
  mask: url(mask.svg);
}

  CSS-Mask

  Что еще?

1. Спрайты

 

2. CSS-Filters

 

3. CSS-Mask

 

4. inline

 

5. inline + use

  Inline

  Inline

<svg width="50" height="40" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
  <g>
    <path fill="none" d="M0,0h48v48h-48Z"></path>
    <path fill="#ff0000" d="M24,9c-10,0 -18.54,6.22 -22,15c3.46,8.78 12,15 22,15c10,0 18.54,-6.22 22,-15c-3.46,-8.78 -11.99,-15 -22,-15Zm0,25c-5.52,0 -10,-4.48 -10,-10c0,-5.52 4.48,-10 10,-10c5.52,0 10,4.48 10,10c0,5.52 -4.48,10 -10,10Zm0,-16c-3.31,0 -6,2.69 -6,6c0,3.31 2.69,6 6,6c3.31,0 6,-2.69 6,-6c0,-3.31 -2.69,-6 -6,-6Z"></path>
  </g>
</svg>

  Inline

  Inline

👎 SSR не френдли?

  Inline

👎 SSR не френдли?

👎 Как разделять код компонента и иконки?

  Inline

Base64 более тяжелый, чем просто иконка, поэтому для простоты будем считать что одна иконка ≈ 400 байт

  Inline

  Inline

  Inline

  Inline

  Inline

  Inline

  Inline

  Inline

  Inline

  Inline

  Inline

10

  Inline

10 + 2?

  Inline

👎 SSR не френдли? — кэш + дублирование

  Inline

  Inline

10 + 7 + 2? + 1?

  Inline

(10 + 7 + 2+ 1) * 400 ≈ 8 Кб

Этот код будет загружен до FMP

  Inline

👎 SSR не френдли?

     кэш

     дублирование

     размер при большом количестве иконок на странице

8 Килобайт, зачем страдать?

(100 + чуть-чуть) * почти полкилобайта = Около 40, а то и 50 Кб

Код, вставленный as is в js компонент, не соптимизируется svgo

Нужно прогонять иконки в svgo, прежде чем вставить на страницу!

А я в себя не верю

  Inline

👎 SSR не френдли?

     кэш

     дублирование

     размер при большом количестве иконок на странице

  Inline

👎 SSR не френдли?

     кэш

     дублирование

     размер при большом количестве иконок на странице

👎 Как разделять код компонента и иконки?

  Оптимизации

👎 SSR не френдли?

     кэш

     дублирование

     размер при большом количестве иконок на странице

👎 Как разделять код компонента и иконки?

  Логика

Давайте начнем с логики

Теперь будет (хард)код

  Разделение

function Icon({ fill, width = 50, height = 40, additionalClassName }) {
  return (
    <svg width={width} height={height}>
      <g className={classnames('icon', additionalClassName)}>
        <path fill="none" d="M0,0h48v48h-48Z"></path>
        <path fill={fill} d="M24,9c-10,0 -18.54,6.22 -22,15c3.46,8.78 12,15 22,15c10,0 18.54,-6.22 22,-15c-3.46,-8.78 -11.99,-15 -22,-15Zm0,25c-5.52,0 -10,-4.48 -10,-10c0,-5.52 4.48,-10 10,-10c5.52,0 10,4.48 10,10c0,5.52 -4.48,10 -10,10Zm0,-16c-3.31,0 -6,2.69 -6,6c0,3.31 2.69,6 6,6c3.31,0 6,-2.69 6,-6c0,-3.31 -2.69,-6 -6,-6Z"></path>
      </g>
    </svg>
  );
}

Всё ещё бойлерплейтишь? автоматизируй!

  Идея

const fs = require('fs');
const { getOptions } = require('loader-utils');
module.exports = function(source) {
    if (this.cacheable) {
        this.cacheable();
    }  
    const options = getOptions(this);
    const template = fs.readFileSync(options.template, 'utf8');

    return template.replace(
        '<icon />',
        source
            .replace('{width}', 'width={width}')
            .replace('{height}', 'height={height}')
            .replace('class="{class}"', 'className={classNames}')
            .replace('{fill}', 'fill={fill}')
            .replace('{stroke}', 'stroke={stroke}');
    );
};
function Icon({ 
  id, fill, stroke, loading, 
  width = 16, height = 16, highlight, disabledColor, 
  additionalClasses = []
}) {
    const classNames = classnames('bloko-icon', additionalClasses, {
        'bloko-icon_loading': loading,
        [`bloko-icon_highlight-${highlight}`]: highlight,
        [`bloko-icon_disabled`]: disabledColor,
    });

    return (
       <icon />
    );
}

  Преимущества

👍 Можно автоматизированно запускать SVGO-loader

  Преимущества

👍 Можно автоматизированно запускать SVGO-loader

👍 Логика разделена, не нужно бойлерплейтить

  Преимущества

👍 Можно автоматизированно запускать SVGO-loader

👍 Логика разделена, не нужно бойлерплейтить

👍 Кастомизируемо:

     Разные цвета

     Анимации

     Все что угодно

Это решение не помогает SSR

* В плане кэша, дублирования и размеров

  SSR-френдли

<svg width={width} height={height}>
  <g className={classnames('icon', additionalClassName)}>
    <path fill="none" d="M0,0h48v48h-48Z"></path>
    <path fill={fill} d="M24,9c-10,0 -18.54,6.22 -22,15c3.46,8.78 12,15 22,15c10,0 18.54,-6.22 22,-15c-3.46,-8.78 -11.99,-15 -22,-15Zm0,25c-5.52,0 -10,-4.48 -10,-10c0,-5.52 4.48,-10 10,-10c5.52,0 10,4.48 10,10c0,5.52 -4.48,10 -10,10Zm0,-16c-3.31,0 -6,2.69 -6,6c0,3.31 2.69,6 6,6c3.31,0 6,-2.69 6,-6c0,-3.31 -2.69,-6 -6,-6Z"></path>
  </g>
</svg>

  SVG store!

<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">  
  <symbol id="arrow" viewBox="0 0 16 15">
    <path />
  </symbol>
  
  <symbol id="eye" viewBox="0 0 50 50">
    ...
  </symbol>
</svg>

  SVG store!

<svg class="bloko-icon">
  <use xlink:href="#arrow" />
</svg>

<svg class="bloko-icon ...">
  <use xlink:href="#eye" width="50" height="50" />
</svg>
module.exports = function(source) {
    if (this.cacheable) {
        this.cacheable();
    }  
    const options = getOptions(this);
    const template = fs.readFileSync(options.template, 'utf8');

    return template.replace(
        '<icon />',
        source
            .replace('{width}', 'width={width}')
            .replace('{height}', 'height={height}')
            .replace('class="{class}"', 'className={classNames}')
            .replace('{fill}', 'fill={fill}')
            .replace('{stroke}', 'stroke={stroke}');
    );
};
module.exports = function(source) {
    if (this.cacheable) {
        this.cacheable();
    }  
    const options = getOptions(this);
    const template = fs.readFileSync(options.template, 'utf8');
  
    const id = this.resourcePath.replace(/\//g, '-');
  
    return template.replace(
      '<icon />',
      `<use 
        xlink:href="#${id}" 
        width={width}
        height={height}
        fill={fill}	
        stroke={stroke}
      />`          
    );
};
module.exports = function(source) {
    if (this.cacheable) {
        this.cacheable();
    }  
    const options = getOptions(this);
    const template = fs.readFileSync(options.template, 'utf8');
  
    const id = this.resourcePath.replace(/\//g, '-');
  
    fs.writeFileSync(`node_modules/our-loader/${this.resourcePath}`, 
                     source.replace('{id}', id));
  
    return template.replace(
      '<icon />',
      `<use 
        xlink:href="#${id}" 
        width={width}
        height={height}
        fill={fill}	
        stroke={stroke}
      />`          
    );
};

Это законно?

fs.writeFileSync(`node_modules/our-loader/${this.resourcePath}`, 
                 source.replace('{id}', id));
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">  
  <symbol id="arrow" viewBox="0 0 16 15">
    <path />
  </symbol>
  
  <symbol id="eye" viewBox="0 0 50 50">
    ...
  </symbol>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">  
  <symbol id="arrow" viewBox="0 0 16 15">
    <path />
  </symbol>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">    
  <symbol id="eye" viewBox="0 0 50 50">
    ...
  </symbol>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">  
  <symbol id="arrow" viewBox="0 0 16 15">
    <path />
  </symbol>
  
  <symbol id="eye" viewBox="0 0 50 50">
    ...
  </symbol>
  <symbol id="arrow" viewBox="0 0 16 15">
    <path />
  </symbol>
  <symbol id="eye" viewBox="0 0 50 50">
    ...
  </symbol>
</svg>

(100 + чуть-чуть) * полкилобайта

(1 + чуть-чуть) * полкилобайта

(1 + чуть-чуть) * полкилобайта + 100 * 60 байт

(1 + чуть-чуть) * полкилобайта + 100 * 60 байт

6 Кб

≈60 Кб

6.5 Кб

VS

SVG Store

SVG Store

  Lazy loading?

А как грузить?

  Как грузить?

  Как грузить?

  Как грузить?

  Как грузить?

  Как грузить?

  Как грузить?

  Как грузить?

👍 Анимация

  Как грузить?

👍 Анимация

👍 Кэш

  Как грузить?

👍 Анимация

👍 Кэш

👍 Грузим только нужные иконки

  Итого

Inline один из наиболее удобных способов, он позволяет нам решить практически все проблемы

Inline расширяем

Готовы ли вы

такое поддерживать?

Sprite

<svg width="124" height="16" viewBox="0 0 124 16" 
	 xmlns="http://www.w3.org/2000/svg" 
     xmlns:xlink="http://www.w3.org/1999/xlink">
<symbol id="filters" width="16" height="16">
    <path fill-rule="evenodd" clip-rule="evenodd" d="M1 4.667c0-.392.317-.709.708-.709h6a.708.708 0 1 1 0 1.417h-6A.708.708 0 0 1 1 4.667zM12.334 4.667c0-.392.317-.709.708-.709h1.333a.708.708 0 0 1 0 1.417h-1.333a.708.708 0 0 1-.708-.708z"/>
</symbol>
  <use x="0" y="0" xlink:href="#filters" fill="#3d3e42"/>
  <use x="18" y="0" xlink:href="#filters" fill="#fff"/>
</svg>

Sprite

CSS-Filters

<svg width="124" height="16" viewBox="0 0 124 16" 
	 xmlns="http://www.w3.org/2000/svg" 
     xmlns:xlink="http://www.w3.org/1999/xlink">
<symbol id="filters" width="16" height="16">
    <path fill-rule="evenodd" clip-rule="evenodd" d="M1 4.667c0-.392.317-.709.708-.709h6a.708.708 0 1 1 0 1.417h-6A.708.708 0 0 1 1 4.667zM12.334 4.667c0-.392.317-.709.708-.709h1.333a.708.708 0 0 1 0 1.417h-1.333a.708.708 0 0 1-.708-.708z"/>
</symbol>
  <use x="0" y="0" xlink:href="#filters" fill="#3d3e42"/>
  <use x="18" y="0" xlink:href="#filters" fill="#fff"/>
</svg>
.red {
  filter: invert(30%) sepia(93%) saturate(7246%) hue-rotate(356deg)
    brightness(102%) contrast(120%);
}

.blue {
  filter: invert(10%) sepia(99%) saturate(4712%) hue-rotate(243deg)
    brightness(108%) contrast(145%);
}

.green {
  filter: invert(54%) sepia(48%) saturate(1714%) hue-rotate(79deg)
    brightness(113%) contrast(126%);
}

Inline + Lazy Loading + SVG Store

Sprite

CSS-Filters

<svg width="124" height="16" viewBox="0 0 124 16" 
	 xmlns="http://www.w3.org/2000/svg" 
     xmlns:xlink="http://www.w3.org/1999/xlink">
<symbol id="filters" width="16" height="16">
    <path fill-rule="evenodd" clip-rule="evenodd" d="M1 4.667c0-.392.317-.709.708-.709h6a.708.708 0 1 1 0 1.417h-6A.708.708 0 0 1 1 4.667zM12.334 4.667c0-.392.317-.709.708-.709h1.333a.708.708 0 0 1 0 1.417h-1.333a.708.708 0 0 1-.708-.708z"/>
</symbol>
  <use x="0" y="0" xlink:href="#filters" fill="#3d3e42"/>
  <use x="18" y="0" xlink:href="#filters" fill="#fff"/>
</svg>
.red {
  filter: invert(30%) sepia(93%) saturate(7246%) hue-rotate(356deg)
    brightness(102%) contrast(120%);
}

.blue {
  filter: invert(10%) sepia(99%) saturate(4712%) hue-rotate(243deg)
    brightness(108%) contrast(145%);
}

.green {
  filter: invert(54%) sepia(48%) saturate(1714%) hue-rotate(79deg)
    brightness(113%) contrast(126%);
}

Inline + Lazy Loading + SVG Store

Sprite

CSS-Filters

CSS Masks

<svg width="124" height="16" viewBox="0 0 124 16" 
	 xmlns="http://www.w3.org/2000/svg" 
     xmlns:xlink="http://www.w3.org/1999/xlink">
<symbol id="filters" width="16" height="16">
    <path fill-rule="evenodd" clip-rule="evenodd" d="M1 4.667c0-.392.317-.709.708-.709h6a.708.708 0 1 1 0 1.417h-6A.708.708 0 0 1 1 4.667zM12.334 4.667c0-.392.317-.709.708-.709h1.333a.708.708 0 0 1 0 1.417h-1.333a.708.708 0 0 1-.708-.708z"/>
</symbol>
  <use x="0" y="0" xlink:href="#filters" fill="#3d3e42"/>
  <use x="18" y="0" xlink:href="#filters" fill="#fff"/>
</svg>
.red {
  filter: invert(30%) sepia(93%) saturate(7246%) hue-rotate(356deg)
    brightness(102%) contrast(120%);
}

.blue {
  filter: invert(10%) sepia(99%) saturate(4712%) hue-rotate(243deg)
    brightness(108%) contrast(145%);
}

.green {
  filter: invert(54%) sepia(48%) saturate(1714%) hue-rotate(79deg)
    brightness(113%) contrast(126%);
}
.clipped {
  clip-path: polygon(50% 0%, 35% 63%, 82% 100%, 18% 100%, 0% 38%);
}

.masked {
  mask: url(mask.svg);
}

Так как правильно? Какое решение правильно?

Все!

Не всегда решение покрывающее все минусы — подходящее

Экспериментируйте и "вовремя останавливайтесь"

Любое решение можно довести до высокой сложности

Вместо выводов

twitter: @xnimorz

Icon.  Оверинжиниринг или необходимость?

By Nik Mostovoy

Icon.  Оверинжиниринг или необходимость?

  • 770