중급 SVG 애니메이션

기본 SVG 애니메이션 항목의 다음 단계인 이 항목에서는 중급 SVG 애니메이션 기술 항목을 소개합니다. 이 항목에서 제공하는 개념을 완전히 이해하려면 약 1시간 정도 걸립니다.

참고  이 항목에 포함된 예제를 보려면 SVG 요소를 지원하는 브라우저(예: Windows Internet Explorer 9 이상)를 사용해야 합니다.

기본 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>

중요  <head> 블록 내에 <meta http-equiv-"X-UA-Compatible" content="IE-Edge" />를 포함하는 것이 아니라 IE=Edge를 사용하여 X-UA-Compatible HTTP 헤더를 보내도록 웹 개발 서버를 구성하면 인트라넷에서 개발 중인 경우 최신 표준 모드에서 실행 중인지 확인할 수 있습니다.

이전 코드 예제에서와 같이 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 (i.e., 0.5) 및 Δy (i.e., 0.2)의 값을 하드 코드하지 않고 두 개의 새로운 사용자 지정 속성을 원 요소에 추가하여 원에 대한 속도 벡터를 지정했습니다.


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는 다음과 같이 그래픽으로 표시할 수 있습니다.

2-D 속도 벡터

Figure 1

따라서 circle0.vx는 원 속도 벡터의 x 구성 요소(초당 픽셀)이며 circle0.vy는 벡터의 y 구성 요소(초당 픽셀)입니다. 위의 xy 좌표계는 화면의 왼쪽 위 모서리에 원점이 있는 SVG 뷰포트를 나타냅니다.

이제 이러한 구성 요소 속도 벡터 중 하나를 애니메이션에 사용할 적절한 거리로 변환하는 함수가 필요합니다. 이 작업에는 s2d(v) 함수를 사용합니다. 예를 들어 매개 변수 v가 초당 50픽셀이고 delay가 16밀리초인 경우 차원 분석(영문)을 사용하면 거리가 (50픽셀/s)•(1s/1000ms)•(16ms) = 0.8픽셀입니다.

마지막으로 원이 SVG 뷰포트의 오른쪽이나 아래쪽 "상자 벽"에 부딪친 경우 애니메이션을 중지합니다. 따라서 단순한 충돌 감지 형식이 필요합니다.


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

원의 중심이 아니라 원의 가장자리가 벽에 부딪치는 시기를 확인하려면 위의 코드 조각과 같이 원의 반경을 빼야 합니다(boxWidth – rboxHeight – 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보다 크거나 같으면 공은 아래쪽 벽과 충돌했습니다. 이 거리는 높이 – r입니다. 따라서 아래쪽 벽에 대한 테스트는 다음과 같습니다.


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

마찬가지로 공 중심의 y 좌표가 거리 r보다 작거나 같으면 공은 위쪽 벽과 충돌했습니다. 이 거리는 간단히 r – 0 = r이므로 위쪽 벽에 대한 테스트는 다음과 같습니다.


circle0.cy.baseVal.value <= r

두 테스트 결과를 결합하면 위와 같은 반환문이 생성됩니다.

예제 4 - 두 개의 공 충돌

예제 4

상자 안에서 하나의 공이 이리저리 튀는 것을 보는 것은 잠깐 동안만 재미있습니다. 그러나 다른 공을 상자에 추가하는 다음 단계는 좀 더 재미있을 수 있습니다. 이 작업을 수행하려면 공 대 공 충돌 및 계산을 처리해야 합니다. 빠르게 시작하려면 예제 4(영문)를 참조하세요. 길이 때문에 이 코드 예제는 표시되지 않지만 Windows Internet Explorer의 View source 기능을 사용하여 관련 코드를 확인하세요. 편의상 예제 4(영문)의 스크린샷은 다음과 같습니다.

예제 4 스크린샷

먼저 다음 네 개의 공통 벡터 작업을 위한 일반 벡터 및 함수를 나타내는 개체를 만듭니다.

  • 벡터 더하기.
  • 벡터 빼기.
  • 스칼라를 벡터로 나누기.
  • 두 벡터의 내적(스칼라).

기본 벡터 작업을 이해하면 이러한 함수를 간단하게 구현할 수 있습니다. 벡터 및 관련 작업에 대한 자세한 내용은 Wikipedia(영문) 또는 Wolfram MathWorld(영문)를 참조하세요.

이 예제에서 벡터 함수는 “VECTOR FUNCTIONS” 스크립트의 블록에 포함되어 있고 잘 설명되어 있습니다. 그러나 이와 관련하여 살펴 볼 하나의 항목은 각 원 요소(공)가 다음과 같이 자체 속도 벡터를 추적한다는 사실입니다(init 함수 참조).


var gv0 = new Vector(0, 0);

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

여기서 새로운 일반 벡터 gv0은 로컬로 생성되고 전역 ball0 원 요소에 추가됩니다. 이 작업이 완료된 후 공 0 속도 벡터의 x 구성 요소 및 y 구성 요소는 각각 초당 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(영문)를 확장할 수 있습니다.

마찬가지로 길이 때문에 이 예제의 코드는 표시되지 않습니다(소스 보기를 사용하여 코드 확인). 그러나 스크린샷은 다음과 같습니다.

공 경기장 스크린샷

살펴 볼 키 코드 관련 항목에는 다음이 포함됩니다.

  • 모든 공 요소(circle 요소)는 프로그래밍 방식으로 생성되고 사용자 지정 속성이 이러한 요소(예: 속도 벡터 개체)에 추가됩니다.
  • 경기장 내에서 각 공의 색상, 반경 및 최초 위치는 무작위로 선택되므로 페이지를 새로 고칠 때마다 다른 초기 조건 집합이 반환됩니다.
  • 공이 더 이상 단순한 상자 벽에 튀기지 않기 때문에 일반 벡터 반사 수식 v – 2(v•n)n을 사용하여 공에 대한 정확한 경기장 벽 충돌 후 속도 벡터를 계산합니다. 자세한 내용은 Wofram MathWorld에서 반사(영문)를 참조하세요.
  • 각 공의 질량은 공(즉, 원)의 면적과 같습니다.
  • 복원 계수(constants.epsilon)를 조정하여 튀기기당 손실되는 에너지 양을 조정할 수 있습니다. 값 1은 완전한 탄성 충돌에서처럼 에너지 손실이 발생하지 않음을 의미합니다.

예제 6 - 개체 지향 공 경기장

예제 6(영문)은 훨씬 더 개체 지향적인 방법으로 구현되었다는 점만 제외하고 예제 5와 동일합니다.

연습

마지막 두 개의 예제에 관련하여 그 다음 논리 단계에는 다음이 포함될 수 있습니다.

  • 원래대로 단추를 추가합니다.
  • 시뮬레이션에 사용되는 공 수를 늘리고 줄이는 단추를 추가합니다.
  • 시뮬레이션 속도를 높이고 낮추는 단추를 추가합니다.
  • 복원 계수를 줄이는 단추를 추가합니다.
  • 공 선 추적을 토글하는 단추를 추가합니다(각 공의 중심이 이동된 위치를 나타내는 “달팽이 자국”을 남긴다는 개념).
  • 또한 가장 중요한 것은 시뮬레이션에 사용되는 충돌 감지를 가능한 간단하게 개선하는 것입니다.

이러한 확장은 직접 연습해 보세요. 이 항목에 제공된 방법을 이해하는 데 크게 도움이 될 것입니다.

관련 항목

기본 SVG 애니메이션
HTML5 그래픽
Scalable Vector Graphics (SVG)

 

 

표시:
© 2014 Microsoft