...

воскресенье, 5 января 2014 г.

Анимация SVG элемента path

Думаю многие видели обзоры игровых консолей нового поколения от Polygon (Vox Media). Это те, где консоли отрисовывались в стиле blueprint'ов:
PlayStation 4



Обзоры выглядели круто, довольно необычно и ново. О том как реализована основная фишка обзоров — SVG анимация, как сделать нечто подобное самому, и какие ещё «секретные» возможности скрывает старый добрый SVG в плане анимации элемента path — можно узнать под катом.

Stroke-dasharray interpolation, теория




Вообще техника подобной анимации линий не нова, просто до недавнего времени SVG и всё, что с ним связано, на мой взгляд, было несправедливо предано забвению, но к счастью ситуация меняется. Итак, трюк с анимацией элемента path возможен благодаря свойству stroke-dasharray элемента path. Это свойство позволяет задавать параметры пунктирной линии, а именно длину штриха и промежутка между штрихами. Если задать длину штриха равной всей длине линии, то получим обыкновенную сплошную линию. Если же задать длину штриха равной нулю, а длину промежутка опять-таки равной всей длине линии, то получим невидимую линию. А постепенно увеличивая длину штриха при длине промежутка, равной длине всей линии, мы можем имитировать её отрисовку. При таком подходе отрисовка будет происходить от начала линии. Если же вдруг необходимо отрисовывать с конца, то нужно использовать ещё одно свойство: stroke-dashoffset. Это свойство определяет смещение для первого штриха. Таким образом, уменьшая смещение и увеличивая длину штриха, получим отрисовку с конца линии.

Ребята из Vox Media использовали гибридный вариант (который, на мой взгляд, избыточен), кстати почитать о том, как они это делали, можно (и нужно) в их блоге: Polygon feature design: SVG animations for fun and profit.


Реализация SVG анимации




В Vox Media предлагают использовать requestAnimationFrame для плавности анимации, но у нас немного другие цели, так что мы пойдём более простым путём, воспользуемся библиотекой D3.js и реализованной в ней анимацией на основе длительности.

Вот собственно рабочий код, использовавшийся для анимации консоли из начала статьи.



queue()
.defer(d3.xml, "PS4.svg", "image/svg+xml")
.await(ready);

function ready(error, xml) {

//Adding our svg file to HTML document
var importedNode = document.importNode(xml.documentElement, true);
d3.select("#pathAnimation").node().appendChild(importedNode);

var svg = d3.select("svg"),
svgWidth = svg.attr("width"),
svgHeight = svg.attr("height");

var paths = svg.selectAll("path")
.call(transition);

function transition(path) {
path.transition()
.duration(5000)
.attrTween("stroke-dasharray", tweenDash)
.each("end", function() { d3.select(this).call(transition); }); // infinite loop
}

function tweenDash() {
var l = this.getTotalLength(),
i = d3.interpolateString("0," + l, l + "," + l); // interpolation of stroke-dasharray attr
return function(t) {
return i(t);
};
}
}




В функции transition(path) мы используем transition.attrTween(name, tween), который вызывает интерполятор, определённый в функции tweenDash(). Для вычисления длины используется метод getTotalLength(), определённый для элементов SVG path. Затем с помощью d3.interpolateString(a, b) задаётся интерполятор свойства stroke-dasharray, который выполнит интерполяцию значений от stroke-dasharray: 0,l; до stroke-dasharray: l,l; (здесь первое значение задаёт длину штриха, а второе длину промежутка). Посмотреть на то как это работает можно на bl.ocks.org: PlayStation 4: SVG animation.

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


Движение вдоль элемента path




У path есть ещё один весьма полезный метод — getPointAtLength(distance in float). Он позволяет получить координаты точки, находящейся на заданной дистанции от начала линии. С помощью него можно реализовать движение какого-либо маркера вдоль линии, и, что немаловажно, плавное вращение за счёт вычисления касательной к имеющейся линии.
Rocket along path animation



Начнём просто с движения вдоль линии, пока без вращения.

queue()
.defer(d3.xml, "wiggle.svg", "image/svg+xml")
.await(ready);

function ready(error, xml) {

//Adding our svg file to HTML document
var importedNode = document.importNode(xml.documentElement, true);
d3.select("#pathAnimation").node().appendChild(importedNode);

var svg = d3.select("svg");

var path = svg.select("path#wiggle"),
startPoint = pathStartPoint(path);

var marker = svg.append("circle");
marker.attr("r", 7)
.attr("transform", "translate(" + startPoint + ")");

transition();

//Get path start point for placing marker
function pathStartPoint(path) {
var d = path.attr("d"),
dsplitted = d.split(" ");
return dsplitted[1].split(",");
}

function transition() {
marker.transition()
.duration(7500)
.attrTween("transform", translateAlong(path.node()))
.each("end", transition);// infinite loop
}

function translateAlong(path) {
var l = path.getTotalLength();
return function(i) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";//Move marker
}
}
}
}




Здесь pathStartPoint(path) вытаскивает координаты начала линии из атрибута d элемента path. В translateAlong(path) с помощью интерполятора задаются координаты нашего маркера. Пример можно посмотреть здесь: Marker animation along SVG path element with D3.js. А ещё можно объединить анимацию отрисовки линии и движение маркера, выглядеть это может следующим образом: Marker animation along SVG path element with D3.js II.

Усложним задачу, добавим вращение (ну и поменяем маркер с круга на что-нибудь поинтереснее). В качестве маркера у нас будет ракета с шириной 48 и длиной 24. Поскольку по умолчанию точкой привязки маркера является левый верхний угол, нам необходимо смещать его, чтобы привязка была к центру маркера. Также необходимо учитывать это при вращении, ведь оно тоже по умолчанию происходит вокруг левого верхнего угла. Со смещением вроде разобрались. Теперь перейдём непосредственно к вращению, здесь нам поможет определение касательной, угол будем определять с помощью арктангенса.


Функция translateAlong(path), определяющая интерполятор будет выглядеть следующим образом:



function translateAlong(path) {
var l = path.getTotalLength();
var t0 = 0;
return function(i) {
return function(t) {
var p0 = path.getPointAtLength(t0 * l);//previous point
var p = path.getPointAtLength(t * l);////current point
var angle = Math.atan2(p.y - p0.y, p.x - p0.x) * 180 / Math.PI;//angle for tangent
t0 = t;
//Shifting center to center of rocket
var centerX = p.x - 24,
centerY = p.y - 12;
return "translate(" + centerX + "," + centerY + ")rotate(" + angle + " 24" + " 12" +")";
}
}
}




Реализацию можно посмотреть здесь: Marker animation along SVG path element with D3.js III.

Где и для чего это можно использовать




Во-первых, для достижения интересного эффекта с точки зрения дизайна. Можно отображать объекты по мере скроллинга, можно делать анимацию текста, правда надо учитывать что по умолчанию каждая буква — это отдельный элемент path, да много чего можно придумать.

А можно использовать с практической точки зрения, например для анимации движения по маршрутам на схемах и планах (анимация стрелочек и прочее). А ещё для инфографики, тут масса вариантов, если грамотно использовать можно весьма эффективно управлять вниманием читателя.


На этом всё. Пробуйте, экспериментируйте, созидайте — всё в ваших руках. И пусть в наступившем году вам сопутствует удача.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Комментариев нет:

Отправить комментарий