Подтвердить что ты не робот

Я хочу сделать анимацию объекта по определенному пути

Мне нужно переместить маленький прямоугольник на путь. Прямоугольник перемещается после щелчка внутри холста.

Я не могу анимировать его, поскольку объект просто перескакивает в нужную точку.

Пожалуйста, найдите код 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();
};
4b9b3361

Ответ 1

Вот как перемещать объект по определенному пути

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

Вот код для получения 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>

Ответ 2

Если вы собираетесь использовать встроенные кривые Безье на холсте, вам все равно придется самому заниматься математикой.

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

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

Slope demo

Полная демонстрация здесь (вырезать и скопировать, как вам будет угодно).

Главное, что вам нужно, это когда у вас есть точечный массив, чтобы найти две точки, которые вы хотите использовать для объекта. Это даст нам угол объекта:

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

Другие две функции, которые нам нужны, - это вычисление угла для линии, а также вычисление конечных точек на основе угла и длины:

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