如何在缓动鼠标移动事件后旋转画布对象?

时间:2016-06-22 11:19:30

标签: javascript html5 html5-canvas

我不确定我是否在这里使用了正确的词语。我想缓和意味着它不会立即跟踪鼠标但有一些延迟?

此时光圈旋转到我的鼠标方向。如果我希望它与this具有相同的效果怎么办?这样做很难或只需要简单的代码更改吗?对于这类问题,有没有标准的方法/解决方案?

这是我目前的代码。它也可以在Rotating Iris找到。

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
    
class Circle {
    constructor(options) {
      this.cx = options.x;
      this.cy = options.y;
      this.radius = options.radius;
      this.color = options.color;

      this.angle = options.angle;

      this.binding();
    }
      
    binding() {
      const self = this;
      window.addEventListener('mousemove', (e) => {
        self.calculateAngle(e);
      });
    }
      
    calculateAngle(e) {
      if (!e) return;
      let rect = canvas.getBoundingClientRect(),
          vx = e.clientX - this.cx,
          vy = e.clientY - this.cy;
      this.angle = Math.atan2(vy, vx);

    }
      
    renderEye() {
      ctx.setTransform(1, 0, 0, 1, this.cx, this.cy);

      ctx.rotate(this.angle);

      let eyeRadius = this.radius / 3;

      ctx.beginPath();
      ctx.arc(this.radius / 2, 0, eyeRadius, 0, Math.PI * 2);
      ctx.fill();

    }
    
    render() {
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.beginPath();
      ctx.arc(this.cx, this.cy, this.radius, 0, Math.PI * 2);
      ctx.closePath();
      ctx.strokeStyle = '#09f';
      ctx.lineWidth = 1;
      ctx.stroke();

      this.renderMessage();
      this.renderEye();

    }
    
    renderMessage() {
      ctx.font = "18px serif";
      ctx.strokeStyle = 'black';
      ctx.fillText('Angle: ' + this.angle, 30, canvas.height - 40);
    }
}
    
var rotatingCircle = new Circle({
    x: 320,
  y: 160,
  radius: 40,
  color: 'black',
  angle: Math.random() * Math.PI * 2
});

function animate() {
    rotatingCircle.render();
    requestAnimationFrame(animate);
}

animate();
<canvas id='canvas' style='width: 700; height: 500;'></canvas>

更新了可能的解决方案:

我实际上是按照我在问题中发布的链接,并使用类似的方式来缓解轮换,我认为类似于@ Blindman67类别作为非确定性缓和。

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

class Circle {
	constructor(options) {
  	this.cx = options.x;
    this.cy = options.y;
    this.radius = options.radius;
    this.color = options.color;
    this.toAngle = 0;
    this.angle = options.angle;
    this.velocity = 0;
    this.maxAccel = 0.04;
    this.binding();
  }
  
  binding() {
  	const self = this;
  	window.addEventListener('mousemove', (e) => {
      self.calculateAngle(e);
    });
  }
  
  calculateAngle(e) {
    if (!e) return;
    let rect = canvas.getBoundingClientRect(),
        // mx = parseInt(e.clientX - rect.left),
        // my = parseInt(e.clientY - rect.top),
        vx = e.clientX - this.cx,
        vy = e.clientY - this.cy;
  	this.toAngle = Math.atan2(vy, vx);

  }
  
  clip(x, min, max) {
    return x < min ? min : x > max ? max : x;
  }

  renderEye() {
    ctx.setTransform(1, 0, 0, 1, this.cx, this.cy);

    let radDiff = 0;
    if (this.toAngle != undefined) {
       radDiff = this.toAngle - this.angle;
    }

    if (radDiff > Math.PI) {
      this.angle += 2 * Math.PI;
    } else if (radDiff < -Math.PI) {
      this.angle -= 2 * Math.PI;
    }

    let easing = 0.06;
    let targetVel = radDiff * easing;
    this.velocity = this.clip(targetVel, this.velocity - this.maxAccel, this.velocity + this.maxAccel);
    this.angle += this.velocity;

    ctx.rotate(this.angle);
        
    let eyeRadius = this.radius / 3;

    ctx.beginPath();
    ctx.arc(this.radius / 2, 0, eyeRadius, 0, Math.PI * 2);
    ctx.fill();

  }

  render() {
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  	ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.beginPath();
    ctx.arc(this.cx, this.cy, this.radius, 0, Math.PI * 2);
    ctx.closePath();
    ctx.strokeStyle = '#09f';
    ctx.lineWidth = 1;
    ctx.stroke();
   
    this.renderMessage();
    this.renderEye();
    
  }

  renderMessage() {
    ctx.font = "18px serif";
    ctx.strokeStyle = 'black';
    ctx.fillText('Angle: ' + this.angle.toFixed(3), 30, canvas.height - 40);
    ctx.fillText('toAngle: ' + this.toAngle.toFixed(3), 30, canvas.height - 20);
  }
}

var rotatingCircle = new Circle({
	x: 250,
  y: 130,
  radius: 40,
  color: 'black',
  angle: Math.random() * Math.PI * 2
});

function animate() {
	rotatingCircle.render();
	requestAnimationFrame(animate);
}

animate();
<canvas id='canvas' style='width: 700; height: 500;'></canvas>

2 个答案:

答案 0 :(得分:1)

有很多方法可以做宽松。我将简要描述的两种方法是确定性缓和和(令人惊讶的)非确定性。不同之处在于,easy的目的地是已知(已确定)或未知(等待更多用户输入)

确定性宽松。

为此,您有一个起始值和一个结束值。你想要做的是根据一些时间价值找到两者之间的位置。这意味着开始和结束值也需要与时间相关联。

例如

var startVal = 10;
var startTime = 100;
var endVal = 100;
var endTime = 200;

您需要在两者之间的中间150找到值。为此,您将时间转换为时间100(开始)返回0且时间200(结束)返回1的分数,我们称之为标准化时间。然后,您可以将起始值和结束值之间的差值乘以此分数,以找到偏移量。

因此,对于获得值(theValue)的时间值150,我们执行以下操作。

var time = 150;
var timeDif = endTime - startTime
var fraction = (startTime - time) / timeDif; // the normalised time
var valueDif = endVal - startVal;
var valueOffset = valueDif * fraction;
var theValue = startVal + valueOffset;

或更简洁。

// nt is normalised time
var nt = (startTime - time) / (endTime - startTime)
var theValue = startVal + (endVal - startVal) * nt;

现在要应用缓动,我们需要修改标准化时间。缓动函数只需要从0到1的值,并修改它。因此,如果输入0.25,则缓动函数返回0.1,或0.5返回0.5,0.75返回0.9。正如您所看到的,修改会随着时间的推移而改变变化率。

缓动功能的一个例子。

var easeInOut = function (n, pow) {
    n = Math.min(1, Math.max(0, n)); // clamp n
    var nn = Math.pow( n, pow);
    return (nn / ( nn + Math.pow(1 - n, pow)))
}

此函数接受两个输入,分数n(0到1,包括0和1)和功率。权力决定了宽松的数量。如果pow = 1则没有缓和,函数返回n。如果pow = 2则该功能与CSS easy in out功能相同,开始慢慢加速然后在结束时减速。如果pow&lt; 1和pow&gt; 0然后轻松开始快速中途减速然后加速到最后。

使用上述缓动值示例中的缓动功能

// nt is normalised time
var nt = (startTime - time) / (endTime - startTime);
nt = easeInOut(nt,2); // start slow speed up, end slow
var theValue = startVal + (endVal - startVal) * nt;

这就是确定性缓和的方式

优秀的宽松功能页面Easing examples和代码以及quick visual easing referance的其他页面

非确定性宽松

您可能不知道缓动函数的最终结果是什么,因为它可能会因新用户输入而发生变化,如果您使用上述方法并在中途更改结束值,则结果将不一致而且丑陋。如果你曾经做过微积分,你可能会发现上面的缓动函数是一个多项式,因此是一个更简单函数的反导数。此功能仅确定每个时间步的更改量。因此,对于非确定性解决方案,我们所知道的是下一个时间步的变化。为了方便起作用(当我们接近目的地时开始快速和慢速),我们保留一个值来表示当前速度(变化率)并根据需要修改该速度。

const ACCELERATION_COEFFICIENT = 0.3;
const DRAG_COEFFICIENT = 0.99;
var currentVal = 100;
var destinationVal = 200;
var currentSpeed = 0;

然后,对于每个时间步,您执行以下操作

var accel = destinationVal - currentVal;  // get the acceleration
accel *= ACCELERATION_COEFFICIENT; // modify it so we are not there instantly
currentSpeed += accel; // add that to the speed
currentSpeed *= DRAG_COEFFICIET; // add some drag to further ease the function as it approaches destination
currentVal += currentSpeed; // add the speed to the current value

现在,如果目标的变化比变化率(速度)也以一致的方式变化,则currentVal将会确定目标值。如果目的地总是在变化,则currentVal可能永远不会到达目的地,但是如果目的地停止改变当前的val将approch并最终停在目的地(通过停止我的意思是速度将变得如此之小以至于毫无意义)

此方法行为非常依赖于两个系数,因此使用这些值会改变缓动。有些值会让你拍摄时有点摇晃,有些则会像穿过糖蜜一样慢。

您还可以通过添加第二个变化率使其变得更加复杂,因此您可以加速加速,这将模拟空气阻力等随时间改变加速度的事物。您还可以将最大值添加到更改速率以设置速度限制。

这应该可以帮助你缓和。

更多信息 有关详细信息,请参阅这些答案How would I animateHow to scale between two points

非确定性缓和适用于您的示例

我已将缓动添加到您的函数中,但它引入了一个新问题,当使用角度等循环值时会发生这个问题。由于我不想在这个答案中进入,你可以在Finding the smallest angle中找到解决该问题的方法。

&#13;
&#13;
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ACCELERATION_COEFFICIENT = 0.15;
const DRAG_COEFFICIENT = 0.5;        
class Circle {
    constructor(options) {
      this.cx = options.x;
      this.cy = options.y;
      this.radius = options.radius;
      this.color = options.color;

      this.angle = options.angle;
      this.angleSpeed = 0;
      this.currentAngle = this.angle;

      this.binding();
    }
      
    binding() {
      const self = this;
      window.addEventListener('mousemove', (e) => {
        self.calculateAngle(e);
      });
    }
      
    calculateAngle(e) {
      if (!e) return;
      let rect = canvas.getBoundingClientRect(),
          vx = e.clientX - this.cx,
          vy = e.clientY - this.cy;
      this.angle = Math.atan2(vy, vx);

    }
      
    renderEye() {
      // this should be in a separate function 
      this.angleSpeed += (this.angle - this.currentAngle) * ACCELERATION_COEFFICIENT;
      this.angleSpeed *= DRAG_COEFFICIENT;
      this.currentAngle += this.angleSpeed;


      ctx.setTransform(1, 0, 0, 1, this.cx, this.cy);

      ctx.rotate(this.currentAngle);

      let eyeRadius = this.radius / 3;

      ctx.beginPath();
      ctx.arc(this.radius / 2, 0, eyeRadius, 0, Math.PI * 2);
      ctx.fill();

    }
    
    render() {
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.beginPath();
      ctx.arc(this.cx, this.cy, this.radius, 0, Math.PI * 2);
      ctx.closePath();
      ctx.strokeStyle = '#09f';
      ctx.lineWidth = 1;
      ctx.stroke();

      this.renderMessage();
   
      this.renderEye();

    }
    
    renderMessage() {
      ctx.font = "18px serif";
      ctx.strokeStyle = 'black';
      ctx.fillText('Angle: ' + this.angle, 30, canvas.height - 40);
    }
}
    
var rotatingCircle = new Circle({
    x: 320,
  y: 160,
  radius: 40,
  color: 'black',
  angle: Math.random() * Math.PI * 2
});

function animate() {
    rotatingCircle.render();
    requestAnimationFrame(animate);
}

animate();
&#13;
<canvas id='canvas' style='width: 700; height: 500;'></canvas>
&#13;
&#13;
&#13;

答案 1 :(得分:0)

据我所知,使用h5 canvas,您可能需要自己编写缓动功能。但是,css3动画有几个内置的easy函数,你可以写.foo {transition: bottom 1s ease}.foo元素&#39; bottom样式属性发生变化,它们将以ease函数定义的速度移动。请参阅:https://developer.mozilla.org/en-US/docs/Web/CSS/transition https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions

另外,看看这些惊人的动画BB-8(用css动画构建):http://codepen.io/mdixondesigns/pen/PPEJwz http://codepen.io/Chyngyz/pen/YWwYGq http://codepen.io/bullerb/pen/gMpxNZ