tools quitar modo devices developer dev debuggear debug chrome javascript optimization google-chrome-devtools hittest

javascript - quitar - Optimización de pruebas de detección nativas de elementos DOM(Chrome)



https://chrome:inspect (2)

Interesante, que pointer-events: none no tiene ningún efecto. Pero si lo piensas bien, tiene sentido, ya que los elementos con esa bandera todavía ocultan los eventos de puntero de otros elementos, por lo que la prueba de éxito debe llevarse a cabo de todos modos.

Lo que puede hacer es colocar una superposición sobre el contenido crítico y responder a los eventos del mouse en esa superposición, deje que su código decida qué hacer con él.

Esto funciona porque una vez que el algoritmo de hittest ha encontrado un hit, y supongo que lo hace hacia abajo, el índice z se detiene.

Con superposición

// ================================================ // Increase or decrease this value for testing: var NUMBER_OF_OBJECTS = 40000; // Wether to use the overlay or the container directly var USE_OVERLAY = true; // ================================================ var overlay = document.getElementById("overlay"); var container = document.getElementById("container"); var contents = document.getElementById("contents"); for (var i = 0; i < NUMBER_OF_OBJECTS; i++) { var node = document.createElement("div"); node.innerHtml = i; node.className = "node"; node.style.top = Math.abs(Math.random() * 2000) + "px"; node.style.left = Math.abs(Math.random() * 2000) + "px"; contents.appendChild(node); } var posX = 100; var posY = 100; var previousX = null; var previousY = null; var mousedownHandler = function (e) { window.onmousemove = globalMousemoveHandler; window.onmouseup = globalMouseupHandler; previousX = e.clientX; previousY = e.clientY; } var globalMousemoveHandler = function (e) { posX += e.clientX - previousX; posY += e.clientY - previousY; previousX = e.clientX; previousY = e.clientY; contents.style.transform = "translate3d(" + posX + "px, " + posY + "px, 0)"; } var globalMouseupHandler = function (e) { window.onmousemove = null; window.onmouseup = null; previousX = null; previousY = null; } if(USE_OVERLAY){ overlay.onmousedown = mousedownHandler; }else{ overlay.style.display = ''none''; container.onmousedown = mousedownHandler; } contents.style.transform = "translate3d(" + posX + "px, " + posY + "px, 0)";

#overlay{ position: absolute; top: 0; left: 0; height: 400px; width: 800px; opacity: 0; z-index: 100; cursor: -webkit-grab; cursor: -moz-grab; cursor: grab; -moz-user-select: none; -ms-user-select: none; -webkit-user-select: none; user-select: none; } #container { height: 400px; width: 800px; background-color: #ccc; overflow: hidden; } #container:active { cursor: move; cursor: -webkit-grabbing; cursor: -moz-grabbing; cursor: grabbing; } .node { position: absolute; height: 20px; width: 20px; background-color: red; border-radius: 10px; pointer-events: none; }

<div id="overlay"></div> <div id="container"> <div id="contents"></div> </div>

Sin superposición

// ================================================ // Increase or decrease this value for testing: var NUMBER_OF_OBJECTS = 40000; // Wether to use the overlay or the container directly var USE_OVERLAY = false; // ================================================ var overlay = document.getElementById("overlay"); var container = document.getElementById("container"); var contents = document.getElementById("contents"); for (var i = 0; i < NUMBER_OF_OBJECTS; i++) { var node = document.createElement("div"); node.innerHtml = i; node.className = "node"; node.style.top = Math.abs(Math.random() * 2000) + "px"; node.style.left = Math.abs(Math.random() * 2000) + "px"; contents.appendChild(node); } var posX = 100; var posY = 100; var previousX = null; var previousY = null; var mousedownHandler = function (e) { window.onmousemove = globalMousemoveHandler; window.onmouseup = globalMouseupHandler; previousX = e.clientX; previousY = e.clientY; } var globalMousemoveHandler = function (e) { posX += e.clientX - previousX; posY += e.clientY - previousY; previousX = e.clientX; previousY = e.clientY; contents.style.transform = "translate3d(" + posX + "px, " + posY + "px, 0)"; } var globalMouseupHandler = function (e) { window.onmousemove = null; window.onmouseup = null; previousX = null; previousY = null; } if(USE_OVERLAY){ overlay.onmousedown = mousedownHandler; }else{ overlay.style.display = ''none''; container.onmousedown = mousedownHandler; } contents.style.transform = "translate3d(" + posX + "px, " + posY + "px, 0)";

#overlay{ position: absolute; top: 0; left: 0; height: 400px; width: 800px; opacity: 0; z-index: 100; cursor: -webkit-grab; cursor: -moz-grab; cursor: grab; -moz-user-select: none; -ms-user-select: none; -webkit-user-select: none; user-select: none; } #container { height: 400px; width: 800px; background-color: #ccc; overflow: hidden; } #container:active { cursor: move; cursor: -webkit-grabbing; cursor: -moz-grabbing; cursor: grabbing; } .node { position: absolute; height: 20px; width: 20px; background-color: red; border-radius: 10px; pointer-events: none; }

<div id="overlay"></div> <div id="container"> <div id="contents"></div> </div>

Tengo una aplicación de JavaScript altamente optimizada, un editor de gráficos altamente interactivo. Ahora comencé a perfilarlo (usando las herramientas de desarrollo de Chrome) con grandes cantidades de datos (miles de formas en el gráfico), y me encuentro con un cuello de botella de rendimiento antes inusual, Hit Test .

| Self Time | Total Time | Activity | |-----------------|-----------------|---------------------| | 3579 ms (67.5%) | 3579 ms (67.5%) | Rendering | | 3455 ms (65.2%) | 3455 ms (65.2%) | Hit Test | <- this one | 78 ms (1.5%) | 78 ms (1.5%) | Update Layer Tree | | 40 ms (0.8%) | 40 ms (0.8%) | Recalculate Style | | 1343 ms (25.3%) | 1343 ms (25.3%) | Scripting | | 378 ms (7.1%) | 378 ms (7.1%) | Painting |

Esto ocupa un 65% de todo (!) , Quedando un cuello de botella de monstruo en mi base de código. Sé que este es el proceso de rastrear el objeto bajo el puntero , y tengo mis inútiles ideas sobre cómo podría optimizarse (usar menos elementos, usar menos eventos de ratón, etc.).

Contexto: El perfil de rendimiento anterior muestra una función de "panorámica de pantalla" en mi aplicación, donde se puede mover el contenido de la pantalla arrastrando el área vacía. Esto da como resultado que se muevan muchos objetos, optimizados moviendo su contenedor en lugar de cada objeto individualmente. Hice una demostración.

Antes de lanzarme a esto, quería buscar los principios generales de la optimización de las pruebas de detección de golpes (los buenos viejos artículos de blog "No sh * t, Sherlock" ), así como si existen trucos para mejorar el rendimiento en este extremo (como utilizando translate3d para habilitar el procesamiento de GPU).

Intenté hacer preguntas como js optimize hit test , pero los resultados están llenos de artículos de programación de gráficos y ejemplos de implementación manual. ¡Es como si la comunidad JS no hubiera oído hablar de esto antes! Incluso la guía cromo devtools carece de esta área.

Así que aquí estoy, orgullosamente terminado con mi investigación, preguntándome: ¿cómo puedo obtener la optimización de las pruebas de impacto nativas en JavaScript?

Preparé una demostración que demuestra el cuello de botella de rendimiento, aunque no es exactamente lo mismo que mi aplicación real, y obviamente los números también variarán según el dispositivo. Para ver el cuello de botella:

  1. Ve a la pestaña Línea de tiempo en Chrome (o el equivalente de tu navegador)
  2. Comience a grabar, luego recorra la demo como un loco
  3. Deje de grabar y verifique los resultados

Un resumen de todas las optimizaciones significativas que ya he hecho en esta área:

  • mover un solo contenedor en la pantalla en lugar de mover miles de elementos individualmente
  • usando la transform: translate3d para mover el contenedor
  • Sincronización v del movimiento del mouse a la frecuencia de actualización de la pantalla
  • eliminando todos los posibles elementos innecesarios de "envoltura" y "fijador"
  • usando pointer-events: none en formas - sin efecto

Notas adicionales:

  • el cuello de botella existe con y sin aceleración GPU
  • las pruebas solo se realizaron en Chrome, la última
  • el DOM se procesa utilizando ReactJS, pero el mismo problema es observable sin él, como se ve en la demostración vinculada

Uno de los problemas es que está moviendo CADA elemento individual dentro de su contenedor, no importa si tiene aceleración de GPU o no, el cuello de la botella está recalculando su nueva posición, es decir, el campo del procesador.

Mi sugerencia aquí es segmentar los contenedores, por lo tanto, puede mover varios paneles de forma individual, reduciendo la carga, esto se denomina cálculo de fase amplia, es decir, solo mover lo que se debe mover. Si sacó algo de la pantalla, ¿por qué debería moverlo?

Comience por hacer en lugar de uno, 16 contenedores, tendrá que hacer algunos cálculos aquí para descubrir cuál de estos paneles se muestra. Luego, cuando ocurre un evento de mouse, mueve solo esos paneles y deja los que no se muestran donde están. Esto debería reducir en gran medida el tiempo utilizado para moverlos.

+------+------+------+------+ | SS|SS | | | | SS|SS | | | +------+------+------+------+ | | | | | | | | | | +------+------+------+------+ | | | | | | | | | | +------+------+------+------+ | | | | | | | | | | +------+------+------+------+

En este ejemplo, tenemos 16 paneles, de los cuales, 2 se muestran (marcados con S para Pantalla). Cuando un usuario hace una búsqueda, verifique el cuadro delimitador de la "pantalla", descubra qué paneles pertenecen a la "pantalla", mueva solo esos paneles. Esto es teóricamente infinitamente escalable.

Lamentablemente, me falta tiempo para escribir el código que muestra el pensamiento, pero espero que esto te ayude.

¡Aclamaciones!