Сейчас все активно обсуждают радости и гадости удалённой работы, и мы даже выпустили статью на эту тему. А ещё провели небольшой опрос и попросили наших пользователей рассказать, с какими трудностями они сталкиваются во время работы дома (спойлер — все ленятся). Статистику нужно как-то красиво оформить, так почему бы не сделать SVG-график? Заодно научимся чему-нибудь новому. Поехали!

Вот такую диаграмму получим в итоге
Вот такую диаграмму получим в итоге

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

Именно этим и займёмся — создадим SVG-диаграмму и легенду к ней, а затем стилизуем всё с помощью CSS и в самом конце добавим немного JavaScript-магии. Вам понадобятся базовые знания HTML, CSS и JavaScript. Если их пока нет — самое время пройти бесплатные тренажёры, а после этого вернуться к туториалу.

Создаём диаграмму

1. Подготовка

Начнём с родительского блока. График и легенда будут лежать внутри элемента .canvas, который мы сделаем флекс-контейнером, чтобы выровнять дочерние элементы внутри него.

<div class="canvas"></div>
.canvas {
  display: flex;
  justify-content: space-between;
  align-items: center;
  max-width: 800px;
}

2. Добавляем SVG

Есть разные методы вставки SVG-элементов, самый подходящий для наших целей — вставить код элемента непосредственно в HTML, то есть заинлайнить. Это способ позволяет изменять свойства SVG-элементов и фигур прямо из CSS. Добавим разметку диаграммы:

<div class="canvas">
  <svg class="chart" width="500" height="500" viewBox="0 0 50 50">
    <circle class="unit" r="15.9" cx="50%" cy="50%"></circle>
    <circle class="unit" r="15.9" cx="50%" cy="50%"></circle>
    <circle class="unit" r="15.9" cx="50%" cy="50%"></circle>
    <circle class="unit" r="15.9" cx="50%" cy="50%"></circle>
    <circle class="unit" r="15.9" cx="50%" cy="50%"></circle>
    <circle class="unit" r="15.9" cx="50%" cy="50%"></circle>
    <circle class="unit" r="15.9" cx="50%" cy="50%"></circle>
  </svg>
</div>

Мы использовали тег svg — специальный тег для создания SVG-элемента, задали элементу размеры с помощью width и height, а ещё добавили атрибут viewBox. Для чего он нужен? Вообще содержимое SVG-элемента отрисовывается на бесконечном холсте, и изменение размеров элемента не влияет на его содержимое. С помощью viewBox как раз можно задать область отрисовки, которая будет растягиваться и сжиматься в зависимости от размеров. В нашем случае первые два нуля в значении — это координаты верхнего левого угла области (начало отсчёта системы координат), а следующие два числа — ширина и высота соответственно.

Внутри SVG-элемента мы нарисовали несколько одинаковых окружностей, задав радиус и центр окружности с помощью тега circle и его атрибутов, где: r — радиус окружности, cx— координата центра окружности по горизонтальной оси, а cy — по вертикальной. По умолчанию центр окружности расположен в начале координат — левом верхнем углу, поэтому мы задаём значение 50%, чтобы окружность находилась по центру.

Количество окружностей равно количеству секций в диаграмме. У нас их будет семь.

3. Создаём секции

На прошлом шаге мы получили обычные окружности, но хотелось бы превратить их в диаграмму в виде пончика, то есть нужно как-то вырезать середину. Мы реализуем это с помощью добавления обводки окружности. Оставим только её и уберём заливку самой фигуры — fill.

.unit {
  fill: none;
  stroke-width: 10;
}

.unit:nth-child(1) {
  stroke: #86cfa3;
}

.unit:nth-child(2) {
  stroke: #a2c6e0;
}

.unit:nth-child(3) {
  stroke: #ffc7ec;
}

/* Далее ещё четыре секции по аналогии */

Свойство stroke-width задаёт толщину обводки — она у всех секций одинаковая. А цвет разный, поэтому свойство stroke, которое за него отвечает, мы прописали отдельно для каждой окружности. Кстати, мы не указываем единицы измерения у свойств обводки, потому что по умолчанию это пиксели, и нам нужны именно они.

На данном этапе каждая секция занимает всю окружность, нам нужно изменить это поведение. Для этого используем свойство stroke-dasharray. Оно предназначено для создания пунктирных обводок и в качестве значения принимает длину отрезков и пробелов между ними. В нашем случае мы используем свойство не совсем традиционным способом: отрезок будет только один — сама секция, а вся остальная окружность — это пробел.

Ещё учтём, что по умолчанию все секции будут начинаться из одной точки, а нам нужно расположить их одну за другой по кругу. Чтобы добиться нужного поведения, используем свойство stroke-dashoffset — оно позволит сдвинуть обводку на нужное нам расстояние.

.unit:nth-child(1) {
  stroke: #86cfa3;
  stroke-dasharray: 8 100;
}

.unit:nth-child(2) {
  stroke: #a2c6e0;
  stroke-dasharray: 11 100;
  stroke-dashoffset: -8;
}

.unit:nth-child(3) {
  stroke: #ffc7ec;
  stroke-dasharray: 11 100;
  stroke-dashoffset: -19;
}

/* Далее ещё четыре секции по аналогии */

Давайте подробнее остановимся на значении свойства stroke-dashoffset. У первой секции этого свойства нет, потому что её не нужно никуда сдвигать, а у остальных сдвиг равен длине всех предшествующих секций. То есть у второго элемента сдвиг равен длине первой секции, у третьего — длине первой и второй секции и так далее. Значение отрицательное, потому что сдвигаем по часовой стрелке, а по умолчанию сдвиг идёт против.

4. Немного математики

Можно пропустить, если вы не любите математику.

Возможно, вы заметили, что на втором шаге мы задали окружностям довольно странный радиус — 15.9, и теперь пришло время рассказать, почему значение именно такое.

В графиках используются данные в процентах, то есть каждая секция диаграммы иллюстрирует какой-то процент от общего количества. В нашем случае длина секции, которая была прописана первым значением у свойства stroke-dasharray, должна отражать этот процент, то есть 100% соответствует длине всей окружности, а n% — длине секции. В таком случае, чтобы найти длину окружности, нам нужно вспомнить школьную формулу C = 2πr. Вот здесь мы схитрим и изначально договоримся, что длина окружности будет равна 100px, и тогда путём нехитрых вычислений выведем из формулы радиус, равный примерно 15.9px. Вот откуда появилось значение.

Для чего это было нужно? Допустим, нас попросили построить диаграмму по некоторым статистическим данным: 1% котов рыжие (и любят командовать), а 99% котов — других цветов. Благодаря тому, что мы приравняли длину всей окружности к 100, длина секций будет равна проценту из статистики — 1 и 99 соответственно. Это очень удобно. Если бы мы пошли альтернативным путём и не стали отталкиваться от длины окружности, то для каждого процента приходилось бы вычислять длину секции из пропорции, и она была бы дробным числом с кучей знаков после запятой.

Здесь стоит отметить, что нам помогает провернуть этот хак атрибут viewBox, который был задан в самом начале SVG-элементу. Масштабируя область видимости, мы может управлять размером диаграммы. При этом радиус окружности изменять не будем — мы зафиксировали его значение по причинам, изложенным выше.

Вот откуда взялась длина секции в свойстве stroke-dasharray — это процент, который занимает каждая секция диаграммы согласно данным статистики.

5. Легенда

Диаграмма готова, а теперь давайте добавим к ней легенду с описанием секций. Мы не будем изобретать ничего сложного, поэтому пробежимся по основным этапам вскользь:

  1. Добавляем разметку легенды внутрь элемента .canvas:

    <div class="legend">
      <p class="title">Что мешает во время работы дома?</p>
      <ul class="caption-list">
        <li class="caption-item">еда</li>
        <li class="caption-item">соседи</li>
        <li class="caption-item">отсутствие рабочего места</li>
        <li class="caption-item">мало двигаюсь</li>
        <li class="caption-item">отсутствие чёткого графика работы</li>
        <li class="caption-item">неудобная коммуникация с коллегами</li>
        <li class="caption-item">много ленюсь</li>
      </ul>
    </div>
    
  2. Убираем лишние отступы и маркеры у списка, задаём отступы нужным элементам, указываем максимальную ширину всего блока с легендой, стилизуем текст и добавляем эффект при наведении на название секции:

    .legend {
      max-width: 250px;
      margin-left: 30px;
    }
    
    .title {
      font-family: "Verdana", sans-serif;
      font-size: 18px;
      line-height: 21px;
      color: #591d48;
    }
    
    .caption-list {
      margin: 0;
      padding: 0;
      list-style: none;
    }
    
    .caption-item {
      position: relative;
    
      margin: 20px 0;
      padding-left: 30px;
    
      font-family: "Verdana", sans-serif;
      font-size: 16px;
      line-height: 18px;
      color: #591d48;
      cursor: pointer;
    }
    
    .caption-item:hover {
      opacity: 0.8;
    }
    
  3. На прошлом этапе мы убрали встроенные маркеры у списка и добавили паддинг слева у его элементов, подготовив место для кастомных маркеров. Теперь создадим их с помощью псевдоэлемента beforeи добавим такие же цвета, как у секций диаграммы:

    .caption-item::before {
      content: ";
    
      position: absolute;
      top: 0;
      left: 0;
    
      width: 20px;
      height: 20px;
    
      border-radius: 8px;
    }
    
    .caption-item:nth-child(1)::before {
      background-color: #86cfa3;
    }
    
    .caption-item:nth-child(2)::before {
      background-color: #a2c6e0;
    }
    
    .caption-item:nth-child(3)::before {
      background-color: #ffc7ec;
    }
    
    /* Далее ещё четыре секции по аналогии */
    

    Готово! Теперь у нас есть диаграмма и легенда к ней.

Добавляем немного интерактивности

Всё хорошо, но не помешало бы сделать диаграмму более живой. Давайте добавим небольшую анимацию во время отрисовки и эффекты при наведении.

1. Анимация

В предыдущем туториале мы уже разбирали, как сделать простую анимацию. Если вы никогда не делали анимации на CSS, будет полезно для начала прочитать его.

Давайте добавим анимацию во время отрисовки секций диаграммы. Для этого будем постепенно изменять свойство stroke-dasharray, начиная с нуля и заканчивая теми значениями, которые мы указали ранее — для каждой секции оно своё. То есть длина секции будет постепенно возрастать от нуля до нужного значения.

Для создания анимации используем ключевое слово @keyframes, зададим название анимации и укажем состояние в начальной точке — 0%:

@keyframes render {
  0% {
    stroke-dasharray: 0 100;
  }
}

Мы намеренно не указали состояние в конечной точки анимации — 100%, так как у каждой секции своя длина. В данном случае после выполнения анимации свойство stroke-dasharray примет то значение, которое мы уже прописали ранее для каждой секции. Так работает анимация — по окончании элемент возвращается в исходное состояние.

Теперь добавим к уже имеющимся свойствам элемента .unit новые — animation-name и animation-duration. Они позволят задать название и продолжительность анимации и применить её к секциям диаграммы.

.unit {
  /* Здесь должны быть свойства, заданные ранее */

  animation-name: render;
  animation-duration: 1.5s;
}

Всё работает — секции постепенно появляются в течение заданного времени.

2. Эффект при наведении

Добавим плавное увеличение секции при наведении на неё. Используем псевдокласс hover, чтобы увеличить толщину обводки наших окружностей-секций и добавить им полупрозрачность.

.unit:hover {
  opacity: 0.8;
  stroke-width: 12;
}

Сейчас переход между состояниями происходит резко, но это поведение можно изменить с помощью плавных переходов. Укажем свойства, которые нужно изменять плавно (в нашем случае все), и длительность перехода — за это отвечают transition-property и transition-duration.

.unit {
  /* Здесь должны быть свойства, заданные ранее */

  transition-property: all;
  transition-duration: 1.5s;
}

Теперь переход между состояниями происходит плавно.

3. Немного JavaScript

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

Мы напишем небольшой скрипт, который позволит выделять нужную секцию при наведении на её название в легенде. Эффекты будем использовать точно такие же, как при наведении на саму секцию. Мы уже прописали их для .unit:hover, поэтому теперь просто продублируем для специального класса hovered, который далее будем использовать в JavaScript-коде.

.hovered {
  opacity: 0.8;
  stroke-width: 12;
}

Вот шаги, которые должны быть реализованы в скрипте:

  1. Найти все названия секций в легенде.
  2. Найти все секции диаграммы.
  3. Добавить каждому названию секции отслеживание событий наведения и снятия курсора.
  4. Внутри обработчиков этих событий добавлять или удалять класс hovered у секций диаграммы в зависимости от положения курсора.
let captionsList = document.querySelectorAll('.caption-item');
let unitsList = document.querySelectorAll('.unit');

captionsList.forEach(function (item, index) {
  item.addEventListener('mouseover', function () {
     unitsList[index].classList.add('hovered');
  });

  item.addEventListener('mouseout', function () {
     unitsList[index].classList.remove('hovered');
  });
});

Здесь мы отслеживаем движение мыши с помощью событий mouseover (курсор над элементом) и mouseout (курсор уходит с элемента). А для поиска нужной секции используем совпадение порядковых номеров названий в легенде и секций диаграммы — index названия = index секции. Нужно учесть, что их всегда должно быть равное количество.

Вот теперь диаграмма готова! Весь код, который мы написали в туториале, доступен на CodePen.

See the Pen svg pie chart by sasha_htmlacademy (@sasha-sm)on CodePen.

Полезности


«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.

ТелеграмПодкастБесплатные учебники

Читать дальше

Что писать в атрибуте alt

Что писать в атрибуте alt

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

Мы уже обсудили основные правила написания alt-текста для фотографий и изображений. В этот раз поговорим о том, каким именно должно быть описание, чтобы в нём был смысл.

Читать дальше
HTML
  • 27 февраля 2024
Что такое Lazy Loading и как её включить на сайте

Что такое Lazy Loading и как её включить на сайте

Ленивая загрузка (буквально, lazy loading) — это возможность отложить загрузку ненужного прямо сейчас медиаконтента.

<img src="picture.jpg" loading="lazy">

<iframe src="supplementary.html" loading="lazy"></iframe>

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

Читать дальше
HTML
  • 22 ноября 2023
Знакомство с HTML

Знакомство с HTML

Привет, будущие фронтенд-разработчики! Добро пожаловать в увлекательный мир веб-разработки. Перед тем как двигаться дальше, давайте разберёмся с основами — языком разметки гипертекста или просто HTML.

HTML (Hypertext Markup Language) — это стандартный язык, который используется для создания веб-страниц. Представьте, что ваша веб-страница — это книга. Тогда HTML — это скелет этой книги, костяк, который определяет структуру: где будет заголовок, абзацы, изображения и так далее.

Читать дальше
HTML
  • 1 ноября 2023
Специальные символы в HTML

Специальные символы в HTML

HTML использует особенные комбинации символов, чтобы корректно отображать определенные знаки на веб-страницах. Например, вместо простого знака «меньше» мы видим комбинацию <. В этой статье представлен список таких символов. Это небольшой справочник для тех, кто хочет быстро найти нужный код.

Читать дальше
HTML
  • 23 октября 2023
Зачем нужен метатег viewport

Зачем нужен метатег viewport

Каждый из нас хоть раз в жизни сталкивался с веб-страницами, которые кажутся «сломанными» или странно отображаются на мобильных устройствах. Одной из причин такого поведения может быть отсутствие маленького, но важного элемента в коде страницы — метатега viewport.

Метатеги — это инструкции для браузера о том, как отображать содержимое страницы. Один из таких метатегов — viewport, который говорит браузеру, как масштабировать и отображать страницу на разных устройствах, особенно на смартфонах и планшетах.

Важно, чтобы сайты классно выглядели не только на компьютерах, но и на маленьких экранах смартфона. Если не использовать viewport, то сайт может выглядеть как уменьшенная версия десктопного сайта на мобильном экране, что делает его трудночитаемым и неудобным для использования. Конечно, нужно ещё много всего сделать для адаптивности, но viewport тоже нужен.

Читать дальше
HTML
  • 18 сентября 2023
Атрибут class в HTML на примерах

Атрибут class в HTML на примерах

Атрибут class используется для добавления CSS-классов элементам HTML. Классы позволяют применять одни и те же стили CSS или поведение JavaScript к разным элементам на странице.

Так, одному элементу можно присвоить один или несколько классов, разделяя их пробелами.

<!-- Один класс -->
<div class="container">
  <!-- ... -->
</div>

<!-- Несколько классов -->
<div class="container special-box">
  <!-- ... -->
</div>
Читать дальше
HTML
  • 14 сентября 2023
Как отличить h1, h2 и h3

Как отличить h1, h2 и h3

Заголовки используются для организации и структурирования содержимого на сайте. В HTML существует шесть уровней заголовков, обозначаемых тегами от <h1> до <h6>. Каждый уровень заголовка имеет свой семантический вес, где <h1> имеет наибольший вес, а <h6> — наименьший.

Часто кажется, что заголовок — это простой и понятный тег. Но это впечатление обманчиво, потому что не всегда крупный и выделенный текст на макете является заголовком.

При вёрстке сайта важно соблюдать семантику, чтобы все теги использовались корректно и ресурс работал без ошибок. Если напутать уровни заголовков, то структура страницы будет не семантичной, а скринридеры неправильно прочтут сайт.

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

Читать дальше
HTML
  • 8 июня 2023
Как правильно вставлять SVG

Как правильно вставлять SVG

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

SVG — это формат векторной графики, дословно: масштабируемая векторная графика. МВГ? SVG! В векторных форматах хранится не само изображение, а инструкция по его построению по точкам и кривым.

В растровых форматах информация о конкретном числе точек изображения плотно упакована в бинарный кирпич. В него бесполезно заглядывать и менять его можно только в редакторах графики.

�PNG
IH�aV
PLTE�������0�
IDAcZ�d���� �W=
S�3�o;���]P
���IEND�B`�~

Формат SVG тоже можно создавать и менять в редакторах графики, например, в Illustrator или Figma. Но ещё он текстовый, а значит его можно открыть как HTML или CSS в любом редакторе кода.

<svg width="20">
  <rect fill="#fc0"
    width="20"
    height="20"/>
  <line stroke="black"
    x1="0" y1="0"
    x2="20" y2="20"/>
</svg>

SVG — это как отдельная HTML-страница. Когда вставляете SVG, вы, на самом деле, вставляете не просто картинку, а целую страницу. Со своей системой координат, вьюпортом, стилями, скриптами и удивительными особенностями.

Если смотреть на SVG как на отдельную страницу — становится понятнее, какой способ вставки вам нужен.

Читать дальше
HTML
  • 1 июня 2023