ScreenshotNeo

BlogHow-to

How to Fix Blurry Website Screenshots on High-DPI Displays

Find out whether blur comes from canvas resolution, image assets, capture settings, or later resizing—and fix it with a practical high-DPI workflow.

By the ScreenshotNeo team4 October 20268 min read

A blurry screenshot on a high-DPI display usually means that some part of the image has fewer pixels than the size at which it is being shown or captured. Start by checking the saved file at its native dimensions. If it is sharp there but soft in a preview or after sharing, investigate that later display or processing step. If a canvas is soft in the saved file, size its bitmap for the rendered CSS dimensions multiplied by the device pixel ratio (DPR), then scale its drawing context by the same ratio.

DPR is the ratio of physical pixels to CSS pixels for the current display. A DPR of 2 means a 300 × 150 CSS-pixel canvas needs a 600 × 300 bitmap to represent that area at the display’s pixel density. Browser zoom can change DPR; pinch zoom does not. MDN’s devicePixelRatio reference describes the value and the possibility that it changes when a window moves between displays.

1. Locate where the blur enters

  1. Inspect the exported screenshot at 100% scale. Check the actual pixel dimensions, not just how large an app preview makes it appear. If the file itself is sharp, the capture is probably not where the softness was introduced.
  2. Check the browser’s DPR and zoom. In the page’s DevTools Console, run window.devicePixelRatio. Also note browser zoom, viewport size, and whether the window is on a different-density monitor.
  3. Compare the page and the capture. If ordinary text and CSS edges are sharp but chart lines or drawings are fuzzy, inspect canvas sizing. If a photo or raster logo is soft, inspect its source dimensions and rendered size.
  4. Keep capture conditions fixed while comparing. Use the same page, viewport, zoom, DPR, and capture scope (viewport or full page). Compare the exported pixel dimensions and inspect both files at native scale.

Text, SVG, and CSS are generally scaled by the browser. A canvas has its own bitmap, so a canvas bitmap that is too small can be enlarged and look blurry. A raster image can have the same problem if its source does not have enough pixels for the displayed size.

2. Fix a canvas by matching its backing bitmap to DPR

Keep the canvas’s layout dimensions in CSS pixels, set its bitmap dimensions to the rendered CSS size times DPR, and scale the 2D context. Drawing commands can then continue to use CSS-pixel coordinates. Measure the actual rendered size so the approach also works for responsive canvases.

<canvas id="chart" style="width: 300px; height: 150px"></canvas>
<script>
const canvas = document.querySelector("#chart");
const ctx = canvas.getContext("2d");

function resizeCanvas() {
  const rect = canvas.getBoundingClientRect();
  const dpr = window.devicePixelRatio || 1;

  // Set the internal bitmap in physical pixels.
  canvas.width = Math.round(rect.width * dpr);
  canvas.height = Math.round(rect.height * dpr);

  // Draw in CSS-pixel coordinates from this point on.
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  draw();
}

function draw() {
  const rect = canvas.getBoundingClientRect();
  ctx.clearRect(0, 0, rect.width, rect.height);
  ctx.fillStyle = "#1769aa";
  ctx.font = "16px sans-serif";
  ctx.fillText("High-DPI canvas", 12, 28);
  ctx.beginPath();
  ctx.moveTo(12, 50);
  ctx.lineTo(rect.width - 12, rect.height - 12);
  ctx.strokeStyle = "#1769aa";
  ctx.lineWidth = 2;
  ctx.stroke();
}

resizeCanvas();
new ResizeObserver(resizeCanvas).observe(canvas);
</script>

The CSS width and height define the layout size; canvas.width and canvas.height define the bitmap size. Do not set the CSS width to the enlarged bitmap dimensions as well, or the canvas may take up more layout space than intended. Assigning either bitmap dimension clears and resets the drawing surface, so resize before drawing and redraw after every resize.

The example uses setTransform to replace the context transform on each resize. This avoids multiplying the scale repeatedly. If other code relies on a particular transform or drawing state, restore or reapply that state when resizing. The same DPR-backed bitmap technique is described in web.dev’s high-DPI canvas guide.

Handle DPR changes when a window moves between displays

A resize observer catches changes to the canvas’s rendered size, but a display change can alter DPR without changing that CSS size. Recheck DPR when the window changes display or the page is resized. A simple practical approach is to check on resize and when the page becomes visible:

let previousDpr = window.devicePixelRatio || 1;

function checkCanvasScale() {
  const nextDpr = window.devicePixelRatio || 1;
  if (nextDpr !== previousDpr) {
    previousDpr = nextDpr;
    resizeCanvas();
  }
}

window.addEventListener("resize", checkCanvasScale);
document.addEventListener("visibilitychange", () => {
  if (!document.hidden) checkCanvasScale();
});

For applications with complicated display transitions, redraw when your layout or rendering lifecycle detects a scale change. Avoid reallocating the canvas bitmap on every animation frame: only resize when the measured dimensions or DPR actually change.

3. Check raster images and screenshot dimensions

For a normal image, compare its intrinsic dimensions with its rendered CSS size and the target DPR. A 300-pixel-wide source displayed at 300 CSS pixels on a DPR 2 display may need to be enlarged to fill 600 physical pixels. Use a higher-resolution source when sharpness at that display size matters. Increasing the dimensions of an already-soft screenshot does not restore missing detail.

When reviewing an exported screenshot, distinguish its pixel dimensions from the CSS viewport dimensions. A capture of a 1280 CSS-pixel-wide viewport at a scale factor of 2 may be 2560 pixels wide. That larger file is useful when you need the extra pixel detail, but it takes more storage and can require more memory to handle.

4. Capture with Chrome DevTools at the intended DPR

  1. Open DevTools and enable the device toolbar (device mode).
  2. Select a device preset or choose a responsive viewport. To set a custom DPR, use More options → Add device pixel ratio.
  3. Reload or redraw the page if its canvas setup ran before the emulated dimensions or DPR were applied.
  4. Use More options → Capture screenshot for the visible viewport, or Capture full size screenshot for the full page.
  5. Inspect the saved image’s pixel dimensions and view it at native scale.

Chrome describes device mode as a first-order approximation of a mobile device. Use an actual target device when exact device behavior matters. The available capture settings and their limits are covered in Chrome DevTools’ device mode documentation.

5. Use ScreenshotNeo when you need repeatable captures

For a one-off diagnosis, local DevTools gives you direct control over the viewport and DPR. For repeated website captures, ScreenshotNeo is a website screenshot API and MCP server. It supports retina scale, viewport presets, full-page captures, and PNG, JPEG, WebP, or PDF output. Its API accepts screenshot parameters also used by other screenshot APIs, which can make switching easier.

Use the scale option when you need more output pixels for a given viewport, and inspect the returned file at its native size. Higher output dimensions mean more image data to store or transfer. Screenshot scale cannot recover detail that the page itself never rendered, such as a low-resolution source image or a canvas drawn into an undersized bitmap.

Or skip the browser setup

Make a screenshot with one GET request. See the ScreenshotNeo API documentation for the available parameters, including retina scale and output format.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
  • Cookie banners are accepted and removed before capture; known newsletter popups and chat widgets are removed too. Each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers report the page verdict and whether the request was billed.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
  • The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is available on every plan.

Create a free ScreenshotNeo account and get 1,000 screenshots a month with no card.

Troubleshooting

Symptom Likely cause Fix
Canvas is blurry while text around it is sharp The canvas bitmap is smaller than its rendered size at the current DPR. Set bitmap width and height from the measured CSS dimensions multiplied by DPR, scale the context, and redraw.
Canvas turns blank after a resize Changing canvas.width or canvas.height clears the drawing surface and resets context state. Resize before drawing, then redraw and reapply styles, transforms, and other state.
Canvas appears larger after adding DPR support The enlarged bitmap dimensions were also applied as CSS dimensions. Keep layout size in CSS pixels; only enlarge the canvas bitmap attributes.
Canvas gets progressively larger or its lines change after repeated resizes The context scale was multiplied repeatedly or the drawing routine uses stale state. Set an absolute transform with setTransform, then redraw with current dimensions.
Canvas is sharp on one monitor and soft on another DPR changed when moving the window, but the bitmap was not resized. Recheck DPR and rebuild the backing bitmap on display or scale changes.
Photos or logos look soft but canvas does not The raster source may be undersized for its rendered size. Use an appropriately sized source and verify the exported file at 100% scale.
The saved image is sharp but a preview looks blurry A later display, embedding, or processing step may be resizing it. Check the original file at native dimensions and investigate the receiving app or export path.
DevTools capture does not match a real phone Device mode approximates device behavior and is not an exact substitute. Validate on the actual target device when fidelity matters.

Performance, reliability, and file size

  • Canvas memory grows with pixel area. Doubling DPR in both dimensions creates roughly four times as many bitmap pixels. Set only the resolution you need, especially for large canvases.
  • Resize deliberately. Rebuild and redraw when CSS dimensions or DPR changes; repeated unnecessary bitmap allocation can waste work.
  • Allow the page to render before capture. A screenshot taken before fonts, images, or canvas drawing are ready can be incomplete even when its pixel dimensions are correct.
  • Keep comparisons controlled. Match viewport, zoom, DPR, and capture scope before drawing conclusions about sharpness. The cited documentation does not establish that one browser universally produces sharper screenshots.
  • Plan for output size. More pixels require more storage and transfer. Choose the output format and dimensions for the destination rather than enlarging every capture by default.

FAQ

Does setting a larger screenshot scale fix every blurry page?

No. It can increase the capture’s pixel dimensions, but it cannot add detail missing from an undersized source image or canvas bitmap.

Should I use DPR or browser zoom to make the screenshot sharper?

Set the capture conditions to match the display or device you are targeting. Zoom can affect DPR, so record both when diagnosing a mismatch.

Is a full-page screenshot automatically sharper than a viewport capture?

No. Capture scope changes what is included, not whether the underlying content has enough pixels. Check the exported dimensions and the page’s canvas and image sources.

Can DevTools prove how a real device will render?

It is useful for approximation and debugging. Chrome recommends testing on an actual target device when exact behavior matters.