От автора: в этом уроке мы рассмотрим, как анимировать иконку svg иконку меню при помощи Dribbble шота от Tamas Kojo, в котором используются SVG и Segment – JavaScript библиотека для рисования и анимирования элемента SVG path.
Сегодня мы рады представить вам очень интересный эффект с иконкой меню. Идея возникла из Dribbble шота hamburger menu от Tamas Kojo. На первый взгляд это обычная иконка-гамбургер для меню мобильных устройств. Но при клике на нее, иконка превращается в крест, при этом процесс сопровождается довольно забавной анимацией. При клике на крестик, анимация прокручивается в обратном порядке, и иконка снова превращается в гамбургер. Давайте посмотрим:
При помощи SVG и библиотеки Segment мы воссоздадим этот эффект. Сперва, мы распланируем нашу анимацию, познакомимся с библиотекой, а затем нарисуем анимацию для иконки.
Планирование
Для воссоздания данного эффекта я не вижу ничего лучше, чем SVG. А JS библиотека Segment (альтернатива DrawSVGPlugin от GSAP) в этом нам поможет.
Необходимо создать три элемента path, которые будут описывать анимацию каждой прямой иконки гамбургера. С помощью Segment можно как угодно анимировать svg path элементы. Для прорисовки пути анимации каждой прямой можно воспользоваться любым редактором векторной графики (Adobe Illustrator или Inkscape); для достижения большей точности мы будем рисовать данные пути самостоятельно (связующие линии, кривые и дуги). Помните, что наша анимация «эластична», т.е. нужно учесть длину пути каждой прямой. Но прежде, давайте познакомимся с бибилиотекой Segment.
Знакомство с Segment
Данная библиотека – основной инструмент, который мы будем использовать, а точнее, небольшой JS класс (без зависимостей) для рисования и анимирования SVG путей. Пользоваться библиотекой достаточно просто:
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 32 |
<!-- добавьте segment скрипт(меньше 2kb) --> <script src="/dist/segment.min.js"></script> <!—задаем path --> <svg> <path id="my-path" ...> </svg> <script> // инициализируем новый класс segment с path var myPath = document.getElementById("my-path"), segment = new Segment(myPath); // рисуем путь в любое время // Syntax: .draw(begin, end[, duration, options]) segment.draw("25%", "75% - 10", 1); /* пример со всеми возможными вариантами */ // задаем функцию смягчения (t параметр в промежутке [0, 1]) function cubicIn(t) { return t * t * t; } // задаем колбек функцию function done() { alert("Done!"); } // рисуем путь segment.draw(0, "100%", 1, {delay: 1, easing: cubicIn, callback: done}); </script> |
Чтобы более подробно разобраться в принципе работы, поиграйтесь с demo и ознакомьтесь с документацией на GitHub. Также можно прочесть данную статью. Важно отметить, что в библиотеке Segment нет своих функций смягчения (кроме линейной по умолчанию). Дабы восполнить недостачу, мы задействуем библиотеку d3-ease.
Прорисовка путей
Анимация довольно быстрая, но если рассмотреть ее покадрово, можно нарисовать путь каждой прямой. В результате получим что-то похожее на это:
Код ниже рисует картинку выше шаг за шагом:
1 2 3 4 5 |
<svg width="100px" height="100px"> <path d="M 30 40 L 70 40 C 90 40 90 75 60 85 A 40 40 0 0 1 20 20 L 80 80"></path> <path d="M 30 50 L 70 50"></path> <path d="M 70 60 L 30 60 C 10 60 10 20 40 15 A 40 38 0 1 1 20 80 L 80 20"></path> </svg> |
Для достижения желаемого эффекта, необходимо добавить CSS стили и идентификаторы к элементам path. ID нам пригодится для получения доступа к path через скрипт. Далее будем использовать следующую HTML разметку:
1 2 3 4 5 6 7 8 9 10 11 |
<!-- Wrapper --> <div id="menu-icon-wrapper" class="menu-icon-wrapper"> <!-- SVG с элементами path --> <svg width="100px" height="100px"> <path id="pathA" d="M 30 40 L 70 40 C 90 40 90 75 60 85 A 40 40 0 0 1 20 20 L 80 80"/> <path id="pathB" d="M 30 50 L 70 50"/> <path id="pathC" d="M 70 60 L 30 60 C 10 60 10 20 40 15 A 40 38 0 1 1 20 80 L 80 20"/> </svg> <!-- триггер анимации --> <button id="menu-icon-trigger" class="menu-icon-trigger"></button> </div> |
И CSS стили:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
// обертка задана с фиксированными шириной и высотой // Заметьте, что свойство pointer-events установлено в 'none'. // нам не нужно, чтобы курсор изменялся на элементе анимации. .menu-icon-wrapper{ position: relative; display: inline-block; width: 34px; height: 34px; pointer-events: none; transition: 0.1s; } // выполняем трансформацию второго демо .menu-icon-wrapper.scaled{ transform: scale(0.5); } // задаем позицию SVG элемента .menu-icon-wrapper svg{ position: absolute; top: -33px; left: -33px; } // задаем стили для path .menu-icon-wrapper svg path{ stroke: #fff; stroke-width: 6px; stroke-linecap: round; fill: transparent; } // устанавливаем pointer-events на 'auto', // позволяя запускать анимацию только по одному событию .menu-icon-wrapper .menu-icon-trigger{ position: relative; width: 100%; height: 100%; cursor: pointer; pointer-events: auto; background: none; border: none; margin: 0; padding: 0; } |
Анимация
SVG код готов. Теперь наша задача, для каждой секции анимации попытаться подобрать правильную функцию смягчения и добиться соответствующей синхронизации с GIF анимацией. Займемся анимацией верхней и нижней планкой иконки гамбургера. Для каждой планки необходимо задать сегмент со значениями begin и end. Так как у нас под рукой только GIF анимация, то процесс этот будет долгим и не обойдется без ошибок при подборе правильных значений.
1 2 3 4 |
var pathA = document.getElementById('pathA'), pathC = document.getElementById('pathC'), segmentA = new Segment(pathA, 8, 32), segmentC = new Segment(pathC, 8, 32); |
После этого можно приступить к анимации, сохраняя длину планки (end — begin = 24) в процессе анимирования. Проанализировав последовательность анимации, можно понять, что первая функция смягчения линейная, заканчивающаяся гибким поворотом. Мы будем использовать функции, в качестве параметра в которые передается сегмент. Для верхней и нижней планки гамбургера функция будет одна и та же, так как анимация одинаковая, только направлена в другую сторону.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// линейная секция с колбеком к следующей секции function inAC(s) { s.draw('80% - 24', '80%', 0.3, {delay: 0.1, callback: function(){ inAC2(s) }}); } // Гибкая секция, применяется функция elastic-out function inAC2(s) { s.draw('100% - 54.5', '100% - 30.5', 0.6, {easing: ease.ease('elastic-out', 1, 0.3)}); } // запуск анимации inAC(segmentA); // верхняя планка inAC(segmentC); // нижняя планка То же самое необходимо повторить и для средней планки: // инициализация var pathB = document.getElementById('pathB'), segmentB = new Segment(pathB, 8, 32); // немного расширяем планку function inB(s) { s.draw(8 - 6, 32 + 6, 0.1, {callback: function(){ inB2(s) }}); } // обратно уменьшаем с уаругим эффектом function inB2(s) { s.draw(8 + 12, 32 - 12, 0.3, {easing: ease.ease('bounce-out', 1, 0.3)}); } // запуск анимации inB(segmentB); |
Для обратного эффекта, превращения крестика в гамбургер:
1 2 3 4 5 6 7 8 9 10 |
function outAC(s) { s.draw('90% - 24', '90%', 0.1, {easing: ease.ease('elastic-in', 1, 0.3), callback: function(){ outAC2(s) }}); } function outAC2(s) { s.draw('20% - 24', '20%', 0.3, {callback: function(){ outAC3(s) }}); } function outAC3(s) { s.draw(8, 32, 0.7, {easing: ease.ease('elastic-out', 1, 0.3)}); } function outB(s) { s.draw(8, 32, 0.7, {delay: 0.1, easing: ease.ease('elastic-out', 2, 0.4)}); } // запуск анимации outAC(segmentA); outB(segmentB); outAC(segmentC); |
Теперь, чтобы соответствующая анимация запускалась по клику, необходимо написать следующее:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var trigger = document.getElementById('menu-icon-trigger'), toCloseIcon = true; trigger.onclick = function() { if (toCloseIcon) { inAC(segmentA); inB(segmentB); inAC(segmentC); } else { outAC(segmentA); outB(segmentB); outAC(segmentC); } toCloseIcon = !toCloseIcon; }; |
Анимация закончена, но есть небольшая проблема. В разных браузерах, она выглядит немного по-разному. Длина элементов path вычисляется в браузерах по-разному, а значит, есть и небольшое различие (существенное). Особенно это заметно в Firefox и Chrome.
Решение данной проблемы достаточно простое. Мы увеличим наш SVG, тем самым увеличив размер элементов path, а потом уменьшим svg под необходимый размер. В нашем случае мы изменили размер SVG в 10 раз, в итоге получили следующий код:
1 2 3 4 5 |
<svg width="1000px" height="1000px"> <path id="pathA" d="M 300 400 L 700 400 C 900 400 900 750 600 850 A 400 400 0 0 1 200 200 L 800 800"></path> <path id="pathB" d="M 300 500 L 700 500"></path> <path id="pathC" d="M 700 600 L 300 600 C 100 600 100 200 400 150 A 400 380 0 1 1 200 800 L 800 200"></path> </svg> |
А затем уменьшили размер с помощью CSS:
1 2 3 4 |
.menu-icon-wrapper svg { transform: scale(0.1); transform-origin: 0 0; } |
Обратите внимание на то, что необходимо также увеличить значения float в JS (умножить на 10), а также изменить значение stroke-width в CSS. Данный метод поможет снизить до минимума различия в браузерах, но если вас не смущают небольшие данные мелочи, то можно придерживаться первоначального варианта.
Сегодня мы узнали, как создавать эластичную анимацию с помощью Segment. Это только один из возможных способов. Теперь ваш черед, создайте необычную SVG анимацию 🙂
Надеемся, данный урок был полезен для вас!
Автор: Luis Manuel
Источник: //tympanus.net/
Редакция: Команда webformyself.