Dibujar la ruta de HTML5 / Javascript Canvas en el tiempo

Digamos que tengo un camino:

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

Esto imprime el camino de una vez:

Ruta renderizada

¿Cómo puedo dividir la ruta en subtrazos de longitud determinada? Por ejemplo: context.splitCurrentPathIntoSubPath(0, 0.75) debería devolver solo los primeros 3/4 de la ruta.

Me gustaría utilizar esto para realizar una animación. Si hay un método más fácil, también es bienvenido.

Me encontré con un problema similar al animar arcos SVG usando D3.js. Mi solución toma prestado de eso. No es el más intuitivo, pero se usa comúnmente en animaciones D3. Requiere una configuración cuidadosa del desplazamiento del tablero y la longitud de la línea. CSS Tricks da una buena explicación de la técnica aquí que recomiendo leer antes de ver mi código .

He modificado el JSFiddle anterior con esta técnica implementada para su línea aquí . Tenga en cuenta que esto funcionará incluso si la línea regresa a sí misma.

Una nota sobre la longitud de la línea:

Esta implementación requiere que conozca la longitud aproximada de su línea para que pueda establecer la longitud var para que sea mayor. Para las curvas bezier y cuadrática, esto es complicado, pero se puede hacer sin embargo ( esta pregunta SO parece prometedora ). Para mi demostración, utilicé el método de prueba y error para encontrar que el suyo era de aproximadamente 608 px. Establecer una longitud de hasta 10000 probablemente garantizará que sus líneas siempre dibujen correctamente, pero a costa de tener muchas devoluciones innecesarias de intervalos llamadas cada milisegundo. La conclusión es: si le importa el rendimiento, descubra las cosas más bonitas de la fórmula; si no lo haces, establece esa variable en alto.

Código:

HTML

   webgl couldn't be started   

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(progressleft var dir = dir || -1 context.setLineDash([length]); context.lineDashOffset = dir*(frac+length); context.stroke(); } 

Esta es mi solución, básicamente dibujar un rectángulo sobre tu camino, luego cada actualización de fotogtwig mueve el rectángulo 1 posición X a lo largo, así que lentamente la caja se moverá hacia arriba y lejos de la ruta y se verá como si estuvieras dibujando una ruta animada,

Lo he guardado en jsfiddle para ti 🙂 y aquí está el código independiente

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

Una demostración que dibuja una ruta compleja usando puntos uniformemente espaciados:

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

Una visión general de Uniform Speed

“Velocidad” se define como la distancia por unidad de tiempo.

La “velocidad uniforme”, por lo tanto, viaja a una distancia específica constante por unidad de tiempo.

Por lo tanto, moverse a lo largo de su camino a 2 píxeles por 1/60 de segundo sería un ejemplo de movimiento a una velocidad uniforme.

Para viajar 2 píxeles debes calcular un punto a lo largo de tu camino que está a 2 píxeles de tu último punto.

Dibujar incrementalmente un camino que contiene líneas y curvas a una velocidad uniforme requiere cientos de pequeños cálculos.

A continuación, le mostramos cómo determinar una matriz de puntos espaciados a lo largo de su ruta de manera uniforme:

  • Divide tu camino en sus segmentos: línea, curva cuadrática, curva de Bezier, línea.

  • Calcule muchos (300+) puntos a lo largo de cada segmento usando la fórmula matemática que define cada segmento (vea las fórmulas a continuación) y coloque esos puntos en una matriz.

  • Camine secuencialmente a lo largo de cada punto y calcule la distancia entre puntos (vea la fórmula a continuación).

  • Mantenga un total de la distancia acumulada recorrida a lo largo de los puntos.

  • Cuando el punto actual viajado alcanza la longitud especificada, guarde ese punto en una segunda matriz.

Luego, para animar la ruta de forma incremental, puede crear un bucle de animación que dibuja una línea en cada punto siguiente de la segunda matriz.

Nota: Si mantiene la distancia especificada lo suficientemente pequeña (por ejemplo, 1 a 2 píxeles), las líneas dibujadas aparecerán curvadas cuando sea necesario.

Aquí hay una fórmula que admite este método:

Calcular puntos a lo largo de la línea:

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

Calcular puntos a lo largo de la curva cuadrática:

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

Calcule los puntos a lo largo de Bezier Curve:

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

Distancia entre 2 puntos:

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

¡Buena suerte con tu proyecto!