Как сделать интерактивную SVG-диаграмму
- 27 марта 2020
Сейчас все активно обсуждают радости и гадости удалённой работы, и мы даже выпустили статью на эту тему. А ещё провели небольшой опрос и попросили наших пользователей рассказать, с какими трудностями они сталкиваются во время работы дома (спойлер — все ленятся). Статистику нужно как-то красиво оформить, так почему бы не сделать 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. Легенда
Диаграмма готова, а теперь давайте добавим к ней легенду с описанием секций. Мы не будем изобретать ничего сложного, поэтому пробежимся по основным этапам вскользь:
Добавляем разметку легенды внутрь элемента
.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>
Убираем лишние отступы и маркеры у списка, задаём отступы нужным элементам, указываем максимальную ширину всего блока с легендой, стилизуем текст и добавляем эффект при наведении на название секции:
.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; }
На прошлом этапе мы убрали встроенные маркеры у списка и добавили паддинг слева у его элементов, подготовив место для кастомных маркеров. Теперь создадим их с помощью псевдоэлемента
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;
}
Вот шаги, которые должны быть реализованы в скрипте:
- Найти все названия секций в легенде.
- Найти все секции диаграммы.
- Добавить каждому названию секции отслеживание событий наведения и снятия курсора.
- Внутри обработчиков этих событий добавлять или удалять класс
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.
Полезности
- Подборка графиков на CodePen. Всех видов и разного уровня сложности.
- Как правильно вставлять SVG. В туториале мы инлайнили SVG, но в других ситуациях — другие способы.
- Размеры в SVG. В этой статье можно подробнее почитать о том, как работает
viewBox
, но не только. - Движение мыши: mouseover/out. Подробнее о работе этих событий.
- Курс «Основы SVG». Обо всём, что касается создания и оформления SVG-фигур.
- Курс «Динамические эффекты». Здесь можно изучить плавные переходы и анимацию, которые мы только поверхностно затронули в туториале.
- Курс «Мастер анимаций: CSS и JS-анимации». Научитесь анимировать карточки товаров, модальные окна и другие элементы сайта.
«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.
Читать дальше
HTML-коды популярных эмодзи
Эмодзи давно вышли за пределы мессенджеров и социальных сетей, став важной частью веб-дизайна и пользовательского интерфейса. Они помогают сделать ваш сайт живым, ярким и эмоциональным. Но как добавить эмодзи в HTML-код без потери качества? В этой статье мы собрали для вас список из 50 самых популярных эмодзи с их названиями, значениями и HTML-кодами для вставки.
Эта таблица станет настоящим помощником для начинающих и опытных веб-разработчиков, которые хотят улучшить пользовательский опыт и добавить яркие элементы на свои проекты. Узнайте, как просто интегрировать эмоции в свой сайт с помощью одной строки HTML!
Эмодзи | Название | HTML-код |
---|---|---|
😀 | Улыбающееся лицо | 😀 |
😂 | Лицо со слезами радости | 😂 |
😍 | Лицо с глазами в форме сердца | 😍 |
😎 | Лицо в солнцезащитных очках | 😎 |
😭 | Плачущий | 😭 |
😊 | Смущённая улыбка | 😊 |
😢 | Грустное лицо | 😢 |
😅 | Лицо с каплей пота | 😅 |
🤔 | Задумчивое лицо | 🤔 |
👍 | Большой палец вверх | 👍 |
👎 | Большой палец вниз | 👎 |
👏 | Аплодисменты | 👏 |
🙏 | Сложенные руки | 🙏 |
❤ | Красное сердце | ❤ |
💔 | Разбитое сердце | 💔 |
🔥 | Огонь | 🔥 |
💯 | Сто баллов | 💯 |
🎉 | Конфетти | 🎉 |
🎂 | Торт | 🎂 |
🌟 | Звезда | 🌟 |
✨ | Сияние | ✨ |
🌈 | Радуга | 🌈 |
☀ | Солнце | ☀ |
☁ | Облако | ☁ |
🌧 | Дождь | 🌧 |
❄ | Снег | ❄ |
🌍 | Земной шар | 🌍 |
💡 | Лампочка | 💡 |
💤 | Сон | 💤 |
🎵 | Нота | 🎵 |
🎶 | Музыка | 🎶 |
📱 | Телефон | 📱 |
💻 | Ноутбук | 💻 |
🚗 | Машина | 🚗 |
🚀 | Ракета | 🚀 |
✈ | Самолёт | ✈ |
🚲 | Велосипед | 🚲 |
🐶 | Собака | 🐶 |
🐱 | Кошка | 🐱 |
🐭 | Мышь | 🐭 |
🦊 | Лиса | 🦊 |
🐻 | Медведь | 🐻 |
🍎 | Яблоко | 🍎 |
🍕 | Пицца | 🍕 |
🍔 | Бургер | 🍔 |
🍩 | Пончик | 🍩 |
🍉 | Арбуз | 🍉 |
- 15 ноября 2024
Что писать в атрибуте alt
Альтернативный текст — это описание изображения словами. Это описание должно помогать людям, которые читают или слышат это описание, иначе оно не нужно и лучше вообще его не указывать.
Мы уже обсудили основные правила написания alt-текста для фотографий и изображений. В этот раз поговорим о том, каким именно должно быть описание, чтобы в нём был смысл.
- 27 февраля 2024
Что такое Lazy Loading и как её включить на сайте
Ленивая загрузка (буквально, lazy loading) — это возможность отложить загрузку ненужного прямо сейчас медиаконтента.
<img src="picture.jpg" loading="lazy">
<iframe src="supplementary.html" loading="lazy"></iframe>
Ленивая загрузка делает сайт быстрее и экономит трафик, если у пользователя мобильный интернет с ограничениями.
- 22 ноября 2023
Знакомство с HTML
Привет, будущие фронтенд-разработчики! Добро пожаловать в увлекательный мир веб-разработки. Перед тем как двигаться дальше, давайте разберёмся с основами — языком разметки гипертекста или просто HTML.
HTML (Hypertext Markup Language) — это стандартный язык, который используется для создания веб-страниц. Представьте, что ваша веб-страница — это книга. Тогда HTML — это скелет этой книги, костяк, который определяет структуру: где будет заголовок, абзацы, изображения и так далее.
- 1 ноября 2023
Специальные символы в HTML
HTML использует особенные комбинации символов, чтобы корректно отображать определенные знаки на веб-страницах. Например, вместо простого знака «меньше» мы видим комбинацию <. В этой статье представлен список таких символов. Это небольшой справочник для тех, кто хочет быстро найти нужный код.
- 23 октября 2023
Зачем нужен метатег viewport
Каждый из нас хоть раз в жизни сталкивался с веб-страницами, которые кажутся «сломанными» или странно отображаются на мобильных устройствах. Одной из причин такого поведения может быть отсутствие маленького, но важного элемента в коде страницы — метатега viewport
.
Метатеги — это инструкции для браузера о том, как отображать содержимое страницы. Один из таких метатегов — viewport
, который говорит браузеру, как масштабировать и отображать страницу на разных устройствах, особенно на смартфонах и планшетах.
Важно, чтобы сайты классно выглядели не только на компьютерах, но и на маленьких экранах смартфона. Если не использовать viewport
, то сайт может выглядеть как уменьшенная версия десктопного сайта на мобильном экране, что делает его трудночитаемым и неудобным для использования. Конечно, нужно ещё много всего сделать для адаптивности, но viewport
тоже нужен.
- 18 сентября 2023
Атрибут class в HTML на примерах
Атрибут class
используется для добавления CSS-классов элементам HTML. Классы позволяют применять одни и те же стили CSS или поведение JavaScript к разным элементам на странице.
Так, одному элементу можно присвоить один или несколько классов, разделяя их пробелами.
<!-- Один класс -->
<div class="container">
<!-- ... -->
</div>
<!-- Несколько классов -->
<div class="container special-box">
<!-- ... -->
</div>
- 14 сентября 2023
В чём отличия цитат
В HTML есть разные теги для цитирования и указания источников. Основные из них: <blockquote>
, <cite>
и <q>
. Давайте разберёмся в их различиях.
- 12 сентября 2023
Растровая и векторная графика
Давайте попробуем разобраться, в чём отличие растровой графики от векторной.
- 13 июня 2023
Как отличить h1, h2 и h3
Заголовки используются для организации и структурирования содержимого на сайте. В HTML существует шесть уровней заголовков, обозначаемых тегами от <h1>
до <h6>
. Каждый уровень заголовка имеет свой семантический вес, где <h1>
имеет наибольший вес, а <h6>
— наименьший.
Часто кажется, что заголовок — это простой и понятный тег. Но это впечатление обманчиво, потому что не всегда крупный и выделенный текст на макете является заголовком.
При вёрстке сайта важно соблюдать семантику, чтобы все теги использовались корректно и ресурс работал без ошибок. Если напутать уровни заголовков, то структура страницы будет не семантичной, а скринридеры неправильно прочтут сайт.
В статье разберёмся, как верно определять заголовки по макету, какие из них лучше делать скрытыми и почему.
- 8 июня 2023