响应式画布动画,无需调整大小事件

窗口调整大小事件可以响应用户输入设备的移动而触发。当你调整画布大小时,它会自动清除,你将被迫重新呈现内容。对于动画,你可以通过 requestAnimationFrame 调用的主循环函数每帧执行此操作,这样可以最大限度地保持渲染与显示硬件同步。

调整大小事件的问题在于,当使用鼠标调整窗口大小时,事件的触发速度可能比浏览器的标准 60fps 速率快许多倍。当调整大小事件退出画布时,缓冲区被呈现给与显示设备不同步的 DOM,这可能导致剪切和其他负面影响。还有很多不必要的内存分配和释放可能会在 GC 清理一段时间后进一步影响动画。

Debounced resize 事件

处理调整大小事件的高射击率的常用方法是去除调整大小事件。

 // 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 ... }

上面的示例将画布的大小调整延迟到调整大小事件后的 100ms。如果在此时触发了进一步的调整大小事件,则取消现有的调整大小超时并调度新的调整大小。这有效地消耗了大多数调整大小事件。

它仍然存在一些问题,最值得注意的是调整大小和查看调整大小的画布之间的延迟。减少去抖时间可以改善这一点,但调整大小仍然与显示设备不同步。你还将动画主循环渲染到不合适的画布。

更多代码可以减少问题! 更多代码也会产生自己的新问题。

简单而且最好调整大小

尝试了许多不同的方法来平滑画布的大小调整,从荒谬的复杂到忽略问题(谁还在乎呢?)我又回到了一个可靠的朋友身上。

K.I.S.S 是大多数程序员应该知道的(Keep It Simple Stupid),而且事实证明最好的解决方案是最简单的的。

只需在主动画循环中调整画布大小即可。它与显示设备保持同步,没有不必要的渲染,并且在保持全帧速率的同时资源管理是最小的。你也不需要向窗口或任何其他调整大小函数添加 resize 事件。

通过检查画布大小是否与窗口大小匹配,可以在通常清除画布的位置添加调整大小。如果没有调整大小。

// 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);
}