Пользователь
0,0
рейтинг
5 января 2014 в 16:27

Разработка → Анимация SVG-элемента path tutorial

Думаю многие видели обзоры игровых консолей нового поколения от 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, да много чего можно придумать.

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

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

UPD:
Ещё статьи на эту тему:
@KoGor
карма
67,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (15)

  • +2
    В статье не хватает ссылок на их обзоры.

    • +1
      Статья не про устройства, ссылки лишние :)
  • +1
    SVG рулит, примерно в 2010 году попробовал делать мультимедийные штуки на нем, после чего несколько комерческих проектов с использованием SVG вышло, работало мега шустро на любых Atom-ах и FullHD экранах того периода.
    Пример использование SVG для анимации пути прохода
    Был отдельный редактор, в котором задавались POI и связи между ними, дальше алгоритм уже сам строил пути прохода в зависимости от заданных в коде точек «откуда» и «куда» и подсвечивал этажи:

    • 0
      Да, SVG штука мощная и удобная, ведь с ней можно работать как с помощью JavaScript, так и с помощью CSS. Вообще чем больше я работаю с векторной графикой, тем больше мне она нравится.
      • 0
        Самое главное что Chrome умеет (умел?) использовать GPU для SVG, поэтому анимация почти любой сложности с огромным числом элементов (c 0:30) рисуется и двигается плавно.
        • 0
          На видео элементов кот наплакал. SVG тормозит уже на паре тысяч точек (если это график)
          • 0
            1699 мест в отрисованном зале, учитывая дополнительные элементы, номера и прочее думаю около 3000-4000 элементов наберется (а уж сколько точек — незнаю).
            • 0
              Пардон, под точками я имел в виду элемент path. Ну в общем либо я не умею его готовить, либо еще что, но аналог вот такой штуки bl.ocks.org/mbostock/4063423 (это иерархический кластеринг) при около 10 тысячах элементов заставляла меня очень печалится…
              • 0
                Помойму раньше в хроме был флаг, включающий GPU аксселерацию отрисовки SVG (сейчас увидел только про SVG фильтры, но может это оно и есть). К сожалению не осталось у меня исходников, чтобы посмотреть что и как было тогда сделано, может быть какие то хаки и пришлось лепить, чтобы скорость сохранить. Но точно помню что был в диком восторге от SVG, работой с объектами и анимацией. Лепилось это в нескольких проектах подряд и производительность была самая последняя проблема.
              • 0
                Как вы правильно заметили — подготовка (и оптимизация) SVG имеют огромное влияние на производительность. Десять тысяч элементов вроде цифра не маленькая, но поскольку о сложности этих элементов ничего неизвестно — это не показатель. Но то, что с отрисовкой карты мира масштаба 1:50,000,000 (1см = 500км) особых проблем нет (а это порядка 69 тысяч точек), вселяет надежду. Вам я советую изучить вопрос оптимизации SVG, возможно удастся упростить часть элементов, уменьшить точность, это должно помочь.
                • 0
                  По поводу сложности — допустим сектор круга (d3'шный arc). Вопрос не про единовременную отрисовку, к ней вопросов нет, вопрос был про анимацию. Я не знаю с каким фпс-ом работает transition в d3, но вот requestAnimationFrame это по дефолту 60 фпс. В анимации, допустим, меняются радиусы у этих элементов. Профайлер у меня вообще на изменение атрибута у DOM-элемента ругался больше всего. Снижение фпс, замена честных секторов на полигоны и тому подобное да, помогает. Просто мне не понравился тезис
                  поэтому анимация почти любой сложности с огромным числом элементов (c 0:30) рисуется и двигается плавно.

                  Ну и не надо забывать что есть например IE, в котором SVG работает не в пример хуже чем в хроме. Если я где-то не прав, поправьте пожалуйста, скорее всего я и впрямь чего-то не знаю.
                  • 0
                    Ну возможно у вас действительно очень сложный объект, могу посоветовать ещё попробовать анимацию с помощью CSS (если вы этого ещё не сделали), хотя вряд ли там результат будет кардинально отличаться. Ну и как вариант использовать более проворный canvas, если есть возможность.
  • +3
    Ещё статья на тему — 24 ways: Animating Vectors with SVG
    • 0
      Добавил.
  • 0

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.