web-dev-qa-db-ja.com

特定のパスに沿ってオブジェクトをアニメーション化したい

パス上の小さな長方形を移動する必要があります。長方形は、キャンバス内をクリックすると移動します。

オブジェクトが必要なポイントにジャンプするだけなので、アニメーション化することはできません。

Fiddle でコードを見つけてください。

[〜#〜] html [〜#〜]

<canvas id="myCanvas" width=578 height=200></canvas>

[〜#〜] css [〜#〜]

#myCanvas {
    width:578px;
    height:200px;
    border:2px thin;
}

JavaScript

var myRectangle = {
    x: 100,
    y: 20,
    width: 25,
    height: 10,
    borderWidth: 1
};

$(document).ready(function () {
    $('#myCanvas').css("border", "2px solid black");
    var canvas = document.getElementById('myCanvas');
    var context = canvas.getContext('2d');
    var cntxt = canvas.getContext('2d');
    drawPath(context);
    drawRect(myRectangle, cntxt);

    $('#myCanvas').click(function () {
        function animate(myRectangle, canvas, cntxt, startTime) {
            var time = (new Date()).getTime() - startTime;
            var linearSpeed = 10;
            var newX = Math.round(Math.sqrt((100 * 100) + (160 * 160)));
            if (newX < canvas.width - myRectangle.width - myRectangle.borderWidth / 2) {

                myRectangle.x = newX;

            }


            context.clearRect(0, 0, canvas.width, canvas.height);
            drawPath(context);
            drawRect(myRectangle, cntxt);

            // request new frame
            requestAnimFrame(function () {
                animate(myRectangle, canvas, cntxt, startTime);
            });
        }
        drawRect(myRectangle, cntxt);
        myRectangle.x = 100;
        myRectangle.y = 121;
        setTimeout(function () {
            var startTime = (new Date()).getTime();
            animate(myRectangle, canvas, cntxt, startTime);
        }, 1000);

    });
});

$(document).keypress(function (e) {
    if (e.which == 13) {


        $('#myCanvas').click();

    }
});

function drawRect(myRectangle, cntxt) {

    cntxt.beginPath();
    cntxt.rect(myRectangle.x, myRectangle.y, myRectangle.width, myRectangle.height);
    cntxt.fillStyle = 'cyan';
    cntxt.fill();

    cntxt.strokeStyle = 'black';
    cntxt.stroke();
};

function drawPath(context) {

    context.beginPath();
    context.moveTo(100, 20);

    // line 1
    context.lineTo(200, 160);
    // quadratic curve
    context.quadraticCurveTo(230, 200, 250, 120);

    // bezier curve
    context.bezierCurveTo(290, -40, 300, 200, 400, 150);

    // line 2
    context.lineTo(500, 90);
    context.lineWidth = 5;
    context.strokeStyle = 'blue';
    context.stroke();
};
22
Chandni

特定のパスに沿ってオブジェクトを移動する方法は次のとおりです

enter image description here

アニメーションには、時間の経過に伴う動きが含まれます。したがって、アニメーションの「フレーム」ごとに、移動オブジェクト(長方形)を描画するXY座標を知る必要があります。

このコードは、パーセント完了(0.00から1.00)を取り、パスセグメントに沿ったそのパーセントであるXY座標を返します。例えば:

  • 0.00は、線(または曲線)の先頭のXYを返します。
  • 0.50は、線(または曲線)の中央のXYを返します。
  • 1.00は、線(または曲線)の最後のXYを返します。

行に沿って指定された割合でXYを取得するコードは次のとおりです。

// line: percent is 0-1
function getLineXYatPercent(startPt,endPt,percent) {
    var dx = endPt.x-startPt.x;
    var dy = endPt.y-startPt.y;
    var X = startPt.x + dx*percent;
    var Y = startPt.y + dy*percent;
    return( {x:X,y:Y} );
}

以下は、二次ベジェ曲線に沿って指定された割合でXYを取得するコードです。

// quadratic bezier: percent is 0-1
function getQuadraticBezierXYatPercent(startPt,controlPt,endPt,percent) {
    var x = Math.pow(1-percent,2) * startPt.x + 2 * (1-percent) * percent * controlPt.x + Math.pow(percent,2) * endPt.x; 
    var y = Math.pow(1-percent,2) * startPt.y + 2 * (1-percent) * percent * controlPt.y + Math.pow(percent,2) * endPt.y; 
    return( {x:x,y:y} );
}

以下は、3次ベジェ曲線に沿って指定された割合でXYを取得するコードです。

// cubic bezier percent is 0-1
function getCubicBezierXYatPercent(startPt,controlPt1,controlPt2,endPt,percent){
    var x=CubicN(percent,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
    var y=CubicN(percent,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
    return({x:x,y:y});
}

// cubic helper formula at percent distance
function CubicN(pct, a,b,c,d) {
    var t2 = pct * pct;
    var t3 = t2 * pct;
    return a + (-a * 3 + pct * (3 * a - a * pct)) * pct
    + (3 * b + pct * (-6 * b + b * 3 * pct)) * pct
    + (c * 3 - c * 3 * pct) * t2
    + d * t3;
}

そして、あなたのパスのさまざまなセグメントをアニメーション化するためにそれをすべてまとめる方法は次のとおりです

// calculate the XY where the tracking will be drawn

if(pathPercent<25){
    var line1percent=pathPercent/24;
    xy=getLineXYatPercent({x:100,y:20},{x:200,y:160},line1percent);
}
else if(pathPercent<50){
    var quadPercent=(pathPercent-25)/24
    xy=getQuadraticBezierXYatPercent({x:200,y:160},{x:230,y:200},{x:250,y:120},quadPercent);
}
else if(pathPercent<75){
    var cubicPercent=(pathPercent-50)/24
    xy=getCubicBezierXYatPercent({x:250,y:120},{x:290,y:-40},{x:300,y:200},{x:400,y:150},cubicPercent);
}
else {
    var line2percent=(pathPercent-75)/25
    xy=getLineXYatPercent({x:400,y:150},{x:500,y:90},line2percent);
}

// draw the tracking rectangle
drawRect(xy);

これが作業コードとフィドルです: http://jsfiddle.net/m1erickson/LumMX/

<!doctype html>
<html lang="en">
<head>

  <style>
      body{ background-color: ivory; }
      canvas{border:1px solid red;}
  </style>

  <link rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" />
  <script src="http://code.jquery.com/jquery-1.9.1.js"></script>
  <script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>

  <script>

  $(function() {

      var canvas=document.getElementById("canvas");
      var ctx=canvas.getContext("2d");

      // set starting values
      var fps = 60;
      var percent=0
      var direction=1;

      // start the animation
      animate();

      function animate() {

          // set the animation position (0-100)
          percent+=direction;
          if(percent<0){ percent=0; direction=1; };
          if(percent>100){ percent=100; direction=-1; };

          draw(percent);

          // request another frame
          setTimeout(function() {
              requestAnimationFrame(animate);
          }, 1000 / fps);
      }


      // draw the current frame based on sliderValue
      function draw(sliderValue){

          // redraw path
          ctx.clearRect(0,0,canvas.width,canvas.height);
          ctx.lineWidth = 5;

          ctx.beginPath();
          ctx.moveTo(100, 20);
          ctx.lineTo(200, 160);
          ctx.strokeStyle = 'red';
          ctx.stroke();

          ctx.beginPath();
          ctx.moveTo(200, 160);
          ctx.quadraticCurveTo(230, 200, 250, 120);
          ctx.strokeStyle = 'green';
          ctx.stroke();

          ctx.beginPath();
          ctx.moveTo(250,120);
          ctx.bezierCurveTo(290, -40, 300, 200, 400, 150);
          ctx.strokeStyle = 'blue';
          ctx.stroke();

          ctx.beginPath();
          ctx.moveTo(400, 150);
          ctx.lineTo(500, 90);
          ctx.strokeStyle = 'gold';
          ctx.stroke();

          // draw the tracking rectangle
          var xy;

          if(sliderValue<25){
              var percent=sliderValue/24;
              xy=getLineXYatPercent({x:100,y:20},{x:200,y:160},percent);
          }
          else if(sliderValue<50){
              var percent=(sliderValue-25)/24
              xy=getQuadraticBezierXYatPercent({x:200,y:160},{x:230,y:200},{x:250,y:120},percent);
          }
          else if(sliderValue<75){
              var percent=(sliderValue-50)/24
              xy=getCubicBezierXYatPercent({x:250,y:120},{x:290,y:-40},{x:300,y:200},{x:400,y:150},percent);
          }
          else {
              var percent=(sliderValue-75)/25
              xy=getLineXYatPercent({x:400,y:150},{x:500,y:90},percent);
          }
          drawRect(xy,"red");

      }


      // draw tracking rect at xy
      function drawRect(point,color){
          ctx.fillStyle="cyan";
          ctx.strokeStyle="gray";
          ctx.lineWidth=3;
          ctx.beginPath();
          ctx.rect(point.x-13,point.y-8,25,15);
          ctx.fill();
          ctx.stroke();
      }

      // draw tracking dot at xy
      function drawDot(point,color){
          ctx.fillStyle=color;
          ctx.strokeStyle="black";
          ctx.lineWidth=3;
          ctx.beginPath();
          ctx.arc(point.x,point.y,8,0,Math.PI*2,false);
          ctx.closePath();
          ctx.fill();
          ctx.stroke();
      }

      // line: percent is 0-1
      function getLineXYatPercent(startPt,endPt,percent) {
          var dx = endPt.x-startPt.x;
          var dy = endPt.y-startPt.y;
          var X = startPt.x + dx*percent;
          var Y = startPt.y + dy*percent;
          return( {x:X,y:Y} );
      }

      // quadratic bezier: percent is 0-1
      function getQuadraticBezierXYatPercent(startPt,controlPt,endPt,percent) {
          var x = Math.pow(1-percent,2) * startPt.x + 2 * (1-percent) * percent * controlPt.x + Math.pow(percent,2) * endPt.x; 
          var y = Math.pow(1-percent,2) * startPt.y + 2 * (1-percent) * percent * controlPt.y + Math.pow(percent,2) * endPt.y; 
          return( {x:x,y:y} );
      }

      // cubic bezier percent is 0-1
      function getCubicBezierXYatPercent(startPt,controlPt1,controlPt2,endPt,percent){
          var x=CubicN(percent,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
          var y=CubicN(percent,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
          return({x:x,y:y});
      }

      // cubic helper formula at percent distance
      function CubicN(pct, a,b,c,d) {
          var t2 = pct * pct;
          var t3 = t2 * pct;
          return a + (-a * 3 + pct * (3 * a - a * pct)) * pct
          + (3 * b + pct * (-6 * b + b * 3 * pct)) * pct
          + (c * 3 - c * 3 * pct) * t2
          + d * t3;
      }


  });   // end $(function(){});

  </script>
</head>
<body>
    <canvas id="canvas" width=600 height=300></canvas>
</body>
</html>
57
markE

キャンバスの組み込みのベジェ曲線を使用する場合でも、計算を自分で行う必要があります。

cardinal spline のこの実装を使用して、すべてのポイントを事前に計算することができます。

使用例は、この小さなソーセージモバイルが斜面に沿って移動することです(上記の基本スプラインで生成されます)。

Slope demo

完全なデモはこちら(必要に応じてカットアンドコピー)。

必要な主なことは、ポイント配列がある場合、オブジェクトに使用する2つのポイントを見つけることです。これにより、オブジェクトの角度がわかります。

cPoints = quantX(pointsFromCardinalSpline); //see below

//get points from array (dx = current array position)
x1 = cPoints[dx];
y1 = cPoints[dx + 1];

//get end-points from array (dlt=length, must be an even number)
x2 = cPoints[dx + dlt];
y2 = cPoints[dx + dlt + 1];

急斜面での伸びを避けるために、角度に基づいて長さを再計算します。おおよその角度を取得するには、元のエンドポイントを使用して角度を取得し、必要な長さとこの角度に基づいて線の新しい長さを計算します。

var dg = getLineAngle(x1, y1, x2, y2);
var l = ((((lineToAngle(x1, y2, dlt, dg).x - x1) / 2) |0) * 2);

x2 = cPoints[dx + l];
y2 = cPoints[dx + l + 1];

これで、y位置から垂直方向の高さを引くことにより、傾斜に沿って「車」をプロットできます。

まさにこれを行うことに気付くのは、「車」が可変速度で動くということです。これは、基本スプラインの補間によるものです。

X軸を量子化することにより、速度をより均一に見せることができます。急な勾配では点と点の間のy距離が平坦な表面よりも大きくなるため、完全ではありません。実際には2次量子化が必要ですが、この目的のためにx軸のみを実行します。

これにより、x位置ごとに新しいポイントを持つ新しい配列が得られます。

function quantX(pts) {

    var min = 99999999,
        max = -99999999,
        x, y, i, p = pts.length,
        res = [];

    //find min and max of x axis
    for (i = 0; i < pts.length - 1; i += 2) {
        if (pts[i] > max) max = pts[i];
        if (pts[i] < min) min = pts[i];
    }
    max = max - min;

    //this will quantize non-existng points
    function _getY(x) {

        var t = p,
            ptX1, ptX2, ptY1, ptY2, f, y;

        for (; t >= 0; t -= 2) {
            ptX1 = pts[t];
            ptY1 = pts[t + 1];

            if (x >= ptX1) {
                //p = t + 2;

                ptX2 = pts[t + 2];
                ptY2 = pts[t + 3];

                f = (ptY2 - ptY1) / (ptX2 - ptX1);
                y = (ptX1 - x) * f;

                return ptY1 - y;
            }
        }
    }

    //generate new array per-pixel on the x-axis
    //note: will not work if curve suddenly goes backwards
    for (i = 0; i < max; i++) {
        res.Push(i);
        res.Push(_getY(i));
    }
    return res;
}

必要な他の2つの関数は、線の角度を計算する関数と、角度と長さに基づいて終点を計算する関数です。

function getLineAngle(x1, y1, x2, y2) {
    var dx = x2 - x1,
        dy = y2 - y1,
        th = Math.atan2(dy, dx);

    return th * 180 / Math.PI;
}

function lineToAngle(x1, y1, length, angle) {

    angle *= Math.PI / 180;

    var x2 = x1 + length * Math.cos(angle),
        y2 = y1 + length * Math.sin(angle);

    return {x: x2, y: y2};
}
19
user1693593