Расширенные сведения об анимации SVG

В этом разделе представлены техники анимации SVG промежуточного уровня. Это продолжение раздела Анимация в SVG: начальный уровень. Для усвоения материала этого раздела рекомендуется потратить не менее одного часа.

Примечание  Для просмотра примеров в этом разделе необходим браузер, например Windows Internet Explorer 9 или Internet Explorer более поздней версии, поддерживающий элемент SVG.

В разделе Основные сведения об анимации SVG основное внимание было уделено вращающимся объектам. В этом разделе рассмотрим перемещение объектов в пространстве и наиболее распространенные последствия перемещений, т. е. столкновения.

Для изучения перемещений и столкновений объектов начнем с самого простого объекта — с круга. В следующем примере кода реализовано перемещение круга по экрану.

Пример 1. Простейшее движение

Пример 1


<!DOCTYPE html>
<html>

<head>
  <title>SVG Animation - Circle Translation</title>
  <meta http-equiv="X-UA-Compatible" content="IE=Edge"/> <!-- Remove this comment only if you have issues rendering this page on an intranet site. -->
  <style>
    svg {
      display: block; /* SVG is inherently an inline element. */
      margin: 0 auto; /* Center the SVG viewport. */
    }
  </style>
</head>

<body>
  <svg id="svgElement" width="800px" height="600px" viewBox="0 0 800 600">
    <rect x="0" y="0" width="100%" height="100%" rx="10" ry="10" style="fill: white; stroke: black;" />
    <circle id="circle0" cx="40" cy="40" r="40" style="fill: orange; stroke: black; stroke-width: 1;" />
  </svg>
  <script>
    "use strict";

    var delay = 16; // Used to compute the required displacement value.
    var svgElement = document.getElementById("svgElement"); 
    var circle0 = document.getElementById("circle0"); 

    /* Create custom properties to store the circle's velocity: */
    circle0.vx = 50; // Move the circle at a velocity of 50 pixels per second in the x-direction.
    circle0.vy = 20; // Move the circle at a velocity of 20 pixels per second in the y-direction.

    var r = circle0.r.baseVal.value; // The radius of circle0.
    var boxWidth = svgElement.width.baseVal.value; // The width of the SVG viewport.
    var boxHeight = svgElement.height.baseVal.value; // The height of the SVG viewport.

    var requestAnimationFrameID = window.requestAnimationFrame(doAnim); // Call the doAnim() function to start the demo.

    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

    function s2d(s)
      /*
        The function name "s2d" means "speed to displacement". This function returns the required
        displacement value for an object traveling at "s" pixels per second. This function assumes the following:

           * The parameter s is in pixels per second.
           * "constants.delay" is a valid global constant.
           * The SVG viewport is set up such that 1 user unit equals 1 pixel.
      */ {
      return (s / 1000) * delay; // Given "constants.delay", return the object's displacement such that it will travel at s pixels per second across the screen.
    }

    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

    function doAnim() {
      requestAnimationFrameID = window.requestAnimationFrame(doAnim); // Continue calling the doAnim() function until the circle has hit the bottom or right wall of SVG viewport.

      circle0.cx.baseVal.value += s2d(circle0.vx); // Move the circle in the x-direction by a small amount.
      circle0.cy.baseVal.value += s2d(circle0.vy); // Move the circle in the y-direction by a small amount.

      if ((circle0.cx.baseVal.value >= (boxWidth - r)) || (circle0.cy.baseVal.value >= (boxHeight - r))) // Detect if the circle attempts to exit the SVG viewport assuming the ball is moving to the right and down.
        window.cancelAnimationFrame(requestAnimationFrameID); // The circle has hit the bottom or right wall so instruct the browser to stop calling doAnim().
    }
  </script>
</body>
</html>

Важно  Вместо добавления тега <meta http-equiv-"X-UA-Compatible" content="IE-Edge" /> в блок <head> можно настроить веб-сервер разработки для отправки совместимого с X-UA заголовка HTTP со значением IE=Edge, чтобы страница отображалась в последнем доступном стандартном режиме при разработке в интрасети.

Как видно из предыдущего примера кода, используется стиль создания сценариев SVG DOM (дополнительные сведения об этом стиле создания сценариев см. в разделе Основные сведения об анимации SVG).

Положение центра круга незначительно изменяется каждые 16 миллисекунд (значение переменной delay). Например, это поведение можно описать с помощью псевдокода следующим образом:


<x-coordinate of circle> = <x-coordinate of circle> + 0.5
<y-coordinate of circle> = <y-coordinate of circle> + 0.2

Вместо задания значений приращения Δx (0,5) и Δy (0,2) в коде при помощи добавления двух новых настраиваемых свойств в элемент circle задается вектор скорости движения круга:


circle0.vx = 50; // Move the circle at a velocity of 50 pixels per second in the x-direction.
circle0.vy = 20; // Move the circle at a velocity of 20 pixels per second in the y-direction.

Этот вектор скорости v можно представить в графическом виде следующим образом:

Двухмерный вектор скорости

Figure 1

Следовательно, circle0.vx — это составляющая вектора скорости движения круга по оси x (в пикселях в секунду), а circle0.vy — это составляющая вектора скорости движения круга по оси y (в пикселях в секунду). Обратите внимание, что система координат xy представляет окно просмотра SVG с началом координат в верхнем левом углу экрана.

Для создания анимации требуется функция, которая преобразует один из компонентов вектора скорости в соответствующее перемещение. Для этого используется функция s2d(v). Например, если параметр v равен 50 пикселям в секунду, а переменная delay равна 16 миллисекундам, то пространственный анализ показывает смещение (50 пкс/с)•(1 с/1000 мс)•(16 мс) = 0,8 пикселей.

Анимация останавливается, когда круг ударяется о правую или нижнюю стенку воображаемого ящика (окна просмотра SVG). Т. е. требуется простая система обнаружения столкновений:


if ( (circle0.cx.baseVal.value > (boxWidth - r)) || (circle0.cy.baseVal.value > (boxHeight - r)) )
  clearInterval(timer);

Поскольку требуется определить, когда край круга (а не его центр) ударится о стенку, необходимо вычесть радиус круга, как показано во фрагменте кода выше (т. е. boxWidth – r и boxHeight – r).

В следующем примере используется методика обнаружения столкновений, описанная выше, и показывается, как мяч (т. е. круг) отскакивает от стенки:

Пример 2. Отскок мяча от стенки ящика

Пример 2


<!DOCTYPE html>
<html>

<head>
  <title>SVG Animation - Circle Translation</title>
  <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> <!-- Remove this comment only if you have issues rendering this page on an intranet site. -->
  <style>
    svg {
      display: block; /* SVG is inherently an inline element. */
      margin: 0 auto; /* Center the SVG viewport. */
    }
  </style>
</head>

<body>
  <svg id="svgElement" width="800px" height="600px" viewBox="0 0 800 600">
    <rect x="0" y="0" width="100%" height="100%" rx="10" ry="10" style="fill: white; stroke: black;" />
    <circle id="circle0" cx="40" cy="40" r="40" style="fill: orange; stroke: black; stroke-width: 1;" />
  </svg>
  <script>
    "use strict";

    var requestAnimationFrameID; // Contains the requestAnimationFrame() object, used to stop the animation.
    var delay = 16; // Used to compute the required displacement value.

    var svgElement = document.getElementById("svgElement"); // Required for Mozilla, this line is not necessary IE9 or Chrome.
    var circle0 = document.getElementById("circle0"); // Required for Mozilla, this line is not necessaryIE9 or Chrome.

    circle0.vx = 150; // Move the circle at a velocity of 150 pixels per second in the x-direction.
    circle0.vy = 60; // Move the circle at a velocity of 60 pixels per second in the y-direction.

    var r = circle0.r.baseVal.value; // The radius of circle0.
    var boxWidth = svgElement.width.baseVal.value; // The width of the SVG viewport.
    var boxHeight = svgElement.height.baseVal.value; // The height of the SVG viewport.

    requestAnimationFrameID = window.requestAnimationFrame(doAnim); // Call the doAnim() function.

    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

    function s2d(s)
      /*
        The function name "s2d" means "speed to displacement". This function returns the required
        displacement value for an object traveling at "s" pixels per second. This function assumes the following:

           * The parameter s is in pixels per second.
           * "constants.delay" is a valid global constant.
           * The SVG viewport is set up such that 1 user unit equals 1 pixel.
      */ {
      return (s / 1000) * delay; // Given "constants.delay", return the object's displacement such that it will travel at s pixels per second across the screen.
    }

    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

    function doAnim() {
      requestAnimationFrameID = window.requestAnimationFrame(doAnim); // Continue calling the doAnim() function until the circle has hit the bottom wall.

      circle0.cx.baseVal.value += s2d(circle0.vx); // Move the circle in the x-direction by a small amount.
      circle0.cy.baseVal.value += s2d(circle0.vy); // Move the circle in the y-direction by a small amount.

      /* Assumes the circle's velocity is such that it will only hit the right wall: */
      if (circle0.cx.baseVal.value >= (boxWidth - r)) // Detect if the circle attempts to exit the right side of the SVG viewport.
        circle0.vx *= -1; // Reverse the direction of the x-component of the ball's velocity vector - this is a right-wall bounce.

      if (circle0.cy.baseVal.value >= (boxHeight - r))
        window.cancelAnimationFrame(requestAnimationFrameID); // The circle has hit the bottom wall so instruct the browser to stop calling doAnim().
    }
  </script>
</body>
</html>

Главный принцип отскока мяча от стенки — это отражение вектора, как показано на следующем упрощенном рисунке:

Отражение вектора от стенки

Figure 2

На рисунке 2 черная пунктирная линия представляет стенку, vin представляет вектор скорости мяча до столкновения со стенкой, а vout — вектор скорости мяча после столкновения со стенкой. Изменяется только знак компонента x вектора скорости. Следовательно, все, что требуется для правильного описания отскока мяча от стенки, — это изменить знак компонента x вектора скорости мяча:


if ( circle0.cx.baseVal.value > (boxWidth - r) )
  circle0.vx *= -1; 

Обратите внимание, что анимация останавливается, когда мяч ударяется о нижнюю стенку:


if ( circle0.cy.baseVal.value > (boxHeight - r) )
  clearInterval(timer);

Предыдущий пример является искусственным в том смысле, что код работает только тогда, когда мяч изначально движется в правильном направлении. Следующий пример исправляет этот недостаток. Однако прежде чем продолжить, взгляните еще раз на рис. 2. Представьте синий вектор, отскакивающий от левой стенки. Очевидно, что, как и в случае с правой стенкой, для получения правильного поведения отскакивающего мяча необходимо изменить знак компонента вектора скорости по оси x. Применяя этот же аргумент для верхней и нижней стенок, можно заключить, что для получения правильного результата необходимо только изменить знак компонента y вектора скорости. Эта логика используется в следующем примере:

Пример 3. Отскок мяча от четырех стенок

Пример 3


<!DOCTYPE html>
<html>

<head>
  <title>SVG Animation - Circle Translation</title>
  <meta http-equiv="X-UA-Compatible" content="IE=Edge"/> <!-- Remove this comment only if you have issues rendering this page on an intranet site. -->
  <style>
    svg {
      display: block; /* SVG is inherently an inline element. */
      margin: 0 auto; /* Center the SVG viewport. */
    }
  </style>
</head>

<body>
  <svg id="svgElement" width="800px" height="600px" viewBox="0 0 800 600">
    <rect x="0" y="0" width="100%" height="100%" rx="10" ry="10" style="fill: white; stroke: black;" />
    <circle id="circle0" cx="40" cy="40" r="40" style="fill: orange; stroke: black; stroke-width: 1;" />
  </svg>
  <script>
    "use strict";

    var requestAnimationFrameID; // Contains the requestAnimationFrame() object.
    var delay = 20; // Used to compute the required displacement value.

    var svgElement = document.getElementById("svgElement"); // Required for Mozilla, this line is not necessary for IE9 or Chrome.
    var circle0 = document.getElementById("circle0"); // Required for Mozilla, this line is not necessary for IE9 or Chrome.
    var r = circle0.r.baseVal.value; // The radius of circle0.
    var boxWidth = svgElement.width.baseVal.value; // The width of the SVG viewport.
    var boxHeight = svgElement.height.baseVal.value; // The height of the SVG viewport.

    /* Create custom properties to store the circle's velocity: */
    circle0.vx = 200; // Move the circle at a velocity of 200 pixels per second in the x-direction.
    circle0.vy = 80; // Move the circle at a velocity of 80 pixels per second in the y-direction.

    requestAnimationFrameID = window.requestAnimationFrame(doAnim); // Call the doAnim() function.

    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

    function s2d(s)
      /*
        The function name "s2d" means "speed to displacement". This function returns the required
        displacement value for an object traveling at "s" pixels per second. This function assumes the following:

           * The parameter s is in pixels per second.
           * "constants.delay" is a valid global constant.
           * The SVG viewport is set up such that 1 user unit equals 1 pixel.
      */ {
      return (s / 1000) * delay; // Given "constants.delay", return the object's displacement such that it will travel at s pixels per second across the screen.
    }

    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

    function verticalWallCollision(r, width)
      /*
        Returns true if circl0 has hit (or gone past) the left or the right wall; false otherwise.
      */ {
      return ((circle0.cx.baseVal.value <= r) || (circle0.cx.baseVal.value >= (width - r)));
    }

    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

    function horizontalWallCollision(r, height)
      /*
        Returns true if circl0 has hit (or gone past) the top or the bottom wall; false otherwise.
      */ {
      return ((circle0.cy.baseVal.value <= r) || (circle0.cy.baseVal.value >= (height - r)));
    }

    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

    function doAnim() {
      circle0.cx.baseVal.value += s2d(circle0.vx); // Move the circle in the x-direction by a small amount.
      circle0.cy.baseVal.value += s2d(circle0.vy); // Move the circle in the y-direction by a small amount.

      if (verticalWallCollision(r, boxWidth))
        circle0.vx *= -1; // Reverse the direction of the x-component of the ball's velocity vector.

      if (horizontalWallCollision(r, boxHeight))
        circle0.vy *= -1; // Reverse the direction of the y-component of the ball's velocity vector.

      requestAnimationFrameID = window.requestAnimationFrame(doAnim); // Continue calling the doAnim() function.
    }
  </script>
</body>
</html>

Пример 2. Отскок от стенки и Пример 3. Отскок от четырех стенок отличаются только функциями verticalWallCollision(r, width) и horizontalWallCollision(r, height). Последняя функция состоит из следующей строки:


return ( (circle0.cy.baseVal.value <= r) || (circle0.cy.baseVal.value >= (height - r)) );

Назначение этой строки можно понять из следующего рисунка:

Столкновения с горизонтальными стенками

Figure 3

Как видно из рисунка 3, мяч сталкивается с нижней стенкой, когда координата по оси y центра мяча больше или равна расстоянию r от нижней стенки. Это расстояние можно рассчитать, отняв от высоты значение радиуса. Следовательно, проверка нижней стенки:


circle0.cy.baseVal.value >= (height - r)

Аналогичным образом, мяч сталкивается с верхней стенкой, когда координата по оси y центра мяча меньше или равна расстоянию r. Данное расстояние r – 0 = r, поэтому проверкой для верхней стенки является:


circle0.cy.baseVal.value <= r

Объединение обоих результатов проверки показано в примере кода выше.

Пример 4. Столкновение двух мячей

Пример 4

Наблюдение за одним мячом, который отскакивает от стенок ящика, — не очень интересное занятие. Однако добавление в ящик второго мяча делает пример более интересным. Для этого требуется рассмотреть столкновения мячей друг с другом. Пример 4 поможет вам начать работу. Этот пример кода показан не полностью, поскольку он является очень большим. Чтобы просмотреть код, используйте функцию View source в Windows Internet Explorer. Для удобства приведен снимок экрана Примера 4:

Снимок экрана для примера 4

Сначала создается объект, который представляет общий вектор, и функции для четырех операций с векторами:

  • Сложение векторов.
  • Вычитание векторов.
  • Умножение скаляра и вектора.
  • Скалярное произведение двух векторов (представляющее собой скаляр).

Если понятны основные операции с векторами, реализация этих функций не вызовет затруднений. Чтобы лучше рассмотреть векторы и их операции, обратитесь к Википедии или к сайту Wolfram MathWorld.

Обратите внимание, что функции работы с векторами содержатся в разделе сценария "Функции векторов" (VECTOR FUNCTIONS) и снабжены подробными комментариями. Необходимо отметить, что элемент круга (т. е. мяч) отслеживает его собственный вектор скорости, как указано далее (см. функцию init):


var gv0 = new Vector(0, 0);

ball0.v = gv0;
ball0.v.xc = 200;      
ball0.v.yc = 80;

В этом примере локально создается новый общий вектор gv0, а затем добавляется в глобальный элемент круга ball0. После этого составляющие по оси x и по оси y вектора скорости мяча 0 задаются равными 200 и 80 пикселей в секунду соответственно.

Столкновения мяча со стенкой были описаны в примере 3, поэтому необходимо описать столкновения мяча с мячом. К сожалению, соответствующие математические расчеты являются нетривиальными. Для правильного расчета векторов скорости после столкновения для двух мячей в общем случае требуются следующие математические вычисления:

  1. С помощью двух векторов скорости мячей до столкновения рассчитать относительную скорость Vab:
    
    var Vab = vDiff(ballA.v, ballB.v);
    
    
  2. Вычислить единичный вектор n, который представляет перпендикуляр к точке столкновения:
    
    var n = collisionN(ballA, ballB);
    
    
  3. Вычислить "импульс" f, поскольку импульс сохраняется:
    
    f = f_numerator / f_denominator;
    
    
  4. С помощью двух векторов скорости мячей до столкновения рассчитать относительную скорость Vab:
    
    ballA.v = vAdd( ballA.v, vMulti(f/Ma, n) ); 
    ballB.v = vDiff( ballB.v, vMulti(f/Mb, n) );
    
    

Дополнительные сведения см. в разделе "Создайте столкновение, получите перемещение" в главе Реакция при столкновении.

Пример 5. Сбор воедино: арена мячей

Пример 5

Описав столкновения мяча со стенкой и с другими мячами, можно развернуть пример 4 и рассмотреть несколько мячей, сталкивающихся в пределах круглой арены (в противоположность квадрату) — "арену мячей".

Этот пример кода очень большой, поэтому показан не полностью. Чтобы просмотреть код, используйте функцию Просмотр HTML-кода. Снимок экрана:

Снимок арены мячей

Основной код, относящийся к элементам:

  • Все элементы мячей (т. е. элементы circle) создаются программно, а затем в эти элементы добавляются настраиваемые свойства (например, объект вектора скорости).
  • Цвет, радиус и изначальное расположение (на арене) каждого мяча выбираются случайным образом. Следовательно, при каждом обновлении страницы набор исходных условий будет отличаться.
  • Поскольку мячи больше не ударяются о стенки ящика, для вычисления вектора скорости мяча после столкновения с границей арены используется выражение v – 2(v•n)n. Дополнительные сведения см. в разделе Отражение на веб-сайте Wofram MathWorld.
  • Инертность каждого мяча зависит от его площади (т. е. площади круга).
  • Чтобы настроить количество энергии, теряемое при столкновении, можно указать коэффициент восстановления (т. е. constants.epsilon). Значение 1 указывает, что энергия не теряется, т. е. столкновения являются упругими.

Пример 6. Объектно-ориентированная арена мячей

Отличие Примера 6 от Примера 5 только в том, что он реализован гораздо более объектно-ориентированным способом.

Задания

В два последних примера можно добавить следующую логику:

  • Добавить кнопку сброса.
  • Добавить кнопки для увеличения или уменьшения числа мячей, используемых в моделировании.
  • Добавить кнопки для увеличения и уменьшения скорости движения мячей.
  • Добавить кнопку для увеличения коэффициента восстановления.
  • Добавить кнопку, которая включает отображение траектории мяча (за каждым мячом остается "след", указывающий перемещение центра мяча).
  • Увеличить число обнаруживаемых столкновений (самое важное).

Эти задачи оставляем читателю для самостоятельного выполнения. Выполнение этого задания поможет лучше понять методы, описанные в этом разделе.

Связанные разделы

Основные сведения об анимации SVG
Графика в HTML5
Scalable Vector Graphics (SVG)

 

 

Показ:
© 2014 Microsoft