html5-canvas Animazioni su tela reattive senza ridimensionare gli eventi.


Esempio

Gli eventi di ridimensionamento della finestra possono essere attivati ​​in risposta al movimento del dispositivo di input dell'utente. Quando ridimensioni un quadro, questo viene automaticamente cancellato e sei costretto a ri-renderizzare il contenuto. Per le animazioni fai questo ogni fotogramma tramite la funzione del ciclo principale chiamata da requestAnimationFrame che fa del suo meglio per mantenere il rendering sincronizzato con l'hardware del display.

Il problema con l'evento di ridimensionamento è che quando il mouse viene utilizzato per ridimensionare la finestra, gli eventi possono essere attivati ​​molte volte più velocemente rispetto alla velocità standard di 60 fps del browser. Quando l'evento di ridimensionamento termina, il buffer di back nell'area di disegno viene presentato al DOM non sincronizzato con il dispositivo di visualizzazione, che può causare cesoie e altri effetti negativi. C'è anche un sacco di allocazione e rilascio della memoria inutili che possono ulteriormente incidere sull'animazione quando GC si ripulisce qualche tempo dopo.


Evento di ridimensionamento rimbalzato

Un modo comune per gestire le alte velocità di attivazione dell'evento di ridimensionamento è quello di eliminare l'evento di ridimensionamento.

 // Assume canvas is in scope
 addEventListener.("resize", debouncedResize );

 // debounce timeout handle
 var debounceTimeoutHandle;

 // The debounce time in ms (1/1000th second)
 const DEBOUNCE_TIME = 100; 

 // Resize function 
 function debouncedResize () { 
     clearTimeout(debounceTimeoutHandle);  // Clears any pending debounce events

     // Schedule a canvas resize 
     debounceTimeoutHandle = setTimeout(resizeCanvas, DEBOUNCE_TIME);
 }

 // canvas resize function
 function resizeCanvas () { ... resize and redraw ... }

L'esempio precedente ritarda il ridimensionamento della tela fino a 100ms dopo l'evento di ridimensionamento. Se in quel momento vengono attivati ​​ulteriori eventi di ridimensionamento, il timeout di ridimensionamento esistente viene annullato e ne viene pianificato uno nuovo. Questo effettivamente consuma la maggior parte degli eventi di ridimensionamento.

Ha ancora alcuni problemi, il più notevole è il ritardo tra il ridimensionamento e la visualizzazione della tela ridimensionata. La riduzione del tempo di rimbalzo migliora questo ma il ridimensionamento non è ancora sincronizzato con il dispositivo di visualizzazione. Hai anche il rendering dell'anello principale dell'animazione su una tela inadeguata.

Più codice può ridurre i problemi! Più codice crea anche i suoi nuovi problemi.


Semplice e il migliore ridimensionamento

Avendo provato molti modi diversi per appianare il ridimensionamento della tela, dall'assurdamente complesso, per ignorare il problema (chi se ne importa comunque?) Mi sono ricollegato ad un fidato amico.

KISS è qualcosa di cui la maggior parte dei programmatori dovrebbe essere a conoscenza (( K eep I t S imple S tupid) Lo stupido si riferisce a me per non averci pensato anni fa. ) E si scopre che la soluzione migliore è la più semplice di tutte.

Ridimensiona la tela dall'interno del ciclo dell'animazione principale. Rimane sincronizzato con il dispositivo di visualizzazione, non c'è rendering inutile e la gestione delle risorse è al minimo possibile mantenendo la frequenza fotogrammi completa. Né è necessario aggiungere un evento di ridimensionamento alla finestra o altre funzioni di ridimensionamento aggiuntive.

Si aggiunge il ridimensionamento in cui normalmente si cancella la tela controllando se le dimensioni della tela corrispondono alle dimensioni della finestra. Se non lo ridimensiona.

// Assumes canvas element is in scope as canvas

// Standard main loop function callback from requestAnimationFrame
function mainLoop(time) {

    // Check if the canvas size matches the window size
    if (canvas.width !== innerWidth || canvas.height !== innerHeight) {
        canvas.width = innerWidth;    // resize canvas
        canvas.height = innerHeight;  // also clears the canvas
    } else {
        ctx.clearRect(0, 0, canvas.width, canvas.height); // clear if not resized
    }

    // Animation code as normal.



    requestAnimationFrame(mainLoop);
}