Анимация 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, да много чего можно придумать.

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

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

ссылка на оригинал статьи http://habrahabr.ru/post/207908/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *