Dessinez HTML5 / Javascript Canvas Path in Time

Disons que j'ai un chemin:

var context = canvas.getContext('2d'); context.beginPath(); context.moveTo(100, 20); context.lineTo(200, 160); context.quadraticCurveTo(230, 200, 250, 120); context.bezierCurveTo(290, -40, 300, 200, 400, 150); context.lineTo(500, 90); context.lineWidth = 5; context.strokeStyle = 'blue'; context.stroke(); 

Cela imprime le chemin tout à la fois:

Rendered Path

Comment puis-je diviser le chemin en subpaths de longueur donnée? Par exemple: context.splitCurrentPathIntoSubPath(0, 0.75) ne doit retourner que le premier 3/4 du chemin.

J'aimerais utiliser ceci pour réaliser une animation. S'il existe une méthode plus facile, il est également le bienvenu.

J'ai rencontré un problème similaire lors de l'animation d'arcs SVG en utilisant D3.js. Ma solution emprunte à cela. Ce n'est pas le plus intuitif, mais est couramment utilisé dans les animations D3. Il nécessite un réglage minutieux du décalage de tiret et de la longueur de ligne. CSS Tricks donne une bonne explication de la technique ici que je recommande fortement de lire avant de voir mon code .

J'ai modifié le JSFiddle ci-dessus avec cette technique mise en œuvre pour votre ligne ici . Notez que cela fonctionnera même si la ligne s'arrête sur elle-même.

Une note sur la longueur de la ligne:

Cette implémentation nécessite que vous connaissiez la longueur approximative de votre ligne afin que vous puissiez définir la longueur var pour qu'elle soit supérieure à celle-ci. Pour les courbes de bezier et de quadrature, cela est délicat mais peut néanmoins être fait ( cette question de SO est prometteuse ). Pour ma démo, j'ai utilisé un essai et une erreur pour trouver que le vôtre était d'environ 608px. La longueur de réglage à 10000 assurera probablement que vos lignes dessinent toujours correctement, mais au coût d'avoir beaucoup de rappels d'intervalles inutiles appelés chaque milliseconde. La ligne de fond est: si vous vous souciez de la performance, découvrez la formule de Bezier; Si vous ne le faites pas, définissez cette variable élevée.

Code:

HTML

 <body> <canvas id="canvas" width="500" height="500"> webgl couldn't be started </canvas> </body> 

JavaScript

 canvasHolder = document.getElementById( 'canvas' ); context = canvasHolder.getContext('2d'); context.fillStyle = 'white'; var w = canvasHolder.width, h = canvasHolder.height; context.fillRect( 0, 0, w, h); //set the direction the line draws in //1->ltr | -1->rtl var dir = -1; //IMPORTANT: this must be set to greater than the length //of the line var length = 608; //the speed of the line draw var speed = 1; var progress = 0; var lineInterval; //Go! context.globalCompositeOperation='copy'; drawLine(); function drawLine() { //this clears itself once the line is drawn lineInterval = setInterval(updateLine, 1); } function updateLine() { //define the line defineLine(); if(progress<length) { progress+=speed; moveDash(progress, dir); } else { clearInterval(lineInterval); } } function defineLine() { context.beginPath(); context.moveTo(100, 20); context.lineTo(200, 160); context.quadraticCurveTo(230, 200, 250, 120); context.bezierCurveTo(290, -40, 300, 200, 400, 150); context.lineTo(500, 90); context.lineWidth = 5; context.strokeStyle = 'blue'; } function moveDash(frac, dir) { //default direction right->left var dir = dir || -1 context.setLineDash([length]); context.lineDashOffset = dir*(frac+length); context.stroke(); } 

Ceci est ma solution, dessinez essentiellement un rectangle au-dessus de votre chemin, puis chaque mise à jour de la trame déplace le rectangle 1 position X le long, si lentement la boîte sera déplacée et loin du chemin et il semblera que vous dessinez un chemin animé,

Je l'ai sauvegardé sur jsfiddle pour vous 🙂 et voici le code autonome

 window.addEventListener( "load", firstLoaded, false); then = Date.now(); setInterval(main, 1); // Execute as fast as possible var cube_x_position = 0; function main() { context.beginPath(); context.moveTo(100, 20); context.lineTo(200, 160); context.quadraticCurveTo(230, 200, 250, 120); context.bezierCurveTo(290, -40, 300, 200, 400, 150); context.lineTo(500, 90); context.lineWidth = 5; context.strokeStyle = 'blue'; context.stroke(); context.fillRect(cube_x_position, 0, canvasHolder.width, canvasHolder.height); if(cube_x_position < canvasHolder.width) { cube_x_position += 1; } } function firstLoaded() { canvasHolder = document.getElementById( 'canvas' ); context = canvasHolder.getContext('2d'); context.fillStyle = "#AAAAAA"; context.fillRect( 0, 0, 500, 500); } 

Une démonstration dessinant un chemin complexe en utilisant des points uniformément espacés:

http://jsfiddle.net/m1erickson/2fodu9pa/

Un aperçu de la vitesse uniforme

La "vitesse" est définie comme la distance par unité de temps.

La "vitesse uniforme" parcourt donc une distance spécifiée constante par unité de temps.

Ainsi, le déplacement de votre chemin à 2 pixels par 1 / 60ème seconde serait un exemple de déplacement à une vitesse uniforme.

Pour voyager 2 pixels, vous devez calculer un point sur votre chemin qui correspond à 2 pixels de votre dernier point.

Tracer de façon incroyable un chemin qui contient des lignes et des courbes à une vitesse uniforme nécessite des centaines de petits calculs.

Voici comment déterminer un éventail de points qui sont espacés uniformément sur votre chemin:

  • Divisez votre chemin dans leurs segments: ligne, courbe quadratique, courbe de Bezier, ligne.

  • Calculez plusieurs (300) points le long de chaque segment en utilisant la formule mathématique qui définit chaque segment (voir les formules ci-dessous) et placez ces points dans un tableau.

  • Parcourez séquentiellement chaque point et calculez la distance entre les points (voir la formule ci-dessous).

  • Gardez un total de la distance accumulée parcourue le long des points.

  • Lorsque le point actuel parcouru atteint la longueur spécifiée, enregistrez ce point dans un deuxième tableau.

Ensuite, pour animer le chemin de manière incrémentielle, vous pouvez créer une boucle d'animation qui trace une ligne à chaque point suivant du deuxième tableau.

Remarque: Si vous maintenez la distance spécifiée assez petite (par ex. 1-2 pixels), les lignes dessinées apparaissent courbes si nécessaire.

Voici la formule qui prend en charge cette méthode:

Calculer les points le long de la ligne:

 // T is an interval between 0.00 and 1.00 // To divide a Line into 300 parts you would call the function 300 times // with T increasing 1.00/300 each time function getLineXYatPercent(startPt,endPt,T) { var dx = endPt.x-startPt.x; var dy = endPt.y-startPt.y; var X = startPt.x + dx*T; var Y = startPt.y + dy*T; return( {x:X,y:Y} ); } 

Calculer les points le long de la courbe quadratique:

 // T is an interval between 0.00 and 1.00 // To divide a Quadratic Curve into 300 parts you would call the function 300 times // with T increasing 1.00/300 each time function getQuadraticBezierXYatT(startPt,controlPt,endPt,T) { var x = Math.pow(1-T,2) * startPt.x + 2 * (1-T) * T * controlPt.x + Math.pow(T,2) * endPt.x; var y = Math.pow(1-T,2) * startPt.y + 2 * (1-T) * T * controlPt.y + Math.pow(T,2) * endPt.y; return( {x:x,y:y} ); } 

Calculer les points le long de la courbe de Bezier:

 // T is an interval between 0.00 and 1.00 // To divide a BezierCurve into 300 parts you would call the function 300 times // with T increasing 1.00/300 each time function getCubicBezierXYatT(startPt,controlPt1,controlPt2,endPt,T){ var x=CubicN(T,startPt.x,controlPt1.x,controlPt2.x,endPt.x); var y=CubicN(T,startPt.y,controlPt1.y,controlPt2.y,endPt.y); return({x:x,y:y}); } // cubic helper formula at T distance function CubicN(T, a,b,c,d) { var t2 = T * T; var t3 = t2 * T; return a + (-a * 3 + T * (3 * a - a * T)) * T + (3 * b + T * (-6 * b + b * 3 * T)) * T + (c * 3 - c * 3 * T) * t2 + d * t3; } 

Distance entre 2 points:

 var dx=point2.x-point1.x; var dy=point2.y-point1.y; var distance=Math.sqrt(dx*dx+dy*dy); 

Bonne chance pour votre projet!