Is there a way to trigger standard zoom (not headless)
Asked Answered
J

2

14

I am running puppeteer with headless mode off in order to automate and remotely control a visible Chromium browser on another computer.

Is there a way to trigger or emulate zooming on the browser just like in the UI menus or ctrl +/crtl - commands?

Injecting CSS or using the various documented scale commands does not fully replicate this as, for example, elements defined using vh/vw units do not get adjusted.


My current solution

Using viewport scale in Emulation.setDeviceMetricsOverride works well for zooming out but it seems to be resizing a raster of the page rather than rendering at the target size, resulting in blurry text when zooming in.

Adjusting the viewport size and using Emulation.setPageScaleFactor works well for zooming in however a pageScaleFactor of less than 1 seems to be ignored in my testing.

An issue with both of these solutions is that it requires knowing the width/height of the browser window in advance and relies on that not changing, rather than having a fluid viewport. I'm also not sure what other features of the standard browser zoom I am missing.

My code for zooming is now:


async applyFrameZoom(page, zoom) {
    // page is a puppeteer.Page instance
    // zoom is an integer percentage

    const session = await page.target().createCDPSession();

    let window = await session.send('Browser.getWindowForTarget', {
        targetId: page.target()._targetId
    });

    let width = window.bounds.width;
    let height = window.bounds.height;

    if (!zoom || zoom === 100) {
        // Unset any zoom
        await session.send('Emulation.clearDeviceMetricsOverride');
        await session.send('Emulation.resetPageScaleFactor');
    } else if (zoom > 100) {
        // Unset other zooming method
        await session.send('Emulation.clearDeviceMetricsOverride');

        // Zoom in by reducing size then adjusting page scale (unable to zoom out using this method)
        await page.setViewport({
            width: Math.round(width / (zoom / 100)),
            height: Math.round(height / (zoom / 100))
        });

        await session.send('Emulation.setPageScaleFactor', {
            pageScaleFactor: (zoom / 100)
        });

        await session.send('Emulation.setVisibleSize', {
            width: width,
            height: height
        });
    } else {
        // Unset other zooming method
        await session.send('Emulation.resetPageScaleFactor');

        // Zoom out by emulating a scaled device (makes text blurry when zooming in with this method)
        await session.send('Emulation.setDeviceMetricsOverride', {
            width: Math.round(width / (zoom / 100)),
            height: Math.round(height / (zoom / 100)),
            mobile: false,
            deviceScaleFactor: 1,
            dontSetVisibleSize: true,
            viewport: {
                x: 0,
                y: 0,
                width: width,
                height: height,
                scale: (zoom / 100)
            }
        });
    }

    await this.frame.waitForSelector('html');
    this.frame.evaluate(function () {
        window.dispatchEvent(new Event('resize'));
    });
}

Is there a better way to do this?

Jaclynjaco answered 5/9, 2019 at 9:23 Comment(0)
O
5

I just use Javascript to help zoom-in / zoom-out.

await page.evaluate(() => document.body.style.zoom = 0.5  );

It works well for me.

Overweening answered 4/4, 2022 at 2:33 Comment(1)
yes it does, and it's about 200 lines less than other solutionsFissi
G
1

The --force-device-scale-factor command line option seems to work for scaling the complete chrome UI, both for zooming in and zooming out.

Passing that to chrome with puppeteer:

puppeteer.launch({
    args: ["--force-device-scale-factor=0.5"],
    headless: false,
})

(Tested with chromium 78 / puppeteer 1.20.0)


But if you need to zoom without restarting chrome or don't want to scale the whole UI, there actually is a way to trigger the native chrome zoom using puppeteer.

I've created a repository demonstrating that here. It works by taking a detour through a chrome extension, which has access to the chrome.tabs.setZoom API.

chrome-extension/manifest.json:

{
    "name": "my-extension-name",
    "description": "A minimal chrome extension to help puppeteer with zooming",
    "version": "1.0",
    "manifest_version": 2,
    "background": {
        "scripts": ["background.js"],
        "persistent": false
    },
    "permissions": []
}

chrome-extension/background.js:

function setZoom(tabId, zoomFactor) {
    chrome.tabs.setZoom(tabId, zoomFactor)
}

main.js:

const puppeteer = require('puppeteer');

(async () => {
    const extensionPath = require('path').join(__dirname, 'chrome-extension');
    const extensionName = 'my-extension-name';

    const browser = await puppeteer.launch({
        args: [
            `--disable-extensions-except=${extensionPath}`,
            `--load-extension=${extensionPath}`
        ],
        headless: false,
    });
    const page = await browser.newPage();
    await page.goto('https://google.com');
    
    // get the background page of the extension
    const targets = await browser.targets();
    const extenstionPageTarget = targets.find(
        (target) => target._targetInfo.title === extensionName
    );
    const extensionPage = await extenstionPageTarget.page();

    // do the zooming by invoking setZoom of background.js
    const zoomFactor = 0.5
    await extensionPage.evaluate((zoomFactor) => {
        // a tabId of undefined defaults to the currently active tab
        const tabId = undefined;
        setZoom(tabId, zoomFactor);
    }, zoomFactor);
})();

I haven't found a way of getting the tabId of a page using puppeteer, although that is probably again possible through the extension. But the above will do if the page you want to zoom is the currently active one.

Note that loading chrome extensions does not currently work in headless mode, but this is luckily not a problem in your case.

Gunfight answered 3/5, 2021 at 17:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.