2016-06-22 23 views
0

我不确定我是否在这里使用了正确的词。我想放松意味着它不会立即跟随鼠标,但有一些延迟?如何在鼠标移动事件后使用缓动来旋转画布对象?

目前虹膜旋转到我的鼠标方向。如果我想让它与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 { 
 
\t constructor(options) { 
 
    \t 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() { 
 
    \t const self = this; 
 
    \t 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; 
 
    \t 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); 
 
    \t 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({ 
 
\t x: 250, 
 
    y: 130, 
 
    radius: 40, 
 
    color: 'black', 
 
    angle: Math.random() * Math.PI * 2 
 
}); 
 

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

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

回答

0

据我所知,与H5的画布,你可能需要自己编写方便的功能。但是,css3动画有几个内置的缓动功能,您可以编写.foo {transition: bottom 1s ease},当.foo元素的bottom样式属性更改时,它们将以ease函数定义的速度移动。参见:https://developer.mozilla.org/en-US/docs/Web/CSS/transitionhttps://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions

此外,检查出这些惊人的动画BB-8秒(与CSS动画建):http://codepen.io/mdixondesigns/pen/PPEJwzhttp://codepen.io/Chyngyz/pen/YWwYGqhttp://codepen.io/bullerb/pen/gMpxNZ

1

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

确定性宽松。

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

例如

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

你会希望找到在时间150中途两者之间的数值。为此,您将时间转换为时间100(开始)返回0和时间200(结束)返回1的分数,我们称之为归一化时间。然后,可以将开始值和结束值之间的差乘以该分数以找到偏移量。

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

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; 

现在申请一个easing,我们需要修改规范化的时间。一个缓动函数只需要一个从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)(含)和功率。权力决定了宽松的数量。如果pow = 1,那么不松动,函数返回n。如果pow = 2,那么该函数与CSS中的easy in out函数相同,开始时缓慢加速,最后减慢。如果pow < 1且pow> 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将计算策略,并最终停在目的地(通过停止我的意思是速度会变得这么小到毫无意义)

这种方法的行为非常依赖于这两个系数,因此玩这些值会改变缓动。有些价值观会给你带来一点抖动的过度拍摄,其他一些则会像移动糖蜜一样慢。

您也可以使它更复杂的加入改变的第二速率,从而可以有加速的加速,这将模拟这样的事情随时间变化的加速空气阻力。您还可以添加最大值以更改速率来设置速度限制。

这应该帮助你做你的宽松政策。

更多信息 欲了解更多信息请参阅这些答案How would I animateHow to scale between two points

不确定性宽松政策适用于您的例子

我已经加入了宽松政策的功能,但它已经推出了新的使用循环值(如角度)时会发生问题。因为我不会在这个答案中进入它,你可以在Finding the smallest angle找到解决这个问题的办法。

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();
<canvas id='canvas' style='width: 700; height: 500;'></canvas>

+0

的你的方法两者都是非常有帮助的。我学到了很多。然而在这个例子中,在-2.9附近的角度出现了一个小故障,虹膜很快就会跳回到底部。我用一个可能的解决方案更新了我的问题,这个解决方案受到了我在问题中发布的链接的启发。你能看看这是否是好的吗? – newguy

+0

@newguy是的,你的解决方案是一个非常好的解决方案.. – Blindman67

+0

你也可以通过获得'Math.asin(crossProduct(v1,v2))'来找到两个矢量之间的角度,其中v1是当前来自眼睛中心的矢量和v2是前一个矢量。 (注意矢量必须标准化)。这会给你角度的相对变化,而不是绝对角度。交叉产品是'v1.x * v2.y - v1.y * v2.x' – Blindman67