Dev.to WebDev 🛠 Dev 👁 0 📖 4 min read

Your mobile screenshots are probably lying about your layout

I sell HTML templates. Yesterday I found out that every mobile preview in every one of my product listings showed the product broken — cards clipped off the right edge, text cut mid-sentence, a button sliced in half. Th

I sell HTML templates. Yesterday I found out that every mobile preview in every one of my product listings showed the product broken — cards clipped off the right edge, text cut mid-sentence, a button sliced in half.

The templates were fine. The screenshots were wrong.

Here is what happened, because the failure mode is quiet and I suspect it is common.

The setup

I take product screenshots with headless Chrome, which is the obvious thing to do:

chrome --headless=new --hide-scrollbars \
       --window-size=390,900 \
       --screenshot=mobile.png \
       https://example.com

One command, no dependencies, runs on any machine. For desktop widths it is perfect. I had twenty of these in a script and never looked twice.

The failure

At 390px the output looked like this: the page laid out, but every card ran past the right edge of the image. Not cropped by the screenshot — the content itself was wider than the viewport, as if the page had been rendered at desktop width and then the camera had been moved in close.

My first thought was that my own responsive CSS was broken.

It was not. I opened the same page in a real browser at 375px and measured:

document.documentElement.scrollWidth > window.innerWidth  // false
[...document.querySelectorAll('.card')]
  .filter(c => c.getBoundingClientRect().right > window.innerWidth + 1).length  // 0

No horizontal overflow, no card out of bounds. The live page was correct. Only the screenshot was wrong.

What I tried that did not work

Raising the virtual time budget. The obvious guess is a race: the shot is taken before layout and JavaScript settle. So:

--virtual-time-budget=20000

Twenty seconds instead of seven. Identical output, down to a few hundred bytes. Whatever is wrong is not a matter of waiting longer.

Blaming the charts. My dashboard draws its charts with JavaScript, reading host.clientWidth at draw time, so a stale width would explain over-wide SVG. Plausible — except the same clipping appeared on a plain marketing site with no scripted graphics at all. Different codebase, same artifact.

I stopped trying to diagnose it. The point was not to understand headless Chrome; the point was to get an honest picture of the page.

What worked

Render the page inside an <iframe> that is genuinely 390px wide, on a canvas that is a comfortable desktop size, and scale it down with a transform:

<style>
  :root { --ph-w: 390px; --ph-h: 860px; --scale: .482; }

  .frame {
    width:  calc(var(--ph-w) * var(--scale));
    height: calc(var(--ph-h) * var(--scale));
    overflow: hidden;
    border-radius: 18px;
  }
  .frame iframe {
    width:  var(--ph-w);
    height: var(--ph-h);
    border: 0;
    display: block;
    transform: scale(var(--scale));
    transform-origin: top left;
  }
</style>

<div class="frame">
  <iframe src="index.html" scrolling="no"></iframe>
</div>

Then screenshot that page at a normal desktop size:

chrome --headless=new --hide-scrollbars --allow-file-access-from-files \
       --force-device-scale-factor=2 \
       --window-size=910,607 \
       --virtual-time-budget=15000 \
       --screenshot=responsive.png \
       file:///path/_responsive.html

The iframe gets a real 390px layout — media queries fire, the sidebar collapses, the cards reflow — and the outer page is a width headless Chrome handles correctly. The clipping disappeared completely: cards with proper right margins, sparklines ending inside their cards with the end dot visible.

Two side benefits I did not expect:

  • Several phones in one image. Three iframes side by side, each a different page, one file. Much better than three separate narrow screenshots.
  • It is live. Change the CSS, re-run the command, the preview updates. No stale PNGs of an old design sitting in a listing for months.

--force-device-scale-factor=2 renders at 2× so the result is sharp on retina displays while the CSS layout stays at the size you reasoned about.

The part that actually costs money

While fixing this I checked what the marketplaces require, and the numbers are worth knowing before you export anything:

  • One marketplace asks for previews of exactly 1820×1214, not a range. Ordinary 1600×1000 page screenshots are refused.
  • Its stated minimum anywhere is 910×607. A raw phone screenshot at 390×844 is below the minimum width and cannot be uploaded at all.

So the composed canvas is not only more honest, it is the only shape that fits. A real phone screenshot, by itself, is not an acceptable preview on at least one major platform.

The lesson I will keep

A screenshot is a measurement, and measurements need calibrating against the thing they measure.

I had PNGs produced by a script I trusted, sitting in live product listings, showing customers a broken layout. Nobody told me. The only reason I found it was that I happened to open one at full size while working on something else — and a listing can sit like that for as long as nobody looks.

Now the rule is: before a screenshot goes anywhere a customer will see it, open the same page in a real browser at the same width and check that they agree. It takes thirty seconds.

If you generate previews, marketing images or documentation screenshots from a script, this is worth doing once today. The failure is silent, and the people who see it are exactly the people you were trying to impress.

The template this came out of is Mesa, a restaurant analytics dashboard. The free cut is MIT. The chart engine behind it is a separate library: mesa-charts — eleven chart types on one page.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.