Comment animerais-je le processus d'une image de toile transformée dans son état d'origine?

Je suis à une transformation de (1, 0, 0, -0.7, 0.4, 320, 70) et je veux finir progressivement à (1, 0, 0, 1, 0, 0), comment puis-je faire cela?

C'est le code qui transforme les 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"; }); 

Et je veux essentiellement que quelque chose comme ça se produise:

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

Cependant, le code ci-dessus ne fonctionne pas et est codé, ce qui n'est pas ce que je veux. Je pensais en quelque sorte utiliser un type setTimeout pour le faire, mais je ne suis pas sûr de savoir comment j'y vais.

Tweening.

La plupart des animations impliquent des images clés. À sa plus simple, vous commencez à un état et au fil du temps, vous passez à l'étape suivante.

Interpolation simple (AKA lerp)

Par exemple, nous avons des images clés avec une valeur et un temps.

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

À un moment donné, nous voulons ce que la valeur devrait être. Donc, en ignorant les temps en dehors de la gamme, nous pouvons écrire une fonction simple qui obtient la valeur pour un temps donné. Il convertit d'abord le temps en un temps normalisé (0 à 1) entre les touches, où 0 est l'heure à la première touche et 1 fois la seconde touche.

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

C'est l'exemple détaillé et peut être simplifié pour

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

Et assouplissant

La beauté de le faire de cette façon est que le temps normalisé peut également avoir une des nombreuses fonctions d'assouplissement qui lui sont appliquées. Une fonction d'assouplissement prend une valeur de 0 à 1 et renvoie une nouvelle valeur de 0 à 1 mais met une courbe à la place de la ligne linéaire.

Un exemple de facilitation des fonctions

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

Pour l'utiliser (notez que la fonction de facilité accroche la valeur à la plage de 0 à 1 de sorte que si le temps est en dehors de la plage, l'animation arrête

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

La plus grande partie de la fonction d'assouplissement commune peut être trouvée à l' assouplissement facile de Github

Mettre à jour

Oh, j'aurais dû lire la page github ci-dessus avant de publier car les fonctions ne sont que des variations sur la fonction de désactivation dans l'extrait ci-dessus. Une excellente page de fonction d' assouplissement Easing exemples et code et une autre page pour une référence rapide d'assouplissement visuel

La transformation

Donc, c'est l'essentiel de l'interpolation et est facile à adapter pour des choses comme les positions et les transformations.

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

Tout plutôt simple vrai … MAIS ….

Un meilleur lerp (fixant les défigurés)

La matrice de transformation a des propriétés spéciales et encode beaucoup de choses comme la position, la rotation, l'échelle X / Y, l'inclinaison, le cisaillement et l'application d'une fonction simple de lerp (interpolation) n'obtiendront pas les bons résultats lorsque l'objet transformé sera déformé.

Ce que vous devez faire, c'est de décomposer la transformation en une forme plus utilisable. Ce moyen simple prend une transformée normale et renvoie les différentes valeurs codées que nous voulons animer. La transformation a 3 parties de base, l'axe X (deux premières valeurs), l'axe Y (deuxièmes deux valeurs) et l'origine (deux derniers). L'axe X, Y a une direction et une échelle, et l'origine n'est qu'une simple coordonnée.

Donc, créez une fonction de décomposition qui renvoie un tableau qui détient la direction de l'axe x, y, x, y et l'origine

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

Maintenant que nous avons cette fonction, nous pouvons transformer la transformation en valeurs décomposées et les mettre dans les clés

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

Ensuite, vous simplement entre les clés comme auparavant, mais cette fois, les rotations, les échelles et l'inclinaison seront plus précisément interpolés.

Bien sûr, la matrice décomposée est inutile, il faut donc la convertir en une matrice de transformation utilisable.

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

Maintenant, vous pouvez appliquer l'interpolation pour obtenir la nouvelle (transformée décomposée) et la dissimuler à la matrice standard pour appliquer sur le canevas

 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. 

Donc maintenant, vous avez le début d'une interface d'animation à une image clé très pratique. Avec un peu plus de logique pour plusieurs images clés, vous pouvez faire n'importe quelle animation que vous souhaitez, maintenant le problème sera de savoir où obtenir les images clés de ????

J'ai utilisé un peu de votre code et ajouté une boucle d'animation. Vous pouvez utiliser setTimeout au lieu de requestAnimationFrame. SetTimeout (animationLoop, millisecondes);

 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>