簡體   English   中英

在鼠標移動事件發生后如何緩動畫布對象?

[英]How to rotate a canvas object following mouse move event with easing?

我不確定在這里是否使用了正確的詞。 我想寬松意味着它不會立即跟隨鼠標,但是會有些延遲?

此刻虹膜朝我的鼠標方向旋轉。 如果我希望它具有與相同的效果怎么辦? 這樣做非常困難還是僅需要簡單的代碼更改? 是否有解決此類問題的標准方法/解決方案?

這是我當前的代碼。 也可以在“ 旋轉虹膜”中找到。

 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> 

有很多方法可以放松。 簡而言之,我將描述兩種方法:確定性寬松和(令人驚訝地)非確定性。 區別在於輕松的目的地是已知的(確定的)或未知的(等待更多的用戶輸入)

確定性寬松。

為此,您需要一個起始值和一個結束值。 您要做的是根據某個時間值在兩者之間找到一個位置。 這意味着開始和結束值也需要與時間相關聯。

例如

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;

現在應用寬松政策,我們需要修改標准化時間。 緩動函數僅取一個從0到1(含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;

這就是確定性寬松的完成方式

出色的緩動功能頁面緩和示例和代碼,另一頁提供快速的視覺緩動參考

非確定性寬松

您可能不知道緩動功能的最終結果是什么,由於新用戶的輸入,它隨時可能變化,如果您使用上述方法並在整個過程中更改最終值,結果將變得不一致且難看。 如果您曾經做過微積分,您可能會認識到上面的easy函數是多項式,因此是更簡單函數的反導數。 此功能僅確定每個時間步長的變化量。 因此,對於非確定性解決方案,我們所知道的只是下一步驟的更改。 為了簡化功能(在接近目的地時快速開始並放慢速度),我們保留一個代表當前速度(變化率)的值,並根據需要修改該速度。

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將會接近並最終在目標處停止(通過停止,我的意思是速度將變得很小而毫無意義)

該方法的行為非常依賴於兩個系數,因此使用這些值將改變緩動性。 一些值會給您帶來一些抖動的過度拍攝,另一些值會變得很慢,就像在糖蜜中移動一樣。

您還可以通過添加第二變化率來使其更加復雜,從而可以加速加速度,這將模擬諸如空氣阻力之類的隨時間變化的加速度。 您還可以將最大變化速率添加到設置速度限制中。

那應該有助於您放松。

更多信息有關更多信息,請參見以下答案我將 如何 設置動畫以及如何在兩點之間縮放

非確定性寬松適用於您的示例

我已經將緩動添加到您的函數中,但是它引入了一個新的問題,當使用諸如角度的循環值時,將會發生這種情況。 因為我不會在此答案中討論它,所以您可以在“ 找到最小角度”找到該問題的解決方案。

 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> 

據我所知,使用h5 canvas,您可能需要自己編寫easy函數。 但是,css3動畫具有多個內置的緩動函數,您可以編寫.foo {transition: bottom 1s ease} ,當.foo元素的bottom style屬性更改時,它們將以ease函數定義的速度移動。 請參閱: https : //developer.mozilla.org/en-US/docs/Web/CSS/transition https://developer.mozilla.org/en-US/docs/Web/CSS/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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM