diff --git a/js/canvas/canvas_renderer.js b/js/canvas/canvas_renderer.js index 4ffadc9..46da33b 100644 --- a/js/canvas/canvas_renderer.js +++ b/js/canvas/canvas_renderer.js @@ -327,8 +327,22 @@ export class CanvasRenderer { this.canvas.canvas.width = this.canvas.offscreenCanvas.width; this.canvas.canvas.height = this.canvas.offscreenCanvas.height; } - this.canvas.ctx.clearRect(0, 0, this.canvas.canvas.width, this.canvas.canvas.height); - this.canvas.ctx.drawImage(this.canvas.offscreenCanvas, 0, 0); + if (canReuseDragFrame) { + // The offscreen canvas already contains the unchanged pixels. A + // partial blit keeps the visible canvas from copying the entire + // viewport on every drag frame. + this.canvas.ctx.save(); + this.canvas.ctx.globalCompositeOperation = 'source-over'; + this.canvas.ctx.globalAlpha = 1; + dirtyRects.forEach(rect => { + this.canvas.ctx.drawImage(this.canvas.offscreenCanvas, rect.x, rect.y, rect.width, rect.height, rect.x, rect.y, rect.width, rect.height); + }); + this.canvas.ctx.restore(); + } + else { + this.canvas.ctx.clearRect(0, 0, this.canvas.canvas.width, this.canvas.canvas.height); + this.canvas.ctx.drawImage(this.canvas.offscreenCanvas, 0, 0); + } // Ensure overlay canvases are in DOM and properly sized this.addOverlayToDOM(); this.updateOverlaySize(); diff --git a/src/canvas/canvas_renderer.ts b/src/canvas/canvas_renderer.ts index 2b51b63..648f439 100644 --- a/src/canvas/canvas_renderer.ts +++ b/src/canvas/canvas_renderer.ts @@ -425,8 +425,31 @@ export class CanvasRenderer { this.canvas.canvas.width = this.canvas.offscreenCanvas.width; this.canvas.canvas.height = this.canvas.offscreenCanvas.height; } - this.canvas.ctx.clearRect(0, 0, this.canvas.canvas.width, this.canvas.canvas.height); - this.canvas.ctx.drawImage(this.canvas.offscreenCanvas, 0, 0); + if (canReuseDragFrame) { + // The offscreen canvas already contains the unchanged pixels. A + // partial blit keeps the visible canvas from copying the entire + // viewport on every drag frame. + this.canvas.ctx.save(); + this.canvas.ctx.globalCompositeOperation = 'source-over'; + this.canvas.ctx.globalAlpha = 1; + dirtyRects.forEach(rect => { + this.canvas.ctx.drawImage( + this.canvas.offscreenCanvas, + rect.x, + rect.y, + rect.width, + rect.height, + rect.x, + rect.y, + rect.width, + rect.height + ); + }); + this.canvas.ctx.restore(); + } else { + this.canvas.ctx.clearRect(0, 0, this.canvas.canvas.width, this.canvas.canvas.height); + this.canvas.ctx.drawImage(this.canvas.offscreenCanvas, 0, 0); + } // Ensure overlay canvases are in DOM and properly sized this.addOverlayToDOM(); diff --git a/tests/test_drag_scene_cache_characterization.mjs b/tests/test_drag_scene_cache_characterization.mjs index 18cde6d..1ef4c63 100644 --- a/tests/test_drag_scene_cache_characterization.mjs +++ b/tests/test_drag_scene_cache_characterization.mjs @@ -100,6 +100,7 @@ test('drag scene cache groups source-over layers and replays non-normal modes', assert.match(canvasRendererSource, /canReuseDragFrame/); assert.match(canvasRendererSource, /getDragDirtyRects/); assert.match(canvasRendererSource, /dirtyRects\.forEach/); + assert.match(canvasRendererSource, /this\.canvas\.offscreenCanvas,\s*rect\.x/); assert.match(canvasRendererSource, /ctx\.clip\(\)/); assert.match(canvasLayersSource, /getOrCreateDragLayerPreview/); assert.match(canvasLayersSource, /prepareDragLayerPreviews/);