Содержание
Думаю многие видели обзоры игровых консолей нового поколения от Polygon (Vox Media). Это те, где консоли отрисовывались в стиле blueprint’ов:
Обзоры выглядели круто, довольно необычно и ново. О том как реализована основная фишка обзоров — 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). Он позволяет получить координаты точки, находящейся на заданной дистанции от начала линии. С помощью него можно реализовать движение какого-либо маркера вдоль линии, и, что немаловажно, плавное вращение за счёт вычисления касательной к имеющейся линии.
Начнём просто с движения вдоль линии, пока без вращения.
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
, да много чего можно придумать.
А можно использовать с практической точки зрения, например для анимации движения по маршрутам на схемах и планах (анимация стрелочек и прочее). А ещё для инфографики, тут масса вариантов, если грамотно использовать можно весьма эффективно управлять вниманием читателя.
На этом всё. Пробуйте, экспериментируйте, созидайте — всё в ваших руках. И пусть в наступившем году вам сопутствует удача.