niklasvh / html2canvas

Screenshots with JavaScript
https://html2canvas.hertzen.com/
MIT License
30.17k stars 4.75k forks source link

Offscreen Canvas API - freeing up the Main Thread #2880

Open Awendel opened 2 years ago

Awendel commented 2 years ago

One big downside to this library is that it is blocking the main thread and can be quite slow when rendering a large amount of HTML.

What would be ideal is to expose an OffscreenCanvas API where for example if all styles are inline (not relying on a stylesheet), one just sends the entire outerHTML as a string blob to the web worker, that then uses an XML Parser etc. to convert it into Canvas rendering Calls.

This way a huge amount of HTML could be rendered to Canvas without blocking the MainThread at all.

niklasvh commented 2 years ago

The library still depends on DOM layout and style resolution/computed values, which need to be done on the main thread.

Awendel commented 2 years ago

Sure sure, but all of that data can be quite easily string encoded in a pre-step.

My suggested Pipeline:

MAIN-THREAD • get HTML Elements (.outerHTML etc) • get Computed Style & Text encode --> just convert to array or key value Map • send to Web Worker

WEB-WORKER • use XML parser to make sense of .outerHTML • translate the Computed Style Array / Map into ContextCommands • apply to OffscreenCanvas

This new structure might introduce some additional internal steps, but would in the end lead to much better performance

EDIT: Unless the majority of latency comes from getting the computedStyle etc instead of the Canvas Calls, haven't run a benchmark yet around that...

BarbWire-1 commented 5 months ago

I thought I where drawing an image from document.body to an offscreenCanvas to use it as "colorPicker", but logging in a mutationObserver showed, that in older versions html2canvas appends and removes an iFrame on each mutation, newer version a DIV and an IMG. Is there any way to directly write to the offscreenCanvas? As I rerender on every mutation it can become rather "tricky"

This is my current class:


export class BodyToCanvas {
    constructor() {
        this.isRendering = false;
        this.offscreenCanvas = document.createElement('canvas');
        this.offscreenCtx = this.offscreenCanvas.getContext('2d', {
            willReadFrequently: true,
        });

    }

    renderBodyToCanvas() {
        if (this.isRendering) return;
                this.isRendering = true;

        html2canvas(document.body, {
            backgroundColor: 'transparent',
            scale: 1,
            logging: false, // Disable logging
               }).then((bodyCanvas) => {

            const { width, height } = bodyCanvas;
            this.offscreenCanvas.width = width;
            this.offscreenCanvas.height = height;
            this.offscreenCtx.drawImage(bodyCanvas, 0, 0);
            this.isRendering = false;
        });
    }
}