简体   繁体   English

我如何为转换后的画布图像恢复其原始状态的过程设置动画?

[英]How would I animate the process of a transformed canvas image going back into its original state?

I am at a transform of (1, 0, 0, -0.7, 0.4, 320, 70) and I want to gradually end up at (1, 0, 0, 1, 0, 0), how do I do this? 我处于(1,0,0,-0.7,0.4,320,70)的变换中,并且我想要逐渐以(1,0,0,1,1,0,0)结尾,我该怎么做?

This is the code that transforms the images: 这是转换图像的代码:

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

And I basically want something like this to occur: 我基本上希望这样的事情发生:

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

However, the code above doesn't work and is hard coded, which isn't what I want. 但是,上面的代码不起作用并且是硬编码的,这不是我想要的。 I was thinking about somehow using a setTimeout kind of thing to do this but i'm not sure how I would go about it. 我正在考虑以某种方式使用setTimeout来执行此操作,但我不确定如何处理。

I used some of your code and added an animation loop. 我使用了一些代码,并添加了动画循环。 You could use setTimeout in place of requestAnimationFrame. 您可以使用setTimeout代替requestAnimationFrame。 setTimeout(animationLoop, milliseconds); setTimeout(animationLoop,ms);

 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> 

Tweening. 补间。

Most animations involve key frames. 大多数动画涉及关键帧。 At its most simple you start at one state and over time you progress to the next. 最简单的是,您从一种状态开始,随着时间的推移,您会进入另一种状态。

Simple tweening (AKA lerp) 简单补间 (又名lerp)

For example we have some key frames with a value and a time. 例如,我们有一些带有值和时间的关键帧。

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

At some time we want what the value should be. 在某些时候,我们想要的是什么值。 So ignoring times outside the range we can write a simple function that gets the value for a given time. 因此,忽略时间范围之外的时间,我们可以编写一个简单函数来获取给定时间的值。 It first converts the time to a normalised time (0 to 1) between the keys, where 0 is time at first key and 1 is time at second key. 它首先将时间转换为按键之间的标准化时间(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;
}

That is the detailed example and can be simplified to 这是详细的示例,可以简化为

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

And easing 和缓和

The beauty of doing it this way is that the normalised time can also have one of many easing functions applied to it. 这样做的好处是标准化时间也可以应用许多缓动功能之一。 A easing function takes a value from 0 to 1 and returns a new value from 0 to 1 but puts a curve in place of the linear line. 缓动函数使用0到1之间的值,并返回0到1之间的新值,但是将曲线代替了直线。

An example of easing functions 缓动函数示例

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

To use it (note that the ease function clamps the value to the range 0 to 1 so that the if the time is outside the range the animation stops 要使用它(请注意,缓动功能会将值限制在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);

Most of the common easing function can be found at Github simple easing 大多数常见的缓动功能都可以在Github的简单缓动中找到

Update 更新资料

Oh I should have read the above github page before posting as the functions are just variations on the ease in out function in the snippet above. 哦,我应该在发布之前阅读上面的github页面,因为这些功能只是上面代码片段中easy-out功能的变体。 An excellent easing function page Easing examples and code and another page for a quick visual easing referance 出色的缓动功能页面缓和示例和代码 ,另一页提供快速的视觉缓动参考

The Transform 转换

So that is the basics of tweening and is easy to adapt for things like positions and transforms. 因此,这是补间的基础,并且很容易适应诸如位置和变换之类的事物。

// 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++]
    ];
}

All rather simple realy... BUT.... 非常简单的现实...但是...

A better lerp (fixing the disfigured) 更好的lerp (修复毁容的人)

The transformation matrix has some special properties and encodes many things like position, rotation, scale X/Y, skew, shearing and applying a simple lerp (tween) function will not get the correct results as the object being transformed will be deformed. 变换矩阵具有一些特殊的属性,并且对许多东西进行编码,例如位置,旋转,比例X / Y,偏斜,剪切和应用简单的lerp(补间)功能,由于变换的对象会变形,因此无法获得正确的结果。

What you need to do is decompose the transform into a more usable form. 您需要做的是将转换分解为更可用的形式。 This simple means take a normal transform and return the various encoded values we want to animate. 这种简单的方法可以进行常规变换并返回我们要设置动画的各种编码值。 The transform has 3 basic parts, the X axis (first two values), Y axis (second two values), and origin (last two). 变换包含3个基本部分,X轴(前两个值),Y轴(后两个值)和原点(后两个)。 The X,Y axis have a direction and a scale, and the origin is just a simple coordinate. X,Y轴具有方向和比例,原点只是一个简单的坐标。

So lets create a decompose function that returns an array that holds the x,y axis direction, x,y scales and the origin 因此,让我们创建一个分解函数,该函数返回一个包含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
     ];  
}

Now that we have this function we can convert the transform to the decomposed values and put them in the keys 现在我们有了这个功能,我们可以将转换转换为分解后的值,并将其放入键中

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

Then you just tween the keys as before, but this time the rotations, scales, and skewing will more accurately be tweened. 然后,您只需像以前那样对关键帧进行补间,但是这次将更精确地补间旋转,缩放和倾斜。

Of course the decomposed matrix is of no use so we need to convert it back to a usable transformation matrix. 当然,分解后的矩阵没有用,因此我们需要将其转换回可用的转换矩阵。

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

Now you can apply the tween to get the new (decomposed transform) and covert it back to the standard matrix to apply to the canvas 现在,您可以应用补间以获取新的(分解后的变换)并将其隐化回标准矩阵以应用于画布

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.

So now you have the start of a very handy keyframe animation interface. 因此,现在您可以开始使用非常方便的关键帧动画界面。 With a little more logic for multiple key frames you can do any animation you wish, now the problem will be where to get the keyframes from ???? 通过对多个关键帧添加更多逻辑,您可以执行所需的任何动画,现在的问题是从哪里获取关键帧?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM