w3schools property img attribute javascript html5 performance html5-canvas particles

javascript - property - title label html



Partículas de lona, ​​colisiones y rendimiento (4)

Estoy creando una aplicación web que tiene un fondo interactivo con partículas rebotando. En todo momento hay alrededor de 200 partículas circulares en la pantalla y como mucho alrededor de 800 partículas. Algunas de las colisiones y efectos que se están ejecutando para las partículas son los siguientes prototipos. Me pregunto si podría mejorar el rendimiento al usar trabajadores web para hacer estos cálculos.

/** * Particles */ Jarvis.prototype.genForegroundParticles = function(options, count){ count = count || this.logoParticlesNum; for (var i = 0; i < count; i++) { this.logoParticles.push(new Particle()); } } Jarvis.prototype.genBackgroundParticles = function(options, count){ count = count || this.backgroundParticlesNum; for (var i = 0; i < count; i++) { this.backgroundParticles.push(new Particle(options)); } } Jarvis.prototype.motion = { linear : function(particle, pIndex, particles){ particle.x += particle.vx particle.y += particle.vy }, normalizeVelocity : function(particle, pIndex, particles){ if (particle.vx - particle.vxInitial > 1) { particle.vx -= 0.05; } else if (particle.vx - particle.vxInitial < -1) { particle.vx += 0.05; } if (particle.vy - particle.vyInitial > 1) { particle.vy -= 0.05; } else if (particle.vx - particle.vxInitial < -1) { particle.vy += 0.05; } }, explode : function(particle, pIndex, particles) { if (particle.isBottomOut()) { particles.splice(pIndex, 1); } else { particle.x += particle.vx; particle.y += particle.vy; particle.vy += 0.1; } if (particles.length === 0){ particles.motion.removeMotion("explode"); this.allowMenu = true; } } } Jarvis.prototype.collision = { boundingBox: function(particle, pIndex, particles){ if (particle.y > (this.HEIGHT - particle.radius) || particle.y < particle.radius) { particle.vy *= -1; } if(particle.x > (this.WIDTH - particle.radius) || particle.x < particle.radius) { particle.vx *= -1; } }, boundingBoxGravity: function(particle, pIndex, particles){ // TODO: FIX GRAVITY TO WORK PROPERLY IN COMBINATION WITH FX AND MOTION if (particle.y > (this.HEIGHT - particle.radius) || particle.y < particle.radius) { particle.vy *= -1; particle.vy += 5; } if(particle.x > (this.WIDTH - particle.radius) || particle.x < particle.radius) { particle.vx *= -1; particle.vx += 5; } }, infinity: function(particle, pIndex, particles){ if (particle.x > this.WIDTH){ particle.x = 0; } if (particle.x < 0){ particle.x = this.WIDTH; } if (particle.y > this.HEIGHT){ particle.y = 0; } if (particle.y < 0) { particle.y = this.HEIGHT; } } } Jarvis.prototype.fx = { link : function(particle, pIndex, particles){ for(var j = pIndex + 1; j < particles.length; j++) { var p1 = particle; var p2 = particles[j]; var particleDistance = getDistance(p1, p2); if (particleDistance <= this.particleMinLinkDistance) { this.backgroundCtx.beginPath(); this.backgroundCtx.strokeStyle = "rgba("+p1.red+", "+p1.green+", "+p1.blue+","+ (p1.opacity - particleDistance / this.particleMinLinkDistance) +")"; this.backgroundCtx.moveTo(p1.x, p1.y); this.backgroundCtx.lineTo(p2.x, p2.y); this.backgroundCtx.stroke(); this.backgroundCtx.closePath(); } } }, shake : function(particle, pIndex, particles){ if (particle.xInitial - particle.x >= this.shakeAreaThreshold){ particle.xOper = (randBtwn(this.shakeFactorMin, this.shakeFactorMax) * 2) % (this.WIDTH); } else if (particle.xInitial - particle.x <= -this.shakeAreaThreshold) { particle.xOper = (randBtwn(-this.shakeFactorMax, this.shakeFactorMin) * 2) % (this.WIDTH); } if (particle.yInitial - particle.y >= this.shakeAreaThreshold){ particle.yOper = (randBtwn(this.shakeFactorMin, this.shakeFactorMax) * 2) % (this.HEIGHT); } else if (particle.yInitial - particle.y <= -this.shakeAreaThreshold) { particle.yOper = (randBtwn(-this.shakeFactorMax, this.shakeFactorMin) * 2) % (this.HEIGHT); } particle.x += particle.xOper; particle.y += particle.yOper; }, radialWave : function(particle, pIndex, particles){ var distance = getDistance(particle, this.center); if (particle.radius >= (this.dim * 0.0085)) { particle.radiusOper = -0.02; } else if (particle.radius <= 1) { particle.radiusOper = 0.02; } particle.radius += particle.radiusOper * particle.radius; }, responsive : function(particle, pIndex, particles){ var newPosX = (this.logoParticles.logoOffsetX + this.logoParticles.particleRadius) + (this.logoParticles.particleDistance + this.logoParticles.particleRadius) * particle.arrPos.x; var newPosY = (this.logoParticles.logoOffsetY + this.logoParticles.particleRadius) + (this.logoParticles.particleDistance + this.logoParticles.particleRadius) * particle.arrPos.y; if (particle.xInitial !== newPosX || particle.yInitial !== newPosY){ particle.xInitial = newPosX; particle.yInitial = newPosY; particle.x = particle.xInitial; particle.y = particle.yInitial; } }, motionDetect : function(particle, pIndex, particles){ var isClose = false; var distance = null; for (var i = 0; i < this.touches.length; i++) { var t = this.touches[i]; var point = { x : t.clientX, y : t.clientY } var d = getDistance(point, particle); if (d <= this.blackhole) { isClose = true; if (d <= distance || distance === null) { distance = d; } } } if (isClose){ if (particle.radius < (this.dim * 0.0085)) { particle.radius += 0.25; } if (particle.green >= 0 && particle.blue >= 0) { particle.green -= 10; particle.blue -= 10; } } else { if (particle.radius > particle.initialRadius) { particle.radius -= 0.25; } if (particle.green <= 255 && particle.blue <= 255) { particle.green += 10; particle.blue += 10; } } }, reverseBlackhole : function(particle, pIndex, particles){ for (var i = 0; i < this.touches.length; i++) { var t = this.touches[i]; var point = { x : t.clientX, y : t.clientY } var distance = getDistance(point, particle); if (distance <= this.blackhole){ var diff = getPointsDifference(point, particle); particle.vx += -diff.x / distance; particle.vy += -diff.y / distance; } } } }

Además, en caso de que alguien se pregunte tengo 3 capas de lienzo y agregaré la función de renderizado de partículas y la función borrar para todas las capas de lienzo

  1. Fondo que dibuja un degradado radial y partículas de pantalla completa

  2. Lienzo de menú

  3. Selectores de superposición de botón de menú (mostrar qué menú está activo, etc.)

Jarvis.prototype.backgroundDraw = function() { // particles var that = this; this.logoParticles.forEach(function(particle, i){ particle.draw(that.backgroundCtx); that.logoParticles.motion.forEach(function(motionType, motionIndex){ that.motion[motionType].call(that, particle, i, that.logoParticles, "foregroundParticles"); }); that.logoParticles.fx.forEach(function(fxType, fxIndex){ that.fx[fxType].call(that, particle, i, that.logoParticles, "foregroundParticles"); }); that.logoParticles.collision.forEach(function(collisionType, collisionIndex){ that.collision[collisionType].call(that, particle, i, that.logoParticles, "foregroundParticles"); }); }); this.backgroundParticles.forEach(function(particle, i){ particle.draw(that.backgroundCtx); that.backgroundParticles.motion.forEach(function(motionType, motionIndex){ that.motion[motionType].call(that, particle, i, that.backgroundParticles, "backgroundParticles"); }); that.backgroundParticles.fx.forEach(function(fxType, fxIndex){ that.fx[fxType].call(that, particle, i, that.backgroundParticles, "backgroundParticles"); }); that.backgroundParticles.collision.forEach(function(collisionType, collisionIndex){ that.collision[collisionType].call(that, particle, i, that.backgroundParticles, "backgroundParticles"); }); }); } Jarvis.prototype.clearCanvas = function() { switch(this.background.type){ case "radial_gradient": this.setBackgroundRadialGradient(this.background.color1, this.background.color2); break; case "plane_color": this.setBackgroundColor(this.background.red, this.background.green, this.background.blue, this.background.opacity); break; default: this.setBackgroundColor(142, 214, 255, 1); } this.foregroundCtx.clearRect(this.clearStartX, this.clearStartY, this.clearDistance, this.clearDistance); this.middlegroundCtx.clearRect(this.clearStartX, this.clearStartY, this.clearDistance, this.clearDistance); } Jarvis.prototype.mainLoop = function() { this.clearCanvas(); this.backgroundDraw(); this.drawMenu(); window.requestAnimFrame(this.mainLoop.bind(this)); }

Cualquier otro consejo de optimización será muy apreciado. He leído un par de artículos pero no estoy seguro de cómo optimizar aún más este código.


Creo que puede encontrar que el soporte de webworker es aproximadamente igual al soporte de WebGL:

Soporte WebGL: http://caniuse.com/#search=webgl
Soporte de WebWorker: http://caniuse.com/#search=webworker

En la superficie, pueden parecer diferentes, pero en realidad no lo son. Lo único que obtendrá es soporte de IE10 temporalmente. IE11 ya ha superado IE10 en cuota de mercado y la división seguirá creciendo. Lo único que se debe tener en cuenta es que la compatibilidad con webgl también parece estar basada en controladores de tarjeta gráfica actualizados.

Por supuesto, no conozco sus necesidades específicas, así que quizás esto no funcione.

Opciones

¿Esperar lo? 200 elementos en la pantalla son lentos?

Haz menos en lona y haz las cosas geniales en WebGL

Muchas bibliotecas hacen esto. Canvas debe ser útil y un poco genial. WebGL generalmente tiene todas las características de partículas geniales.

WebWorkers

Es probable que necesite utilizar una biblioteca diferida o crear un sistema que se de cuenta cuando todos los trabajadores web hayan finalizado y tengan un conjunto de hilos de trabajo.

Algunas advertencias:

  1. No puede acceder a nada desde su aplicación principal, y debe comunicarse a través de eventos
  2. Los objetos que pasan a través de un webworker se copian y no se comparten
  3. Configurar webworkers sin una secuencia de comandos por separado puede requerir un poco de investigación

Rumor no confirmado: He oído que hay una cantidad limitada de datos que puede pasar a través de los mensajes del trabajador web. Debe probar esto ya que parece directamente aplicable a su caso de uso.


No sé qué mejora importante puede hacer aquí, excepto cambiar a una tecnología que utiliza la aceleración de hardware.

Espero que esto ayude un poco, aunque como se afirma en los comentarios de la pregunta, WebGL sería realmente más rápido. Si no sabes por dónde empezar, aquí hay una buena: webglacademy

Todavía vi algunas pequeñas cosillas:

radialWave : function(particle, pIndex, particles){ // As you don''t use distance here remove this line // it''s a really greedy calculus that involves square root // always avoid if you don''t have to use it // var distance = getDistance(particle, this.center); if (particle.radius >= (this.dim * 0.0085)) { particle.radiusOper = -0.02; } else if (particle.radius <= 1) { particle.radiusOper = 0.02; } particle.radius += particle.radiusOper * particle.radius; },

Otra pequeña cosita

Jarvis.prototype.backgroundDraw = function() { // particles var that = this; // Declare callbacks outside of forEach calls // it will save you a function declaration each time you loop // Do this for logo particles var logoMotionCallback = function(motionType, motionIndex){ // Another improvement may be to use a direct function that does not use ''this'' // and instead pass this with a parameter called currentParticle for example // call and apply are known to be pretty heavy -> see if you can avoid this that.motion[motionType].call(that, particle, i, that.logoParticles, "foregroundParticles"); }; var logoFxCallback = function(fxType, fxIndex){ that.fx[fxType].call(that, particle, i, that.logoParticles, "foregroundParticles"); }; var logoCollisionCallback = function(collisionType, collisionIndex){ that.collision[collisionType].call(that, particle, i, that.logoParticles, "foregroundParticles"); }; this.logoParticles.forEach(function(particle, i){ particle.draw(that.backgroundCtx); that.logoParticles.motion.forEach(motionCallback); that.logoParticles.fx.forEach(fxCallback); that.logoParticles.collision.forEach(collisionCallback); }); // Now do the same for background particles var bgMotionCallback = function(motionType, motionIndex){ that.motion[motionType].call(that, particle, i, that.backgroundParticles, "backgroundParticles"); }; var bgFxCallback = function(fxType, fxIndex){ that.fx[fxType].call(that, particle, i, that.backgroundParticles, "backgroundParticles"); }; var bgCollisionCallback = function(collisionType, collisionIndex){ that.collision[collisionType].call(that, particle, i, that.backgroundParticles, "backgroundParticles"); }; this.backgroundParticles.forEach(function(particle, i){ particle.draw(that.backgroundCtx); that.backgroundParticles.motion.forEach(bgMotionCallback); that.backgroundParticles.fx.forEach(bgFxCallback); that.backgroundParticles.collision.forEach(bgCollisionCallback); }); }


Puede usar FabricJS Canvas Library. FabricJS admite de forma predeterminada la interactividad, cuando crea un objeto nuevo (círculo, rectángulo y etc.) puede manipularlo con el mouse o la pantalla táctil.

var canvas = new fabric.Canvas(''c''); var rect = new fabric.Rect({ width: 10, height: 20, left: 100, top: 100, fill: ''yellow'', angle: 30 }); canvas.add(rect);

Mira, trabajamos allí de manera orientada a objetos.


Si está buscando acelerar el código, aquí hay algunas micro-optimizaciones:

  • for(var i = 0, l = bla.length; i < l; i++) { ... } lugar de bla.forEach(...)
  • reducir el uso de devolución de llamada. Cosas simples en línea.
  • la comparación con la distancia es lenta debido al SQRT. radius <= distance es lenta, radius*radius <= distanceSquared es rápida.
  • calcular la distancia se hace calculando la diferencia. Ahora realiza 2 llamadas a función, primero para obtener la distancia, luego para obtener la diferencia. aquí hay una pequeña reescritura: sin llamadas a funciones, sin cálculos innecesarios.

reverseBlackhole : function(particle, pIndex, particles) { var blackholeSqr = this.blackhole * this.blackhole, touches = this.touches, fnSqrt = Math.sqrt, t, diffX, diffY, dstSqr; for (var i = 0, l = touches.length; i < l; i++) { t = touches[i]; diffX = particle.x - t.clientX; diffY = particle.y - t.clientY; distSqr = (diffX * diffX + diffY * diffY); // comparing distance without a SQRT needed if (dstSqr <= blackholeSqr){ var dist = Math.sqrt(dstSqr); particle.vx -= diffX / dist; particle.vy -= diffY / dist; } } }

Para acelerar el dibujo (o hacerlo menos lento durante el dibujo):

  • Separa tus cálculos de tu dibujo
  • Solo solicite un redibujado después de haber actualizado sus cálculos

Y para toda la animación:

  • this.backgroundParticles.forEach(..) : en caso de 200 partículas, esto hará
    • 200 partículas veces ( this.backgroundParticles.forEach( )
      • 200 partículas ( that.backgroundParticles.motion.forEach )
      • 200 partículas ( that.backgroundParticles.fx.forEach )
      • 200 partículas ( that.backgroundParticles.collision.forEach )
  • lo mismo vale para this.foregroundparticles.forEach(..) de this.foregroundparticles.forEach(..)
  • digamos que tenemos 200 de fondo y 100 de primer plano, es decir (200 * 200 * 3) + (100 * 100 * 3) devoluciones de llamada, que son 150000 devoluciones de llamada, por tick. Y aún no hemos calculado una sola cosa, tampoco hemos mostrado nada.
  • Ejecútelo a 60 fps y tiene hasta 9 millones de devoluciones de llamada por segundo. Creo que puedes detectar un problema aquí.
  • Deja de pasar cadenas en esas llamadas a funciones también.

Para obtener este mayor rendimiento, elimine las cosas de OOP y elija el feo código de espagueti, solo donde tenga sentido.

La detección de colisiones se puede optimizar al no probar cada partícula una contra la otra. Solo busca quadtrees. No es tan difícil de implementar, y lo básico se puede utilizar para encontrar una solución personalizada.

Ya que estás haciendo bastante matemáticas vectoriales, prueba la biblioteca glmatrix . Optimizado vectorial de matemáticas :-)