Rebote y estrangulamiento explicados a través de ejemplos | Programar Plus

La siguiente es una publicación invitada de David Corbacho, ingeniero de front-end en Londres. Hemos abordado este tema antes, pero esta vez, David va a llevar los conceptos a casa a través de demostraciones interactivas que dejan las cosas muy claras.

Rebote y acelerador son dos técnicas similares (¡pero diferentes!) para controlar cuántas veces permitimos que una función se ejecute a lo largo del tiempo.

Tener una versión antirrebote o acelerada de nuestra función es especialmente útil cuando estamos adjuntando la función a un evento DOM. ¿Por qué? Porque nos estamos dando una capa de control entre el evento y la ejecución de la función. Recuerde, no controlamos la frecuencia con la que se emitirán esos eventos DOM. Puede variar

Por ejemplo, hablemos de eventos de desplazamiento. Vea este ejemplo:

Vea el contador de eventos de Pen Scroll de Corbacho (@dcorb) en CodePen.

Cuando se desplaza con un trackpad, una rueda de desplazamiento o simplemente arrastrando una barra de desplazamiento, puede activar fácilmente 30 eventos por segundo. Pero desplazarse lentamente (intercambiar) en un teléfono inteligente podría desencadenar hasta 100 eventos por segundo durante mis pruebas. ¿Está preparado su controlador de desplazamiento para esta tasa de ejecución?

En 2011, apareció un problema en el sitio web de Twitter: cuando se desplazaba hacia abajo en su feed de Twitter, se volvía lento y no respondía. John Resig publicó una publicación de blog sobre el problema en el que se explicaba lo mala que es la idea de adjuntar funciones costosas directamente al scroll evento.

La solución sugerida por John (en ese momento, hace cinco años) era un ciclo que se ejecutaba cada 250 ms, fuera del onScroll event. De esa forma, el controlador no está acoplado al evento. Con esta sencilla técnica, podemos evitar arruinar la experiencia del usuario.

En estos días, hay formas un poco más sofisticadas de manejar los eventos. Permítanme presentarles Debounce, Throttle y requestAnimationFrame. También veremos los casos de uso coincidentes.

Rebote

La técnica Debounce nos permite “agrupar” múltiples llamadas secuenciales en una sola.

Imagina que estás en un ascensor. Las puertas comienzan a cerrarse y, de repente, otra persona intenta subir. El ascensor no comienza su función de cambiar de piso, las puertas vuelven a abrirse. Ahora vuelve a pasar con otra persona. El ascensor está retrasando su función (moviendo pisos), pero optimizando sus recursos.

Pruébelo usted mismo. Haga clic o mueva el mouse sobre el botón:

Vea el Pen Debounce. Seguimiento de Corbacho (@dcorb) en CodePen.

Puede ver cómo los eventos rápidos secuenciales están representados por un solo evento antirrebote. Pero si los eventos se desencadenan con grandes lagunas, la eliminación de rebotes no ocurre.

Vanguardia (o “inmediata”)

Puede resultarle irritante que el evento de eliminación de rebotes espere antes de activar la ejecución de la función, hasta que los eventos dejen de ocurrir tan rápidamente. ¿Por qué no activar la ejecución de la función inmediatamente, para que se comporte exactamente como el controlador original sin rebote? Pero no vuelva a disparar hasta que haya una pausa en las llamadas rápidas.

¡Puedes hacerlo! Aquí hay un ejemplo con el leading bandera en:

Ejemplo de antirrebote “adelantado”.

En underscore.js, la opción se llama immediate en vez de leading

Pruébelo usted mismo:

Vea el Pen Debounce. Liderado por Corbacho (@dcorb) en CodePen.

Implementaciones de rebote

La primera vez que vi implementado el debounce en JavaScript fue en 2009 en esta publicación de John Hann (quien también acuñó el término).

Poco después de eso, Ben Alman creó un complemento jQuery (que ya no se mantiene), y un año después, Jeremy Ashkenas lo agregó a underscore.js. Más tarde se agregó a Lodash, una alternativa directa al subrayado.

Las 3 implementaciones son un poco diferentes internamente, pero su interfaz es casi idéntica.

Hubo un momento en que el subrayado adoptó la implementación de rebote / aceleración de Lodash, después de que descubrí un error en el _.debounce en 2013. Desde entonces, ambas implementaciones se han ido distanciando.

Lodash ha agregado más funciones a su _.debounce y _.throttle funciones. El original immediate la bandera fue reemplazada por leading y trailing opciones. Puede elegir uno o ambos. Por defecto, solo el trailing el borde está habilitado.

El nuevo maxWait La opción (solo en Lodash por el momento) no se trata en este artículo pero puede ser muy útil. En realidad, la función del acelerador se define mediante _.debounce con maxWait, como puede ver en el código fuente de Lodash.

Ejemplos de rebote

Ejemplo de cambio de tamaño

Al cambiar el tamaño de una ventana del navegador (escritorio), pueden emitir muchos resize eventos mientras arrastra el controlador de cambio de tamaño.

Compruébelo usted mismo en esta demostración:

Vea el ejemplo de evento de cambio de tamaño de rebote de lápiz de Corbacho (@dcorb) en CodePen.

Como puede ver, estamos usando el valor predeterminado trailing para el evento de cambio de tamaño, porque solo estamos interesados ​​en el valor final, después de que el usuario deja de cambiar el tamaño del navegador.

pulsación de tecla en formulario de autocompletado con solicitud Ajax

¿Por qué enviar solicitudes Ajax al servidor cada 50 ms, cuando el usuario todavía está escribiendo? _.debounce puede ayudarnos, evitando trabajo extra, y solo enviar la solicitud cuando el usuario deja de escribir.

Aquí, no tendría sentido tener el leading bandera encendida. Queremos esperar hasta la última letra escrita.

Vea el ejemplo de pulsaciones de teclas para eliminar el rebote de la pluma de Corbacho (@dcorb) en CodePen.

Un caso de uso similar sería esperar hasta que el usuario deje de escribir antes de validar su entrada. Tipo de mensajes “Tu contraseña es demasiado corta”.

Cómo usar la función antirrebote y aceleración y errores comunes

Puede ser tentador crear su propia función antirrebote / aceleración, o copiarla de alguna publicación de blog aleatoria. Mi recomendación es usar guión bajo o Lodash directamente. Si solo necesitas el _.debounce y _.throttle funciones, puede utilizar el constructor personalizado de Lodash para generar una biblioteca personalizada minificada de 2 KB. Constrúyelo con este sencillo comando:

npm i -g lodash-cli
lodash include = debounce, throttle

Dicho esto, la mayoría usa la forma modular `lodash / throttle` y` lodash / debounce` o los paquetes `lodash.throttle` y` lodash.debounce` con webpack / browserify / rollup.

Un error común es llamar al _.debounce funcionar más de una vez:

// WRONG
$(window).on('scroll', function() {
   _.debounce(doSomething, 300); 
});

// RIGHT
$(window).on('scroll', _.debounce(doSomething, 200));

Crear una variable para la función antirrebote nos permitirá llamar al método privado debounced_version.cancel(), disponible en lodash y underscore.js, en caso de que lo necesite.

var debounced_version = _.debounce(doSomething, 200);
$(window).on('scroll', debounced_version);

// If you need it
debounced_version.cancel();

Acelerador

Mediante el uso _.throttle, no permitimos que nuestra función se ejecute más de una vez cada X milisegundos.

La principal diferencia entre esto y el antirrebote es que el acelerador garantiza la ejecución de la función con regularidad, al menos cada X milisegundos.

De la misma manera que el antirrebote, la técnica del acelerador está cubierta por el complemento de Ben, underscore.js y lodash.

Ejemplos de limitación

Desplazamiento infinito

Un ejemplo bastante común. El usuario se desplaza hacia abajo en su página de desplazamiento infinito. Debe verificar qué tan lejos del fondo está el usuario. Si el usuario está cerca de la parte inferior, deberíamos solicitar a través de Ajax más contenido y agregarlo a la página.

Aquí nuestro amado _.debounce no sería útil. Solo se activará cuando el usuario deje de desplazarse … y tengamos que empezar a buscar el contenido antes de que el usuario llegue al final.
Con _.throttle podemos garantizar que estamos comprobando constantemente qué tan lejos estamos del fondo.

Vea el desplazamiento de Pen Infinite acelerado por Corbacho (@dcorb) en CodePen.

requestAnimationFrame (rAF)

requestAnimationFrame es otra forma de limitar la velocidad de ejecución de una función.

Puede pensarse como un _.throttle(dosomething, 16). Pero con una fidelidad mucho mayor, ya que es una API nativa del navegador que apunta a una mayor precisión.

Podemos utilizar la API rAF, como alternativa a la función de aceleración, considerando estos pros / contras:

Pros

  • Tiene como objetivo 60 fps (fotogramas de 16 ms), pero internamente decidirá el mejor momento para programar el renderizado.
  • API bastante simple y estándar, que no cambiará en el futuro. Menos mantenimiento.

Contras

  • El inicio / cancelación de rAF es nuestra responsabilidad, a diferencia de .debounce o .throttle, donde se gestiona internamente.
  • Si la pestaña del navegador no está activa, no se ejecutará. Aunque para los eventos de desplazamiento, mouse o teclado, esto no importa.
  • Aunque todos los navegadores modernos ofrecen rAF, todavía no es compatible con IE9, Opera Mini y Android antiguo. Todavía hoy se necesitaría un polyfill.
  • rAF no es compatible con node.js, por lo que no puede usarlo en el servidor para acelerar los eventos del sistema de archivos.

Como regla general, usaría requestAnimationFrame si su función de JavaScript es “pintar” o animar directamente propiedades, utilícela en todo lo que implique volver a calcular las posiciones de los elementos.

Para hacer solicitudes Ajax, o decidir si agregar / eliminar una clase (que podría activar una animación CSS), consideraría _.debounce o _.throttle, donde puede configurar velocidades de ejecución más bajas (200 ms, por ejemplo, en lugar de 16 ms)

Si cree que rAF podría implementarse dentro de subrayado o lodash, ambos han rechazado la idea, ya que es un caso de uso especializado y es bastante fácil llamarlo directamente.

Ejemplos de rAF

Cubriré solo este ejemplo para usar el marco requestAnimation en el pergamino, inspirado en el artículo de Paul Lewis, donde explica paso a paso la lógica de este ejemplo.

Lo puse uno al lado del otro para compararlo _.throttle a 16ms. Ofrece un rendimiento similar, pero probablemente rAF le dará mejores resultados en escenarios más complejos.

Vea la solicitud de comparación de Pen ScrollAnimationFrame vs throttle de Corbacho (@dcorb) en CodePen.

Un ejemplo más avanzado donde he visto esta técnica es en la biblioteca headroom.js, donde la lógica está desacoplada y envuelta dentro de un objeto.

Conclusión

Utilice antirrebote, acelerador y requestAnimationFrame para optimizar sus controladores de eventos. Cada técnica es ligeramente diferente, pero las tres son útiles y se complementan entre sí.

En resumen:

  • rebote: Agrupar una ráfaga repentina de eventos (como pulsaciones de teclas) en uno solo.
  • acelerador: Garantizando un flujo constante de ejecuciones cada X milisegundos. Como comprobar cada 200 ms la posición de desplazamiento para activar una animación CSS.
  • requestAnimationFrame: una alternativa de aceleración. Cuando su función recalcula y renderiza elementos en la pantalla y desea garantizar cambios o animaciones suaves. Nota: no es compatible con IE9.

(Visited 6 times, 1 visits today)