Как оптимизировать код SVG и анимировать значок SVG с помощью библиотеки CSS и Snap.svg.
Работа с файлами SVG больше не вариант. Имея огромное количество устройств высокой четкости, невозможно экспортировать разные размеры одних и тех же растровых ресурсов и нацеливать определенные разрешения устройства с помощью медиа-запросов CSS. Нам нужно полагаться на векторную графику, когда это возможно.
Сегодняшний ресурс - это очень простой значок, который мы импортировали как встроенный SVG в наш файл index.html
. После оптимизации кода svg мы попытались продвинуть его еще на один шаг, анимировав значок с помощью CSS и Snap.svg, который представляет собой библиотеку SVG javascript, созданную командой Adobe. Честно говоря, не так-то просто анимировать svg-файлы с текущими браузерами, поддерживающими статус-кво, но мы научились некоторым трюкам, которыми мы рады поделиться с вами!
Если вы новичок в SVG, вот несколько отличных ресурсов для начала:
- Независимость разрешения от SVG Дэвида Бушелла
- Использование SVG Криса Койера
- Стилизация и анимация масштабируемой векторной графики с помощью CSS (видео) от Sara Soueidan
Самый простой способ создать иллюстрацию SVG - использовать графические редакторы, такие как Adobe Illustrator или Sketch. Выберите тот, который вам больше нравится, и создайте значок (или загрузите наш исходный файл). Большая часть того, что вы создаете в этих приложениях, поддерживает SVG, а это означает, что создаваемая вами графика может быть переведена в код. Самый простой способ понять это - сохранить значок как файл .svg
, а затем открыть его в редакторе кода.
Однако имейте в виду, что способ организации слоев в графическом редакторе повлияет на вывод кода.
В качестве примера: поскольку наш последний значок анимирован в 2 этапа, я создал в Illustrator 2 отдельных слоя: один для эффекта загрузки, а второй - для зданий. Поскольку видимость слоя cd-Building
отключена, я могу ожидать, что отображение: none
применяется к этому элементу в выходном коде. Кроме того, слои и группы в Illustrator считаются группами в экспортируемом коде SVG (что хорошо). Заголовки слоев в AI будут назначены как значения #id
в коде SVG.
При экспорте .svg
из AI настройки по умолчанию работают нормально. Я просто убеждаюсь, что для свойств CSS установлено значение «Элементы стиля», чтобы отделить стиль от содержимого - автоматически создает классы.
Некоторые моменты, которые следует учитывать при создании SVG в графическом редакторе:
- То, как вы организуете / группируете слои, повлияет на выходной код.
- Дайте слоям имя.
- Попробуйте использовать простые формы и удалите ненужные точки привязки (в AI попробуйте объект > Путь > Упростить).
- Старайтесь не векторизовать штрихи, вы можете управлять ими позже в своем коде, плюс код станет чище.
- Не все, что вы делаете в графическом редакторе, совместимо с SVG. У AI есть специальные фильтры SVG, но браузеры не поддерживают большинство из них.
Оптимизация кода SVG
Лично я не использую инструменты для автоматической очистки SVG-кода. Поскольку я всегда начинаю с графического редактора, я обнаружил, что лучший способ экспортировать чистый код - это тщательно продумывать дизайн и следовать советам, которые я предлагал ранее.
После того, как файл .svg
был экспортирован, давайте импортируем его в редактор кода и посмотрим на код:
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="200px" height="200px" viewBox="0 0 200 200" style="enable-background:new 0 0 200 200;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#D7DCE0;stroke-width:4;stroke-miterlimit:10;}
.st1{fill:none;stroke:#A84D54;stroke-width:4;stroke-miterlimit:10;}
.st2{opacity:0;fill:#FFFFFF;}
.st3{fill:#223443;}
.st4{fill:#FFFFFF;stroke:#223443;stroke-width:4;stroke-miterlimit:10;}
.st5{fill:none;stroke:#223443;stroke-width:4;stroke-miterlimit:10;}
</style>
<g id="cd-loading">
<g id="cd-circle">
<circle id="cd-loading-circle" class="st0" cx="100" cy="100" r="77.5"/>
<circle id="cd-loading-circle-filled" class="st1" cx="100" cy="100" r="77.5"/>
</g>
<g id="cd-play-btn">
<rect x="84" y="78.1" class="st2" width="32" height="43.9"/>
<polygon class="st3" points="84,78.1 116,100 84,121.9 "/>
</g>
<g id="cd-pause-btn">
<rect x="81" y="80.1" class="st2" width="38" height="39.9"/>
<rect x="81" y="80.1" class="st3" width="11" height="39.9"/>
<rect x="108" y="80.1" class="st3" width="11" height="39.9"/>
</g>
</g>
<g id="cd-buildings">
<g id="cd-home-3">
<rect id="cd-home-3-base" x="66" y="55" class="st4" width="58" height="123"/>
<polygon id="cd-home-3-roof" class="st4" points="59.4,56 95,15 130.6,56 "/>
<circle id="cd-home-3-window" class="st4" cx="95" cy="77" r="11"/>
</g>
<g id="cd-home-2">
<rect id="cd-home-2-base" x="98" y="132" class="st4" width="69" height="46"/>
<rect id="cd-home-2-roof" x="98" y="122" class="st4" width="69" height="10"/>
<rect id="cd-home-2-door" x="141" y="155" class="st4" width="15" height="22"/>
<rect id="cd-home-2-window" x="106" y="143" class="st4" width="25" height="9"/>
</g>
<g id="cd-home-1">
<rect id="cd-home-1-base" x="29" y="109" class="st4" width="69" height="69"/>
<rect id="cd-home-1-roof" x="23" y="100" class="st4" width="80" height="9"/>
<rect id="cd-home-1-door" x="53" y="151" class="st4" width="20" height="27"/>
<rect id="cd-home-1-window" x="53" y="121" class="st4" width="20" height="18"/>
<rect id="cd-home-1-chimney" x="34" y="89" class="st4" width="11" height="11"/>
</g>
<line id="cd-floor" class="st5" x1="2" y1="178" x2="198" y2="178"/>
<g id="cd-cloud-1">
<line class="st0" x1="5" y1="13" x2="29" y2="13"/>
<line class="st0" x1="35" y1="13" x2="44" y2="13"/>
<line class="st0" x1="72" y1="27" x2="14" y2="27"/>
</g>
<g id="cd-cloud-2">
<line class="st0" x1="191" y1="68" x2="159" y2="68"/>
<line class="st0" x1="153" y1="68" x2="144" y2="68"/>
<line class="st0" x1="179" y1="83" x2="133" y2="83"/>
</g>
</g>
</svg>
Как видите, Illustrator создал для нас несколько классов. Кроме того, структура уже довольно чистая. Дав имена группам и слоям в AI, теперь мы можем легко найти их в структуре кодирования SVG.
Прежде всего, поскольку мы хотим включить этот рисунок как встроенный SVG, мы можем взять только код, заключенный в теги <svg> </svg>
, и вставить его в файл .html
.
Я работаю с документом HTML5, поэтому могу удалить объявление пространства имен и добавить заголовок и описание, чтобы улучшить доступность файла.
<body>
<svg version="1.1" x="0px" y="0px" width="200px" height="200px" viewBox="0 0 200 200" style="enable-background:new 0 0 200 200;" xml:space="preserve">
<title>Buildings</title>
<desc>3 minimal buildings with floating clouds</desc>
<style type="text/css">
<!-- ... -->
</style>
<!-- SVG elements here -->
</svg>
</body>
Следующим шагом будет создание большего количества семантических классов в CSS, чтобы заменить те, которые были созданы в Illustrator. Значок довольно минимален, поэтому в CSS будет всего несколько классов:
/* --------------------------------
Manage colors
-------------------------------- */
.cd-stroke {
fill: none;
stroke-width: 4;
stroke-miterlimit: 10;
}
.cd-stroke-color-1 {
stroke: #223443;
fill: #f8f8f8;
}
.cd-stroke-color-1#floor {
fill: none;
}
.cd-stroke-color-2 {
stroke: #D7DCE0;
}
.cd-stroke-color-3 {
stroke: #A84D54;
}
.cd-fill-color-1 {
fill: #223443;
}
.cd-pointer {
fill: #FFFFFF;
opacity: 0;
}
Да, мы можем изменять атрибуты представления SVG внутри нашего файла CSS! Теперь мы можем удалить стиль из кода SVG и назначить классы соответствующим элементам.
Кроме того, я внес некоторые изменения в структуру: я переместил некоторые элементы вниз в структуре SVG, потому что чем ниже элемент в структуре, тем выше его z-index
. Наконец, я также изменил некоторые координаты - очень мелкие детали, например, перемещение элементов на пару пикселей вокруг, если они не были идеально выровнены. Знаешь, дизайнеры любят тратить время на ненужные вещи.
SVG почти готов к анимации. Я заключил элемент <svg>
в контейнер (.cd-svg-container
), чтобы выровнять значок по центру. Установка max-width: 100%
сделает SVG отзывчивым (как изображения).
Также вы можете заметить overflow: hidden
применительно к SVG: это потому, что мы будем анимировать облака внутри и снаружи холста, и по некоторым неизвестным причинам IE показывает их, даже когда они отсутствуют, если мы не применим свойство переполнения. Также вы можете заметить прямоугольник (.cd-pointer
) внутри элемента #cd-pause-btn
. Он используется для того, чтобы сделать область между двумя прямоугольниками паузы доступной для щелчка, отсюда непрозрачность: 0 и значение заливки.
Мы определили класс .cd-fade-out
, который будет применяться в javascript в конце анимации загрузки к элементу #cd-loading
.
Наконец, мы использовали класс .no-js
на случай, если javascript отключен: в качестве запасного варианта мы показываем не значок загрузки, а здания (это просто имеет больше смысла).
.cd-svg-container {
width: 90%;
max-width: 200px;
margin: 0 auto 100px;
}
.cd-svg-container svg {
display: block;
overflow: hidden;
max-width: 100%;
}
.no-js .cd-svg-container {
height: 200px;
background: url("../img/cd-icon.svg") no-repeat center center;
}
.no-js .cd-svg-container svg {
display: none;
}
/* --------------------------------
Main elements - Loading
-------------------------------- */
#cd-loading {
opacity: 1;
visibility: visible;
-webkit-transition: opacity .3s 0s, visibility 0s 0s;
-moz-transition: opacity .3s 0s, visibility 0s 0s;
transition: opacity .3s 0s, visibility 0s 0s;
}
#cd-loading.fade-out {
opacity: 0;
visibility: hidden;
-webkit-transition: opacity .3s 0s, visibility 0s .3s;
-moz-transition: opacity .3s 0s, visibility 0s .3s;
transition: opacity .3s 0s, visibility 0s .3s;
}
#cd-play-btn, #cd-pause-btn {
cursor: pointer;
}
#cd-pause-btn {
pointer-events: none;
}
.play-is-clicked #cd-pause-btn {
pointer-events: auto;
}
/* --------------------------------
Main elements - Buildings
-------------------------------- */
#cd-home-1-chimney, #cd-home-3-roof {
visibility: hidden;
}
Анимация SVG
Мы решили анимировать наш svg с помощью JavaScript, а не SMIL, чтобы поддерживать Internet Explorer (если вас интересует методика SMIL, ознакомьтесь с этим руководством Сары Суэйдан).
В нашей анимации мы изначально хотели скрыть группу #cd -building
. Для этого мы изменили некоторые атрибуты в коде svg.
В качестве примера давайте сосредоточимся на прямоугольном элементе #cd-home-1-door
(который является дверным элементом первого дома слева). Мы хотели, чтобы этот элемент был скрыт в начале, а затем анимировал его высоту от 0 до 28 пикселей. Итак, в структуре HTML мы устанавливаем height = "0"
(вместо height = "28"
) и добавляем data-height = "28"
, чтобы легко получить его с помощью JavaScript.
<rect id="cd-home-1-door" class="cd-stroke cd-stroke-color-1" x="53" y="150" width="20" height="0" data-height="28"/>
Мы использовали метод animate ()
, предоставленный Snap.svg.
var animatingSvg = Snap('#cd-animated-svg'),
buildingDoor1 = animatingSvg.select('#cd-home-1-door');
buildingDoor1.animate({'height': buildingDoor1.attr('data-height')}, 300, mina.easeinout);
Дверь расширяется сверху вниз, но мы хотели обратного эффекта! Итак, мы применили к элементу поворот на 180 градусов.
<rect id="cd-home-1-door" class="cd-stroke cd-stroke-color-1" x="53" y="150" width="20" height="0" transform="rotate(180 63 164)" data-height="28"/>
Одно важное замечание: вначале мы пытались применить поворот CSS (вместо использования атрибута преобразования svg), но, к сожалению, преобразования CSS в SVG в настоящее время не поддерживаются в IE11 и ниже.
Функция rotate ()
принимает три параметра: первый - угол поворота, второй и третий - координаты центра вращения.
После того, как все элементы группы #cd-Building
были скрыты, мы сосредоточились на #cd-loading
. Во-первых, мы хотели скрыть #cd-pause-btn
. Мы присвоили ему масштабное преобразование:
<g id="cd-pause-btn" transform="translate(100 100) scale(0)">
<!-- .... -->
</g>
Одно важное замечание: функция scale ()
не принимает в качестве аргумента исходные координаты преобразования (по умолчанию используется верхний левый угол svg).
Чтобы масштабировать объект по заданному коэффициенту вокруг точки, вы можете комбинировать два преобразования:
transform="translate(-centerX(factor- 1) -centerY(factor- 1)) scale(factor)"
Поскольку в нашем случае factor = 0
и (centerX
, centerY
) = (100, 100), мы применили также преобразование translate
(100 100).
После этого нам нужно было анимировать элемент #cd-loading-circle-fill
(малиновый круг, который рисуется при нажатии кнопки воспроизведения). Для создания этой анимации мы использовали два атрибута svg: stroke-dasharray
и stroke-dashoffset
. Представляя круг как пунктирную линию, штрих-дашаррей позволяет вам указать длину штриха и промежутки, а штриш-штрих-смещение позволяет вам изменить место начала дашаррея. В нашем случае мы устанавливаем:
var loadingCircle = animatingSvg.select('#cd-loading-circle-filled'),
circumf = Math.PI*(loadingCircle.attr('r')*2);
loadingCircle.attr({
'stroke-dasharray': circumf+' '+circumf,
'stroke-dashoffset': circumf,
});
Таким образом, тире и промежуток равны длине окружности круга, но мы устанавливаем stroke-dashoffset: circf
так, чтобы был виден только промежуток (прозрачный). Чтобы создать эффект загрузки, мы анимировали атрибут stroke-dashoffset
:
var playBtn = animatingSvg.select('#cd-play-btn').
globalAnimation;
playBtn.click(function(){
var strokeOffset = loadingCircle.attr('stroke-dashoffset').replace('px', '');
//animate strokeOffeset desn't work with circle element - we need to use Snap.animate() rather than loadingCircle.animate()
globalAnimation = Snap.animate(strokeOffset, '0', function( value ){
loadingCircle.attr({ 'stroke-dashoffset': value })
}, 1500, mina.easein);
});
Одно важное замечание: атрибут stroke-dashoffset
для элемента круга нельзя анимировать напрямую с помощью метода animate ()
(хотя он отлично работает для элемента пути); вот почему мы использовали метод Snap.animate ()
и сохранили возвращенный объект анимации в переменной globalAnimation
, чтобы остановить анимацию, когда пользователь нажимает кнопку паузы:
var pauseBtn = animatingSvg.select('#cd-pause-btn');
pauseBtn.click(function(){
//pause the animation on the loadingCircle
globalAnimation.stop();
});
Как только круговая анимация завершена, мы начинаем анимировать здания и облака, используя технику, описанную в начале этого абзаца.
Вот и все! Надеюсь, поддержка браузеров скоро станет лучше, и мы будем полагаться на технологию SVG не только для создания масштабируемых графических элементов, но и сложных анимаций;)