如何设置变换后的画布图像返回原始状态的过程的动画



我正在进行(1,0,0,-0.7,0.4320,70)的变换,我想逐渐结束于(1,0,0,1,0,0),我该怎么做?

这是转换图像的代码:

document.addEventListener("DOMContentLoaded", function(event) {
image = new Image();
image2 = new Image();
image3 = new Image();
image4 = new Image();
window.onload = function() {
//first image
var width = image.width,
height = image.height;
canvas1 = document.getElementById("num1Canvas");
bottomSlice = canvas1.getContext("2d");
//second image
var width2 = image2.width,
height2 = image2.height;
canvas2 = document.getElementById("num2Canvas");
topSlice = canvas2.getContext("2d");
//third image
newCanvas1 = document.getElementById("newNum1Canvas");
newBottomSlice = newCanvas1.getContext("2d");
//fourth image
newCanvas2 = document.getElementById("newNum2Canvas");
newTopSlice = newCanvas2.getContext("2d");
for (var i = 0; i <= height / 2; ++i) {
//first image transform
bottomSlice.setTransform(1, 0, -0.7, .4, 320, 70);
bottomSlice.drawImage(image,
0, height / 2 - i, width, 2,
0, height / 2 - i, width, 2);
bottomSlice.setTransform(1, 0, -0.7, 0.4, 320, 70);
bottomSlice.drawImage(image,
0, height / 2 + i, width, 2,
0, height / 2 + i, width, 2);
//second image transform 
topSlice.setTransform(1, 0, -0.7, .4, 320, 0.2);
topSlice.drawImage(image2,
0, height2 / 2 - i, width2, 2,
0, height2 / 2 - i, width2, 2);
topSlice.setTransform(1, 0, -0.7, 0.4, 320, 0.2);
topSlice.drawImage(image2,
0, height2 / 2 + i, width2, 2,
0, height2 / 2 + i, width2, 2);
}
};
image.src = "bottom.png";
image2.src = "top.png";
image3.src = "bottom.png";//
image4.src ="top.png";
});

我基本上希望发生这样的事情:

function b(){
bottomSlice.clearRect(0, 0, canvas1.width, canvas1.height+60);
bottomSlice.setTransform(1, 0, -0.6, 0.3, 200, 40);
bottomSlice.drawImage(image, 0, 0);
bottomSlice.clearRect(0, 0, canvas1.width, canvas1.height+60);
bottomSlice.setTransform(1, 0, -0.4, 0.6, 150, 30);
bottomSlice.drawImage(image, 0, 0);
bottomSlice.clearRect(0, 0, canvas1.width, canvas1.height+60);
bottomSlice.setTransform(1, 0, -0.1, 0.8, 100, 20);
bottomSlice.drawImage(image, 0, 0);
bottomSlice.clearRect(0, 0, canvas1.width, canvas1.height+60);
bottomSlice.setTransform(1, 0, 0, 1, 0, 0);
bottomSlice.drawImage(image, 0, 0);
}

然而,上面的代码不起作用,而且是硬编码的,这不是我想要的。我想用setTimeout之类的东西来做这件事,但我不确定该怎么做

推文

大多数动画都涉及关键帧。最简单的是,你从一个状态开始,随着时间的推移,你会进入下一个状态。

简单粗花呢(又名lerp)

例如,我们有一些具有值和时间的关键帧。

var keys[  // time in seconds
{value : 10, time : 0},  
{value : 20, time : 30},
}

在某些时候,我们想要值应该是什么。因此,忽略范围外的时间,我们可以编写一个简单的函数来获取给定时间的值。它首先将时间转换为关键帧之间的归一化时间(0到1),其中0是第一个关键帧处的时间,1是第二个关键帧的时间。

function lerpKeys(time, fromKey, toKey){
var relativeTime = time - fromKey.time;  
var timeDiferance  = toKey.time - fromKey.time;
var normalisedTime = relativeTime / timeDiferance;
var valueDiferance = toKey.value - fromKey.value; 
var currentValue = valueDiferance * normalisedTime + fromKey.value;
return currentValue;
}

这是一个详细的例子,可以简化为

function lerpKeys (time, fromKey, toKey){
var nt = (time - fromKey.time) / (toKey.time - fromKey.time); // normalised time
return (toKey.value - fromKey.value) * nt + fromKey.value;
}

和缓解

这样做的美妙之处在于,归一化时间也可以应用许多缓和函数中的一个。缓和函数取0到1的值,并返回0到1之间的新值,但用曲线代替线性线。

缓解功能的一个例子

// ease in out
function ease (value, strength) {  // strength is the amount of easing 1= no easing 
//  1  < starts slow to fast then back to slow
//  0 < 1 fast to slow to fast
var v = Math.pow(Math.min(1, Math.max(0, value )), strength);
return v / (v + Math.pow(1 - value, strength));
}
// linear (no easing just clamps the value)
function linear (value){
return Math.max(0, Math.min(1, value));
}

要使用它(请注意,轻松功能将值钳制在0到1的范围内,这样,如果时间超出范围,动画将停止

// time in seconds
// from and to keys
// ease function to use
function lerpKeysEase(time, fromKey, toKey, easeFunc){
var nt = easeFunc((time - fromKey.time) / (toKey.time - fromKey.time), 2); // normalised time
return (toKey.value - fromKey.value) * nt + fromKey.value;
}
var currentValue  = lerpKeysEase(time, keys[0], keys[1], ease);

大多数常见的宽松功能可以在Github简单宽松中找到

更新

哦,我应该在发布之前阅读上面的github页面,因为这些函数只是上面片段中easy-in-out函数的变体。一个优秀的放松功能页面放松示例和代码,以及另一个快速视觉放松参考页面

转换

因此,这是粗花呢的基础,很容易适应位置和变换等情况。

// keys with transforms
var keys[  // time in seconds
{value : [1, 0, -0.7, 0.4, 320, 70] , time : 0},  
{value : [1, 0, 0, 1, 0, 0] , time : 30},
}
// lerp the transform
function lerpTranformKeysEase(time, fromKey, toKey, easeFunc){
var f = fromKey.value; // to save some typing
var t = toKey.value;
var i = 0;
var nt = easeFunc((time - fromKey.time) / (toKey.time - fromKey.time), 2); // normalised time
// return an array with the new transform
return [
(t[i] - f[i]) * nt + f[i++],
(t[i] - f[i]) * nt + f[i++],
(t[i] - f[i]) * nt + f[i++],
(t[i] - f[i]) * nt + f[i++],
(t[i] - f[i]) * nt + f[i++],
(t[i] - f[i]) * nt + f[i++]
];
}

一切都很简单,真的。。。但是。。。。

一个更好的lerp(修复缺陷)

变换矩阵具有一些特殊的性质,并且对许多事情进行编码,如位置、旋转、缩放X/Y、偏斜、剪切和应用简单的lerp(tween)函数将不会得到正确的结果,因为被变换的对象将变形。

你需要做的是将变换分解成一个更有用的形式。这简单的意思是进行一个普通的变换,并返回我们想要设置动画的各种编码值。变换有三个基本部分,即X轴(前两个值)、Y轴(后两个值")和原点(最后两个)。X,Y轴有一个方向和比例,原点只是一个简单的坐标。

因此,让我们创建一个分解函数,返回一个数组,该数组包含x,y轴方向、x,y比例和原点

// NOTE Math.hypot is not supported on all browsers use 
// Math.sqrt(matrix[1] * matrix[1] +  matrix[0] * matrix[0])
// if needed
function matDecompose(matrix){  // matrix as array
return [
Math.atan2(matrix[1], matrix[0]), // x axis direction
Math.atan2(matrix[3], matrix[2]), // y axis direction
Math.hypot(matrix[1], matrix[0]), // x axis scale
Math.hypot(matrix[3], matrix[2]), // y axis scale
matrix[4], matrix[5]   // origin
];  
}

现在我们有了这个函数,我们可以将变换转换为分解的值,并将它们放在密钥中

var keys[  // time in seconds
{value : matDecompose([1, 0, -0.7, 0.4, 320, 70]) , time : 0},  
{value : matDecompose([1, 0, 0, 1, 0, 0]) , time : 30},
}

然后你只需要像以前一样在关键点之间切换,但这一次旋转、缩放和倾斜将更准确地切换。

当然,分解后的矩阵没有用处,所以我们需要将其转换回可用的变换矩阵。

// Returns a matrix as array from the decomposed matrix
function reCompose(m) { // m is the decomposed matrix as array
return [
Math.cos(m[0]) * m[2],  // reconstruct X axis and scale
Math.sin(m[0]) * m[2],
Math.cos(m[1]) * m[3],  // reconstruct Y axis and scale
Math.sin(m[1]) * m[3],
m[4], m[5]  // origin
];
}

现在,您可以应用tween来获得新的(分解的变换),并将其转换回标准矩阵以应用于画布

var currentMatrix  = reCompose( lerpTranformKeysEase(time, keys[0], keys[1], ease));
ctx.setTransform(
currentMatrix[0],
currentMatrix[1],
currentMatrix[2],
currentMatrix[3],
currentMatrix[4],
currentMatrix[5]
);
// Now render the object.

现在,您可以开始使用一个非常方便的关键帧动画界面了。多个关键帧有了更多的逻辑,你就可以随心所欲地制作任何动画,现在的问题是从哪里获得关键帧????

我使用了您的一些代码并添加了一个动画循环。您可以使用setTimeout代替requestAnimationFrame。setTimeout(animationLoop,毫秒);

document.addEventListener("DOMContentLoaded", function(event) {
image = new Image();
image2 = new Image();
image3 = new Image();
image4 = new Image();
window.onload = function() {
//first image
var width = image.width,
height = image.height;
canvas1 = document.getElementById("num1Canvas");
bottomSlice = canvas1.getContext("2d");
//second image
var width2 = image2.width,
height2 = image2.height;
canvas2 = document.getElementById("num2Canvas");
topSlice = canvas2.getContext("2d");
//third image
newCanvas1 = document.getElementById("newNum1Canvas");
newBottomSlice = newCanvas1.getContext("2d");
//fourth image
newCanvas2 = document.getElementById("newNum2Canvas");
newTopSlice = newCanvas2.getContext("2d");
var i = 0;
function animationLoop() {

if (i > height / 2) {
alert('done!');
return;
}

//first image transform
bottomSlice.setTransform(1, 0, -0.7, .4, 320, 70);
bottomSlice.drawImage(image,
0, height / 2 - i, width, 2,
0, height / 2 - i, width, 2);
bottomSlice.setTransform(1, 0, -0.7, 0.4, 320, 70);
bottomSlice.drawImage(image2,
0, height / 2 + i, width, 2,
0, height / 2 + i, width, 2);
//second image transform 
topSlice.setTransform(1, 0, -0.7, .4, 320, 0.2);
topSlice.drawImage(image3,
0, height2 / 2 - i, width2, 2,
0, height2 / 2 - i, width2, 2);
topSlice.setTransform(1, 0, -0.7, 0.4, 320, 0.2);
topSlice.drawImage(image4,
0, height2 / 2 + i, width2, 2,
0, height2 / 2 + i, width2, 2);

i++;
requestAnimationFrame(animationLoop);
}
animationLoop();
};

var can = document.createElement('canvas');
var w = can.width=300;
var h = can.height=300;
var ctx = can.getContext('2d');

ctx.fillStyle="red";
ctx.fillRect(0,0,w,h);
image.src = can.toDataURL("image/png");//"bottom.png";

ctx.fillStyle="blue";
ctx.fillRect(0,0,w,h);
image2.src = can.toDataURL("image/png");//"top.png";

ctx.fillStyle="green";
ctx.fillRect(0,0,w,h);
image3.src = can.toDataURL("image/png");//"bottom.png";

ctx.fillStyle="black";
ctx.fillRect(0,0,w,h);
image4.src = can.toDataURL("image/png");//"top.png";
});
<canvas id="num1Canvas"></canvas>
<canvas id="num2Canvas"></canvas>
<canvas id="newNum1Canvas"></canvas>
<canvas id="newNum2Canvas"></canvas>

最新更新