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

时间:2016-06-03 20:33:23

标签: javascript html canvas

我正处于(1,0,0,-0.7,0.4,320,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这样做但我不确定如何去做。

2 个答案:

答案 0 :(得分:1)

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

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>

答案 1 :(得分:1)

补间。

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

简单补间(AKA 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 simple easing

找到

更新

哦,我应该在发布之前阅读上面的github页面,因为这些功能只是上面代码片段中缓出功能的变体。一个出色的缓动功能页面Easing examples and code和另一个quick visual easing referance

页面

转化

这就是补间的基础知识,很容易适应位置和变换等事情。

// 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(补间)函数将无法获得正确的结果,因为被转换的对象将变形

您需要做的是将转换分解为更有用的形式。这个简单的方法采用法线变换并返回我们想要设置动画的各种编码值。变换有3个基本部分,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
   ];
}

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

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.

所以现在你开始了一个非常方便的关键帧动画界面。使用多个关键帧的逻辑,你可以做任何你想要的动画,现在问题是从哪里获取关键帧????