web-dev-qa-db-fra.com

Je veux faire l'animation d'un objet le long d'un chemin particulier

Je dois déplacer le petit rectangle sur le chemin. Le rectangle se déplace après un clic à l'intérieur du canevas.

Je ne peux pas l'animer car l'objet saute juste au point requis.

Veuillez trouver le code sur 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();
};
24
Chandni

Voici comment déplacer un objet le long d'un chemin particulier

enter image description here

L'animation implique un mouvement dans le temps. Donc, pour chaque "image" de votre animation, vous devez connaître la coordonnée XY où dessiner votre objet en mouvement (rectangle).

Ce code prend un pourcentage complet (0,00 à 1,00) et renvoie la coordonnée XY qui est ce pourcentage le long du segment de chemin. Par exemple:

  • 0,00 renverra le XY au début de la ligne (ou courbe).
  • 0,50 renverra le XY au milieu de la ligne (ou courbe).
  • 1,00 retournera le XY à la fin de la ligne (ou courbe).

Voici le code pour obtenir le XY au pourcentage spécifié le long d'une ligne:

// 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} );
}

Voici le code pour obtenir le XY au pourcentage spécifié le long d'une courbe de Bézier quadratique:

// 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} );
}

Voici le code pour obtenir le XY au pourcentage spécifié le long d'une courbe de Bézier cubique:

// 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;
}

Et voici comment vous mettez tout cela ensemble pour animer les différents segments de votre chemin

// 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);

Voici le code de travail et un violon: 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>
64
markE

Si vous allez utiliser les courbes de Bézier intégrées de la toile, vous devrez toujours faire le calcul vous-même.

Vous pouvez utiliser cette implémentation d'un spline cardinale et avoir tous les points retournés pour vous pré-calculés.

Un exemple d'utilisation est cette petite saucisse mobile se déplaçant le long de la pente (générée avec la spline cardinale ci-dessus):

Slope demo

Démo complète ici (couper et copier à votre guise).

La principale chose dont vous avez besoin est lorsque vous avez le tableau de points, c'est de trouver deux points que vous souhaitez utiliser pour l'objet. Cela nous donnera l'angle de l'objet:

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];

Pour éviter de s'étirer dans des pentes plus raides, nous recalculons la longueur en fonction de l'angle. Pour obtenir un angle approximatif, nous utilisons le point final d'origine pour obtenir un angle, puis nous calculons une nouvelle longueur de la ligne en fonction de la longueur souhaitée et de cet angle:

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];

Maintenant, nous pouvons tracer la "voiture" le long de la pente en soustrayant sa hauteur verticale des positions y.

Ce que vous remarquerez en faisant cela, c'est que la "voiture" se déplace à vitesse variable. Cela est dû à l'interpolation de la spline cardinale.

Nous pouvons l'aplanir pour que la vitesse soit plus uniforme, même en quantifiant l'axe des x. Il ne sera toujours pas parfait car dans les pentes raides, la distance y entre les points sera plus grande que sur une surface plane - nous aurions vraiment besoin d'une quantification quadratique, mais à cette fin, nous ne faisons que l'axe x.

Cela nous donne un nouveau tableau avec de nouveaux points pour chaque position 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;
}

Les deux autres fonctions dont nous avons besoin sont celle qui calcule l'angle d'une ligne et celle qui calcule les points d'extrémité en fonction de l'angle et de la longueur:

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