javascript jquery iscroll

javascript - Mejora del rendimiento de iScroll en una tabla grande



jquery (8)

Estoy actualizando el encabezado de una tabla y las posiciones de su primera columna según la manera en que el usuario se desplaza para mantenerlos alineados.

El problema que estoy experimentando es que tan pronto como mis conjuntos de datos se vuelven lo suficientemente grandes, el desplazamiento se vuelve más y más entrecortado / menos suave.

El código relevante se encuentra en la parte inferior del violín:

iScroll.on(''scroll'', function(){ var pos = $(''#scroller'').position(); $(''#pos'').text(''pos.left='' + pos.left + '' pos.top='' + pos.top); // code to hold first row and first column $(''#scroller th:nth-child(1)'').css({top: (-pos.top), left: (-pos.left), position:''relative''}); $(''#scroller th:nth-child(n+1)'').css({top: (-pos.top), position:''relative''}); // this seems to be the most expensive operation: $(''#scroller td:nth-child(1)'').css({left: (-pos.left), position:''relative''}); });

Sé que esto se puede escribir mucho más eficaz almacenando en caché los elementos y así sucesivamente. Por ejemplo, he intentado guardar los elementos en una matriz y actualizar su posición de una manera más "vanilla":

headerElements[i].style.left = left + ''px''; // etc...

No importa lo rápido que haga la devolución de llamada, todavía no estoy contento con el resultado. ¿Tienes alguna sugerencia?

https://jsfiddle.net/0qv1kjac/16/


¡Sí! Aquí hay algunas mejoras al código de su JS Fiddle. Puede ver mis ediciones en: https://jsfiddle.net/briankueck/u63maywa/

Algunas mejoras sugeridas son:

  1. Cambio de position:relative valores position:relative en la capa JS a position:fixed en la capa CSS.
  2. Acortando las cadenas de DOM de jQuery, para que el código no comience en el elemento raíz y recorra todo el camino del dominio con cada $ búsqueda. El scroller es ahora el elemento raíz. Todo usa .find() fuera de ese elemento, lo que crea árboles más cortos y jQuery puede atravesar esas ramas más rápido.
  3. Mover el código de registro fuera del DOM y en la consola.log. He agregado un interruptor de depuración para desactivarlo, ya que está buscando el desplazamiento más rápido en la mesa. Si se ejecuta lo suficientemente rápido para usted, siempre puede volver a habilitarlo para verlo en el JSFiddle. Si realmente necesita ver eso en el iPhone, entonces se puede agregar al DOM. Aunque, probablemente no sea necesario ver los valores de posición superior e izquierda en el iPhone.
  4. Elimine todos los valores extraños de $, que no están asignados al objeto jQuery. Algo así como $ scroller se confunde con $, ya que esta última es la biblioteca jQuery, pero la primera no lo es.
  5. Al cambiar a la sintaxis de ES6, utilizando let lugar de var hará que su código se vea más moderno.
  6. Hay un nuevo cálculo a la izquierda en la etiqueta <th> , que querrá ver.
  7. El detector de eventos iScroll se ha limpiado. Con position:fixed , las etiquetas superiores <th> solo necesitan tener la propiedad top aplicada a ellas. Las etiquetas <td> la left solo necesitan tener la propiedad left aplicada a ellas. La esquina <th> debe tener aplicadas las propiedades top e left .
  8. Elimine todo lo que no sea necesario, como las etiquetas HTML extrañas que se utilizaron para fines de registro.
  9. Si realmente quieres .style.left= -pos.left + ''px''; más vainilla, cambia los métodos .style.left= -pos.left + ''px''; .css() para el .style.left= -pos.left + ''px''; real .style.left= -pos.left + ''px''; y .style.top= -pos.top + ''px''; Propiedades en el código JS.

Intente usar una herramienta de diferencias como WinMerge o Beyond Compare para comparar el código de su versión con lo que hay en mis ediciones, para que pueda ver fácilmente las diferencias.

Con suerte, esto hará que el desplazamiento sea más suave, ya que el evento de desplazamiento no tiene que procesar nada que no tenga que hacer ... como 5 búsquedas de DOM completas en lugar de 3 búsquedas de árbol corto.

¡Disfrutar! :)

HTML:

<body> <div id="wrapper"> <table id="scroller"> <thead> </thead> <tbody> </tbody> </table> </div> </body>

CSS:

/* ... only the relevant bits ... */ thead th { background-color: #99a; min-width: 120px; height: 32px; border: 1px solid #222; position: fixed; /* New */ z-index: 9; } thead th:nth-child(1) {/*first cell in the header*/ border-left: 1px solid #222; /* New: Border fix */ border-right: 2px solid #222; /* New: Border fix */ position: fixed; /* New */ display: block; /*seperates the first cell in the header from the header*/ background-color: #88b; z-index: 10; }

JS:

// main code let debug = false; $(function(){ let scroller = $(''#scroller''); let top = $(''<tr/>''); for (var i = 0; i < 100; i++) { let left = (i === 0) ? 0 : 1; top.append(''<th style="left:'' + ((123*i)+left) + ''px;">''+ Math.random().toString(36).substring(7) +''</th>''); } scroller.find(''thead'').append(top); for (let i = 0; i < 100; i++) { let row = $(''<tr/>''); for (let j = 0; j < 100; j++) { row.append(''<td>''+ Math.random().toString(36).substring(7) +''</td>''); } scroller.find(''tbody'').append(row); } if (debug) console.log(''initialize iscroll''); let iScroll = null; try { iScroll = new IScroll(''#wrapper'', { interactiveScrollbars: true, scrollbars: true, scrollX: true, probeType: 3, useTransition:false, bounce:false }); } catch(e) { if (debug) console.error(e.name + ":" + e.message + "/n" + e.stack); } if (debug) console.log(''initialized''); iScroll.on(''scroll'', function(){ let pos = scroller.position(); if (debug) console.log(''pos.left='' + pos.left + '' pos.top='' + pos.top); // code to hold first row and first column scroller.find(''th'').css({top:-pos.top}); // Top Row scroller.find(''th:nth-child(1)'').css({left:-pos.left}); // Corner scroller.find(''td:nth-child(1)'').css({left:-pos.left}); // 1st Left Column }); });


¿Es necesario que crees tu propio scroller? ¿Por qué no solo diseña los datos en HTML / CSS y solo usa el atributo de overflow ? JavaScript necesita trabajar en su habilidad para ajustar las tablas de cuadros. Antes estaba usando su jFiddle y funcionó bien con el controlador de desbordamiento nativo.


Encontré esto en el manual. Probablemente no es lo que quieres escuchar, pero así es como es:

IScroll es una clase que debe iniciarse para cada área de desplazamiento. No hay límite para la cantidad de iScrolls que puede tener en cada página, si no es el impuesto por la CPU / memoria del dispositivo. Trate de mantener el DOM tan simple como sea posible. iScroll utiliza la capa de composición de hardware, pero hay un límite para los elementos que el hardware puede manejar.


Está experimentando un desplazamiento entrecortado / no suave porque el evento de scroll se dispara a un ritmo muy alto .

Y cada vez que se dispara , está ajustando la posición de muchos elementos : esto es costoso y, además, hasta que el navegador haya completado el repintado, no responde (en este caso, el desplazamiento entrecortado).

Veo dos opciones:

Opción número uno: mostrar solo el subconjunto visible de todo el conjunto de datos (esto ya se ha sugerido en otra respuesta, así que no voy a ir más allá)

Opción número dos (más fácil)

Primero, deje que las animaciones en la left y en la top cambios css ocurran a través de las transiciones . Esto es más eficiente, no bloquea y a menudo permite que el navegador aproveche el gpu.

Luego, en lugar de ajustar repetidamente la left y la top , hazlo de vez en cuando; por ejemplo 0,5 segundos. Esto se realiza mediante la función ScrollWorker() (ver código a continuación) que se recupera a través de un setTimeout() .

Finalmente, use la devolución de llamada invocada por el evento de desplazamiento para mantener #scroller posición #scroller (almacenada en una variable).

// Position of the `#scroller` element // (I used two globals that may pollute the global namespace // that piece of code is just for explanation purpose) var oldPosition, newPosition; // Use transition to perform animations // You may set this in the stylesheet $(''th'').css( { ''transition'': ''left 0.5s, top 0.5s'' } ); $(''td'').css( { ''transition'': ''left 0.5s, top 0.5s'' } ); // Save the initial position newPosition = $(''#scroller'').position(); oldPosition = $(''#scroller'').position(); // Upon scroll just set the position value iScroll.on(''scroll'', function() { newPosition = $(''#scroller'').position(); } ); // Start the scroll worker ScrollWorker(); function ScrollWorker() { // Adjust the layout if position changed (your original code) if( newPosition.left != oldPosition.left || newPosition.top != oldPosition.top ) { $(''#scroller th:nth-child(1)'').css({top: (-newPosition.top), left: (-newPosition.left), position:''relative''}); $(''#scroller th:nth-child(n+1)'').css({top: (-newPosition.top), position:''relative''}); $(''#scroller td:nth-child(1)'').css({left: (-newPosition.left), position:''relative''}); // Update the stored position oldPosition.left = newPosition.left; oldPosition.top = newPosition.top; // Let animation complete then check again // You may adjust the timer value // The timer value must be higher or equal the transition time setTimeout( ScrollWorker, 500 ); } else { // No changes // Check again after just 0.1secs setTimeout( ScrollWorker, 100 ); } }

Aquí está el Fiddle

Puse el ritmo de trabajo y el tiempo de transición en 0,5 segundos . Puede ajustar el valor con una temporización más alta o más baja, eventualmente de forma dinámica en función del número de elementos en la tabla.


Esta no será la respuesta que estás buscando, pero aquí tienes mis 2 centavos de todos modos.

La animación de Javascript (especialmente teniendo en cuenta la cantidad de DOM que debe renderizar) nunca será tan suave como la desea. Incluso si pudiera hacerlo sin problemas en su máquina, es probable que varíe drásticamente en otras personas (PC más antiguas, navegadores, etc.).

Me gustaría ver 2 opciones si tuviera que abordar esto yo mismo.

  1. Ve a la vieja escuela y agrega una barra de desplazamiento horizontal y vertical. Sé que no es una solución bonita pero funcionaría bien.

  2. Solo renderiza una cierta cantidad de filas y descártalas fuera de la pantalla. Esto podría ser un poco complicado, pero en esencia, sería decir, 10 filas. Una vez que el usuario se desplaza a un punto donde debería estar allí el 11, rinda ese y elimine el 1er. Los metes dentro y fuera según sea necesario.

En términos del JS real (usted mencionó poner elementos en una matriz), eso no va a ayudar. La interferencia real se debe a que el navegador tiene que representar tantos elementos en primer lugar.


La razón por la que se está produciendo una degradación del rendimiento es que el controlador de eventos de desplazamiento se dispara una y otra vez, en lugar de esperar un intervalo razonable e imperceptible.

La captura de pantalla muestra lo que sucedió cuando rastreé cuántas veces se activó el controlador de eventos, mientras se desplazaba por unos pocos segundos. El controlador de eventos de gran peso computacional fue disparado más de 600 veces !!! ¡Esto es más de 60 veces por segundo!

Puede parecer contrario a la intuición, pero reducir la frecuencia con la que se actualiza la tabla aumentará enormemente los tiempos de respuesta percibidos. Si su usuario se desplaza por una fracción de segundo, aproximadamente 150 milisegundos, y la tabla se actualiza diez veces, congelando la pantalla durante el desplazamiento, el resultado neto es mucho peor que si la tabla se actualizara solo tres veces y se moviera con fluidez en lugar de congelarse . Solo se desperdicia la grabación del procesador para actualizarse más veces de las que el navegador puede manejar sin congelarse.

Entonces, ¿cómo se hace un controlador de eventos que se dispare a una frecuencia máxima, por ejemplo, 25 veces por segundo, incluso si se dispara con mucha más frecuencia, como 100 veces por segundo?

La forma ingenua de hacerlo es ejecutar un evento setInterval. Eso es mejor, pero horriblemente ineficiente también. Hay una mejor manera de hacerlo, configurando un controlador de eventos retrasado y eliminándolo en las invocaciones posteriores antes de volver a configurarlo, hasta que haya transcurrido el intervalo de tiempo mínimo. De esta manera, solo se ejecuta con la frecuencia máxima deseada. Este es un caso importante por el que se inventó el método `` clearInterval ''''.

Aquí está el código de trabajo en vivo:

https://jsfiddle.net/pgjvf7pb/7/

Nota: cuando se actualiza continuamente de esta manera, la columna del encabezado puede aparecer fuera de posición.

Aconsejo realizar la actualización solo cuando el desplazamiento se haya detenido durante unos 25 ms o menos, en lugar de hacerlo de forma continua. De esta manera, al usuario le parece que la columna del encabezado se calcula dinámicamente y se fija en su lugar, porque aparece instantáneamente después del desplazamiento en lugar de desplazarse con los datos.

https://jsfiddle.net/5vcqv7nq/2/

La lógica es así:

variables fuera de su controlador de eventos

// stores the scrolling operation for a tiny delay to prevent redundancy var fresh; // stores time of last scrolling refresh var lastfresh = new Date();

Operaciones dentro de su controlador de eventos.

// clears redundant scrolling operations before they are applied if (fresh) clearTimeout(fresh); var x = function() { // stores new time of scrolling refresh lastfresh = new Date(); // perform scrolling update operations here... }; // refresh instantly if it is more than 50ms out of date if (new Date() - lastfresh > 50) x(); // otherwise, pause for half of that time to avoid wasted runs else fresh = setTimeout(x, 25);

Demostración: https://jsfiddle.net/pgjvf7pb/7/

Una vez más, le recomiendo que elimine la línea de código que actualiza los datos al instante, y la otra condición después de eso, y simplemente use una línea

fresh = setTimeout(x, 25);

Esto aparecerá para calcular instantáneamente la columna del encabezado en el momento en que finaliza el desplazamiento y guarda aún más operaciones. Mi segundo enlace a JS Fiddle muestra cómo se ve esto aquí: https://jsfiddle.net/5vcqv7nq/2/


Para poder manejar grandes cantidades de datos, necesita virtualización de datos. Aunque tiene algunas restricciones.

Primero debe decidir el tamaño de un puerto de vista. Digamos que desea representar 10 elementos en una fila y 20 elementos en la columna. Serían 10x20 elementos entonces. En tu violín es div con envoltorio de identificación.

Entonces necesitas saber la cantidad total de datos que tienes. Desde tu violín serían 100x100 ítems. Y, también es necesario saber la altura y el ancho de un elemento (celda). Tomemos 40x120 (en px).

Entonces div # wrapper es un puerto de vista, debería tener un tamaño fijo como 10x20 elementos. Entonces necesitas configurar el ancho y la altura correctos para la tabla . La altura de la tabla sería igual a la cantidad total de datos en la columna, incluida la altura cabeza por elemento. El ancho para la tabla sería la cantidad total de artículos en una fila por ancho de artículo.

Una vez que configure estos, el envoltorio div # recibirá desplazamientos horizontales y verticales. Ahora puedes desplazarte hacia la izquierda y hacia abajo, pero solo será un espacio vacío. Sin embargo, este espacio vacío puede contener la cantidad exacta de datos que tiene.

Luego, debe tomar los datos de desplazamiento hacia la izquierda y hacia arriba (posición), que vienen en píxeles y normalizarlos a la cantidad de elementos, para que pueda saber no cuántos píxeles ha desplazado, sino cuántos elementos ha desplazado (o filas). si nos desplazamos de arriba a abajo).

Se podría hacer por división de píxeles desplazados en la altura del elemento. Por ejemplo, se desplazó a la izquierda 80px, que son 2 elementos. Significa que estos elementos deben ser invisibles porque los has desplazado. Entonces, sabe que ha desplazado los 2 elementos anteriores y sabe que debería ver 10 elementos seguidos. Eso significa que tomas tu matriz de datos que tiene datos por fila con 100 artículos, y la divides así:

var visibleItems = rowData.slice(itemsScrolled, itemsScrolled + 10);

Le proporcionará elementos que deberían estar visibles en la vista en la posición de desplazamiento actual. Una vez que tenga estos elementos, debe construir html y adjuntarlo a la tabla.

También en cada evento de desplazamiento, debe establecer la posición superior e izquierda de tbody y thead para que se muevan con desplazamiento, de lo contrario tendrá sus datos, pero estará en (0; 0) dentro de una ventana gráfica.

De todos modos, el código habla miles de palabras, así que aquí está el violín: https://jsfiddle.net/Ldfjrg81/9/

Tenga en cuenta que este enfoque requiere que las alturas y los anchos sean precisos, de lo contrario funcionará incorrectamente. Además, si tiene artículos de diferentes tamaños, esto también debe tenerse en cuenta, así que mejor si tiene artículos de tamaños fijos e iguales. En jsfiddle, comenté el código que obliga a la primera columna a permanecer en su lugar, pero puede representarlo por separado.

Es una buena solución ceñirse a alguna biblioteca como se sugiere en los comentarios, ya que maneja muchos casos para usted.

Puede hacer que el renderizado sea aún más rápido si usa react.js o vue.js


Sólo tiene que utilizar ClusterizeJS ! Puede manejar cientos de miles de filas y fue construido exactamente para este propósito.

¿Cómo funciona, te preguntas?

La idea principal es no contaminar DOM con todas las etiquetas usadas. En lugar de eso, divide la lista en grupos, luego muestra elementos para la posición de desplazamiento actual y agrega filas adicionales en la parte superior e inferior de la lista para emular la altura completa de la tabla, de modo que el navegador muestra la barra de desplazamiento como para la lista completa