puppeteer: Full page screenshot is not using viewport size for `vw` and `vh` CSS.

Using fullPage: true option with Page.screenshot seems to be messing vw and vh CSS units, even though page size is set with Page.setViewport.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 14
  • Comments: 21 (3 by maintainers)

Commits related to this issue

Most upvoted comments

My current workaround – instead of using fullPage I measure the body bounding box and use that as a clipping argument:

const bodyHandle = await page.$('body');
const { width, height } = await bodyHandle.boundingBox();
const screenshot = await page.screenshot({
  clip: {
    x: 0,
    y: 0,
    width,
    height
  },
  type: 'png'
});

await bodyHandle.dispose();

Hey! Any progress here? 😃 Maybe somebody knows the workaround?

add this code before screenshot to fix:

await page.evaluate(async () => { var allElems = window.document.getElementsByTagName("*"); for (var i = 0, max = allElems.length; i < max; i++) { var hEleme = window.getComputedStyle(allElems[i]).height; if(hEleme != 'auto'){ var numdEleme = Number(hEleme.slice(0, -2)); if(numdEleme > 100 && numdEleme < 1000){ allElems[i].style.setProperty("height", numdEleme + 'px', "important"); } } } return true; });

An example page that’s using vh CSS units - http://jsbin.com/fawerivecu/edit?html,css,output.

Script:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('http://output.jsbin.com/fawerivecu');
  await page.setViewport({
  	width: 1920,
  	height: 1080
  });
  await page.screenshot({ path: 'example.png', fullPage: true });

  browser.close();
})();

Expected Result: expected_result

Actual Result: actual_result

For what’s it’s worth, I don’t think this is an actual bug, but rather the expected (or unexpected) behavior of the viewport.

If you open a Chrome page in a vertical monitor at full height, and you open a website using 100vh in a section (for example the hero), the section will take the entire height of the screen (as expected). When people implement their websites with Viewport units, they don’t account for supermassive vertical screens, so it doesn’t make sense to think of a scenario where the page isn’t visualized with at least one screen fold scroll (which is what Puppeteer is doing when fullPage is true)

There are many ways to get around this but not all the strategies will cover 100% of the cases. It seems that the issue comes from Puppeteer interpreting that fullPage=true means the height of the viewport is equivalent to the height of the window.

The solution that puppeteer could provide is to receive an extra parameter besides the fullPage=true, with a pseudo-viewport height in pixels, then when rendering, before snapping it could swap any references that are using 100vh, to the passed pseudo-viewport height in pixels. I say pseudo-viewport value but this could be simply assuming that the height value you pass when you do Page.setViewport.

What I don’t know is how expensive would it be to make that swap in terms of performance.

There’s actually a PostCSS plugin that does that something similar fix an issue with a CMS. Not the same problem but within the same realm of what’s going on here: https://www.npmjs.com/package/postcss-vh-to-px

Thanks, that’s a bug with full page screenshots. Filed upstream: crbug.com/763421

The only workaround I have been able to get working is to change this line https://github.com/GoogleChrome/puppeteer/blob/master/lib/Page.js#L713 from this:

const width = Math.ceil(metrics.contentSize.width);

to this:

const width = this._viewport.width;

The current code is resizing the height correctly because you want a full-height screenshot, but it’s also resizing the screenshot’s width to the size of the content, ignoring the viewport settings. I think the width should maintain the desired viewport size.

@schnerd - That worked like a charm for me! Exactly what I needed. Big Thanks