limitación de eventos de desplazamiento, requestAnimationFrame, optimización del rendimiento

1. evento de desplazamiento

Debido a que los eventos de desplazamiento pueden dispararse a alta velocidad, los controladores de eventos no deben realizar operaciones computacionalmente costosas, como las modificaciones del DOM. En su lugar, se recomienda utilizar requestAnimationFrame()u optimizarsetTimeout() los eventos, como se muestra a continuación.CustomEvent

  // 使用 requestAnimationFrame 节流
  let lastKnownScrollPosition = 0;
  let ticking = false;

  function doSomething(scrollPosition) {
    
    
    // Do something with the scroll position
  }

  document.addEventListener(
    "scroll",
    function (e) {
    
    
      lastKnownScrollPosition = window.scrollY;

      if (!ticking) {
    
    
        // 使用 requestAnimationFrame 节流
        window.requestAnimationFrame(function () {
    
    
          doSomething(lastKnownScrollPosition);
          ticking = false;
        });

        ticking = true;
      }
    },
    {
    
    
      // 使用 passive 可改善的滚屏性能
      passive: true,
    }
  );

Tenga en cuenta que los eventos de entrada y los cuadros de animación se activan aproximadamente a la misma velocidad, por lo que la optimización anterior generalmente no es necesaria.


2. Use requestAnimationFrame para acelerar

1. Frecuencia de actualización de la pantalla

La velocidad a la que se actualiza la imagen en la pantalla (la cantidad de veces que aparece la imagen en la pantalla por segundo), y su unidad es Hertz (Hz).

60 Hz: la pantalla también actualiza constantemente la imagen en la pantalla a una frecuencia de 60 veces por segundo, y el intervalo entre cada vez es de 16,7 ms (1000/60≈16,7).

2. Principios de la animación

La esencia de la animación es permitir que las personas vean el efecto visual de la imagen que se refresca y provoca cambios. Este cambio debe transicionarse de manera coherente y fluida.

3. establecer el tiempo de espera

La animación realizada mediante el uso de seTimeout puede congelarse y temblar en algunas máquinas de gama baja. razón:

  • El tiempo de ejecución de setTimeout no es determinista. En Javascript, la tarea setTimeout se coloca en una cola asíncrona, y solo después de ejecutar la tarea en el subproceso principal, verificará si la tarea en la cola debe ejecutarse, por lo que el tiempo real de ejecución de setTimeout es generalmente más largo que su puesta más tarde.
  • La frecuencia de actualización se ve afectada por la resolución y el tamaño de la pantalla, por lo que la frecuencia de actualización de la pantalla de diferentes dispositivos puede ser diferente, y setTimeout solo puede establecer un intervalo de tiempo fijo, que no es necesariamente el mismo que el tiempo de actualización de la pantalla.

4. solicitudAnimationFrame

  • requestAnimationFrame aprovecha al máximo el mecanismo de actualización de la pantalla y el sistema determina el tiempo de ejecución de la función de devolución de llamada, lo que ahorra recursos del sistema, mejora el rendimiento del sistema y mejora los efectos visuales.
  • El ritmo de requestAnimationFrame sigue el ritmo de actualización del sistema. Puede garantizar que la función de devolución de llamada se ejecute solo una vez en cada intervalo de actualización de la pantalla, de modo que no cause pérdida de fotogramas ni que la animación se congele.
  • requestAnimationFrame le dice al navegador que desea realizar una animación y le pide que programe un redibujado de la página en el siguiente cuadro de animación.
  • requestAnimationFrame reunirá todas las operaciones DOM en cada cuadro y las completará en un redibujado o reflujo, y el intervalo de tiempo de redibujado o reflujo sigue de cerca la frecuencia de actualización del navegador.
  • La pantalla tiene una frecuencia de actualización fija (60 Hz o 75 Hz), es decir, solo puede volver a dibujar 60 o 75 veces por segundo como máximo. La idea básica de requestAnimationFrame es mantenerse sincronizado con esta frecuencia de actualización y usar esta frecuencia de actualización para volver a dibujar la página.
  • Una vez que la página no esté en la pestaña actual del navegador, dejará de actualizarse automáticamente. Esto ahorra CPU, GPU y energía.


5. Las ventajas de requestAnimationFrame

  • Ahorro de energía de la CPU : cuando la animación se implementa usando setTimeout, cuando la página está oculta o minimizada, setTimeout aún ejecuta la tarea de animación en segundo plano. Dado que la página es invisible o no está disponible en este momento, no tiene sentido actualizar la animación, lo cual es una pérdida de recursos de la CPU. . El requestAnimationFrame es completamente diferente. Cuando el procesamiento de la página no está activado, el sistema también suspenderá la tarea de actualización de pantalla de la página, por lo que el requestAnimationFrame que sigue al sistema también dejará de procesarse. Cuando la página esté activada, la animación comience desde la última vez Continúe ejecutándose donde permanece, ahorrando efectivamente la sobrecarga de la CPU.
  • Limitación de funciones : en eventos de alta frecuencia (cambio de tamaño, desplazamiento, etc.), para evitar múltiples ejecuciones de funciones dentro de un intervalo de actualización, use requestAnimationFrame para asegurarse de que la función solo se ejecute una vez dentro de cada intervalo de actualización, lo que puede garantizar un rendimiento sin problemas. , y puede ahorrar mejor la sobrecarga de ejecución de la función. No tiene sentido cuando la función se ejecuta varias veces dentro de un intervalo de actualización, porque la pantalla se actualiza cada 16,7 ms y los dibujos múltiples no se reflejarán en la pantalla.

6. Degradación elegante de requestAnimationFrame

Porque requestAnimationFrame todavía tiene problemas de compatibilidad, y los diferentes navegadores necesitan tener diferentes prefijos. Por lo tanto, es necesario encapsular requestAnimationFrame de manera elegante, dar prioridad al uso de funciones avanzadas y luego retroceder según la situación de los diferentes navegadores, hasta que solo se pueda usar setTimeout.

// 开源组件库 ngx-tethys里的requestAnimationFrame优雅降级
const availablePrefixes = ['moz', 'ms', 'webkit'];

function requestAnimationFramePolyfill(): typeof requestAnimationFrame {
    
    
    let lastTime = 0;
    return function(callback: FrameRequestCallback): number {
    
    
        const currTime = new Date().getTime();
        const timeToCall = Math.max(0, 16 - (currTime - lastTime));
        const id = setTimeout(() => {
    
    
            callback(currTime + timeToCall);
        }, timeToCall) as any; // setTimeout type warn
        lastTime = currTime + timeToCall;
        return id;
    };
}

function getRequestAnimationFrame(): typeof requestAnimationFrame {
    
    
    if (typeof window === 'undefined') {
    
    
        return () => 0;
    }
    if (window.requestAnimationFrame) {
    
    
        // https://github.com/vuejs/vue/issues/4465
        return window.requestAnimationFrame.bind(window);
    }

    const prefix = availablePrefixes.filter(key => `${
      
      key}RequestAnimationFrame` in window)[0];

    return prefix ? (window as any)[`${
      
      prefix}RequestAnimationFrame`] : requestAnimationFramePolyfill();
}

export function cancelRequestAnimationFrame(id: number): any {
    
    
    if (typeof window === 'undefined') {
    
    
        return null;
    }
    if (window.cancelAnimationFrame) {
    
    
        return window.cancelAnimationFrame(id);
    }
    const prefix = availablePrefixes.filter(
        key => `${
      
      key}CancelAnimationFrame` in window || `${
      
      key}CancelRequestAnimationFrame` in window
    )[0];

    return prefix
        ? ((window as any)[`${
      
      prefix}CancelAnimationFrame`] || (window as any)[`${
      
      prefix}CancelRequestAnimationFrame`])
              // @ts-ignore
              .call(this, id)
        : clearTimeout(id);
}

export const reqAnimFrame = getRequestAnimationFrame();


3. Use pasivo para mejorar el rendimiento de desplazamiento

Algunos eventos en el lado móvil, como touchstart, touchmove, touchend, touchcancel, etc., si el comportamiento predeterminado está bloqueado en estos eventos, se prohibirá el desplazamiento o el zoom de la página.


El navegador no puede saber de antemano si un oyente prohibirá el comportamiento predeterminado y no ejecutará el comportamiento predeterminado hasta que se ejecute el oyente. La ejecución del oyente lleva mucho tiempo. Si tarda 2 segundos antes de event.preventDefault(), esto hará que la página se congele.


Para mejorar la experiencia de desplazamiento en este escenario, necesitamos un parámetro para decirle al navegador que no habrá event.preventDefault() en mi detector de eventos, y puede desplazarse todo lo que quiera sin esperar a que termine el detector. ejecutando Así que hay un atributo pasivo. Para ser compatible con el useCapture anterior, el último parámetro se cambió a opciones de objeto.


Por lo tanto, al enlazar eventos táctiles y de desplazamiento relacionados con el terminal móvil, utilice tanto como sea posible { passive: true }para mejorar el rendimiento y la experiencia y evitar bloqueos de página.


Sin embargo, no todos los navegadores admiten la función pasiva. Los navegadores que no admiten la función pasiva usarán el último parámetro como useCapture, por lo que se necesita este código sutil para determinar si la función pasiva es compatible:

// Test via a getter in the options object to see 
// if the passive property is accessed
var supportsPassive = false;
try {
    
    
  var opts = Object.defineProperty({
    
    }, 'passive', {
    
    
    get: function() {
    
    
      supportsPassive = true;
    }
  });
  window.addEventListener("test", null, opts);
} catch (e) {
    
    }


// Use our detect's results. 
// passive applied if supported, capture will be false either way.
elem.addEventListener(
  'touchstart',
  fn,
  supportsPassive ? {
    
     passive: true } : false
);

Al Object.definePropertyconfigurar un descriptor de acceso de obtención pasivo y agregar un evento de prueba, se llamará al descriptor de acceso de obtención cuando el navegador lo admita, y se establece supportPassive en el descriptor de acceso de obtención.


En el marco Angular, Angular CDK ya se ha proporcionado en @angular/cdk/platformel módulo normalizePassiveListenerOptions({passive: true})para que podamos resolver problemas de compatibilidad. El código central es el siguiente:

/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

/** Cached result of whether the user's browser supports passive event listeners. */
let supportsPassiveEvents: boolean;

/**
 * Checks whether the user's browser supports passive event listeners.
 * See: https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
 */
export function supportsPassiveEventListeners(): boolean {
    
    
  if (supportsPassiveEvents == null && typeof window !== 'undefined') {
    
    
    try {
    
    
      window.addEventListener(
        'test',
        null!,
        Object.defineProperty({
    
    }, 'passive', {
    
    
          get: () => (supportsPassiveEvents = true),
        }),
      );
   } finally {
    
    
      supportsPassiveEvents = supportsPassiveEvents || false;
    }
  }

  return supportsPassiveEvents;
}

/**
 * Normalizes an `AddEventListener` object to something that can be passed
 * to `addEventListener` on any browser, no matter whether it supports the
 * `options` parameter.
 * @param options Object to be normalized.
 */
export function normalizePassiveListenerOptions(
  options: AddEventListenerOptions,
): AddEventListenerOptions | boolean {
    
    
  return supportsPassiveEventListeners() ? options : !!options.capture;
}

Agregue problemas relacionados para vincular eventos para admitir parámetros pasivos: https://github.com/angular/angular/issues/8866

Supongo que te gusta

Origin blog.csdn.net/Kate_sicheng/article/details/125712876
Recomendado
Clasificación