Qaid
ARTICLE

Screenshot Capture: Visual Context for Feedback

Add automatic screenshot capture to your feedback embed, with options for DOM-based or permission-based capture.

Qaid Team

Half your bug reports say "it looks wrong". A screenshot answers the follow-up question before you ask it. There are two ways to take one, and which you get depends partly on the device.

0:00 / 0:00
A captured screenshot on a feedback item, opened full size

Switching it on

One flag:

<script
  src="https://unpkg.com/@qaiddev/thumbs-embed/dist/embed.js"
  data-endpoint="/api/feedback"
  data-capture-screenshot="true"
></script>

Or in JavaScript:

import { QaidFeedback } from '@qaiddev/thumbs-embed';

new QaidFeedback({
  endpoint: '/api/feedback',
  captureScreenshot: true
});

From then on a submission carries an image, encoded as WebP, in the same payload as the message.

Two ways to take the picture

One asks the browser for the pixels and one rebuilds them from your DOM. They fail in different places.

Permission, the default on desktop

"permission" goes through the browser's Screen Capture API.

new QaidFeedback({
  endpoint: '/api/feedback',
  captureScreenshot: true,
  screenshotMethod: 'permission'  // This is the default
});

The browser asks the reader to share their screen, they pick the current tab, one frame comes off the video stream, and the stream stops.

What you get is exactly what was on screen: canvas, WebGL, iframes, animations, whatever CSS you were doing. No library to load.

What it costs is a browser dialog asking permission to record the screen, which is alarming if you were not expecting it. Some people say no, and that is the end of the screenshot.

DOM, the default on phones

"dom" redraws the page from the DOM with html2canvas. Nothing is asked and nothing is shown.

new QaidFeedback({
  endpoint: '/api/feedback',
  captureScreenshot: true,
  screenshotMethod: 'dom'
});

html2canvas walks the DOM, reads the computed style of everything it finds, paints its best reconstruction onto a canvas, and exports that as WebP.

Nobody is asked anything, so nobody declines. It works anywhere canvas does.

It is a reconstruction, so it gets things wrong. Canvas and WebGL come out blank. Cross-origin images go missing unless the other server sends CORS headers. Filters and blend modes land approximately. And it fetches html2canvas from qaid.dev/lib on first use, which is about 190KB, though only for readers who actually submit feedback.

Touch devices always use DOM

On any device whose primary pointer is touch, the embed uses DOM capture no matter what screenshotMethod says. getDisplayMedia on a phone throws up a system-level recording prompt, dims the screen, and on iOS hands back a rotated frame, so the permission path is not usable there.

Setting screenshotMethod: "permission" therefore means "permission on desktop, DOM on mobile". Setting "dom" means DOM everywhere. There is no way to force the permission dialog onto a phone, which is a feature.

PermissionDOM
AccuracyExactA reconstruction
Asks the userYesNo
Canvas and WebGLCapturedBlank
Cross-origin imagesCapturedNeeds CORS
Extra downloadNone~190KB, on first use
Where it runsDesktop onlyEverywhere

Leave it on "permission" unless the dialog is costing you reports, and switch to "dom" if it is. On an internal dashboard, where nobody is startled by a permission prompt they see weekly, stay on "permission".

Quality and dimensions

screenshotOptions controls what comes out:

new QaidFeedback({
  endpoint: '/api/feedback',
  captureScreenshot: true,
  screenshotOptions: {
    quality: 0.8,      // WebP compression (0-1), default: 1.0
    maxWidth: 1920,    // Maximum width in pixels, default: 1280
    maxHeight: 1080    // Maximum height in pixels, default: 800
  }
});

quality

WebP compression, from 0 to 1. Expect 500KB to 2MB at 1.0, 100 to 400KB at 0.8, and 50 to 150KB at 0.6.

Use 0.8, and drop to 0.6 when the storage bill shows up. A slightly soft screenshot still answers the question.

maxWidth and maxHeight

Both caps preserve the aspect ratio, scaling the image down if either is passed.

Defaults are 1280 by 800. Raise them if your readers work on large monitors and you keep squinting at the result.

// For high-DPI displays or detailed UI
screenshotOptions: {
  quality: 0.9,
  maxWidth: 2560,
  maxHeight: 1440
}

// For minimal storage footprint
screenshotOptions: {
  quality: 0.6,
  maxWidth: 800,
  maxHeight: 600
}

Server-side capture, on Pro

When the browser produces nothing, the payload still carries the element bounds and the user agent. On a Pro plan our servers can use those to render the page themselves.

That covers a declined prompt, a DOM capture that came back blank, and any case where every item needs an image.

A headless browser opens the URL and shoots the viewport.

It can only reach pages it can load, so a page behind a login needs authenticated access configured in your project settings first.

Everything together

All the screenshot options in one config:

import { QaidFeedback } from '@qaiddev/thumbs-embed';

new QaidFeedback({
  endpoint: '/api/feedback',
  apiKey: 'your-api-key',

  // Enable screenshot capture
  captureScreenshot: true,

  // Choose capture method
  screenshotMethod: 'permission',  // or 'dom'

  // Fine-tune output
  screenshotOptions: {
    quality: 0.85,
    maxWidth: 1600,
    maxHeight: 1000
  },

  // Other configuration
  position: 'bottom-right',
  colors: {
    positive: '#22c55e',
    negative: '#ef4444'
  }
});

Or as data attributes:

<script
  src="https://unpkg.com/@qaiddev/thumbs-embed/dist/embed.js"
  data-endpoint="/api/feedback"
  data-api-key="your-api-key"
  data-capture-screenshot="true"
  data-screenshot-method="dom"
></script>

Before you turn it on

Put a line in your privacy policy saying feedback may include a picture of the page. You are now collecting images of whatever your users had on screen.

Give the permission dialog a week and compare submission rates either side of it. A halving means switch to "dom".

Quality and dimensions are both cheap to turn down, and the screenshot stays useful well below the defaults.

Failures need no handling from you. A capture that throws, times out or gets declined submits the feedback without an image rather than losing it.

Pair it with annotation and your readers can circle the problem and paint over their own account number before any of it reaches you.

Back to all articles