Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use navigator.mediaDevices.getUserMedia() to request a webcam stream, display it in a <video> element, then copy a ready frame to a canvas with drawImage(). The canvas approach is a straightforward way to preview, export, or upload a still image. For code working directly with a video track, ImageCapture.grabFrame() is an alternative that returns an ImageBitmap.

Capture a frame from a webcam with video and canvas

This example requests camera permission, displays the stream, waits for the video to become ready, and captures a PNG when the user clicks the button. Serve it over HTTPS or from localhost; camera access is restricted to secure contexts.

Save the following as an HTML file and run it from a local development server, or place the JavaScript in a page with matching video, canvas, and button elements.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<video id="camera" autoplay playsinline></video>
<button id="capture" type="button" disabled>Capture frame</button>
<canvas id="photo"></canvas>
<p id="status" role="status">Camera has not started.</p>

<script>
const video = document.querySelector("#camera");
const canvas = document.querySelector("#photo");
const button = document.querySelector("#capture");
const status = document.querySelector("#status");
let stream;

async function startCamera() {
  try {
    stream = await navigator.mediaDevices.getUserMedia({ video: true });
    video.srcObject = stream;
    await video.play();

    // Wait until dimensions are known; a zero-sized video is not ready to capture.
    if (!video.videoWidth || !video.videoHeight) {
      await new Promise((resolve) => {
        video.addEventListener("loadedmetadata", resolve, { once: true });
      });
    }

    button.disabled = false;
    status.textContent = "Camera ready.";
  } catch (error) {
    status.textContent = `Could not start camera: ${error.name}`;
    console.error(error);
  }
}

button.addEventListener("click", () => {
  if (!video.videoWidth || !video.videoHeight) {
    status.textContent = "The video is not ready yet.";
    return;
  }

  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
  const context = canvas.getContext("2d");
  if (!context) {
    status.textContent = "A 2D canvas context is unavailable.";
    return;
  }

  context.drawImage(video, 0, 0, canvas.width, canvas.height);
  status.textContent = "Frame captured.";
});

function stopCamera() {
  if (stream) {
    stream.getTracks().forEach((track) => track.stop());
    video.srcObject = null;
    stream = null;
    button.disabled = true;
    status.textContent = "Camera stopped.";
  }
}

startCamera();
// Call stopCamera() when the user is done or when this view is disposed.
</script>

Why wait for video dimensions?

A stream can be assigned before the browser has usable video dimensions. Checking video.videoWidth and video.videoHeight prevents drawing a zero-sized or not-yet-ready frame. The example waits for metadata if dimensions are initially unavailable and enables capture after playback starts.

Choose the output size

Setting the canvas width and height to the video’s intrinsic dimensions captures at the dimensions currently exposed by the video element. To create a smaller output, set the canvas dimensions to the desired width and height; drawImage(video, 0, 0, width, height) scales the frame to fit. For cropping, use the version of drawImage() that accepts source and destination rectangles, and calculate those rectangles for the crop you want.

Export the captured canvas

After drawImage(), the canvas contains the pixels. Use toDataURL() for a convenient data URL, or use toBlob() when you want a blob suitable for upload without constructing a large base64 string.

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option
// Data URL: simple to use for a small preview or data-URL-based workflow.
const pngDataUrl = canvas.toDataURL("image/png");

// Blob: usually a better fit for a file upload.
canvas.toBlob((blob) => {
  if (!blob) {
    console.error("The browser could not encode the canvas.");
    return;
  }

  const formData = new FormData();
  formData.append("photo", blob, "webcam-frame.png");
  // Send formData to your upload endpoint with fetch().
}, "image/png");

The example requests PNG output. If you choose another supported canvas image format, make sure the receiving endpoint expects it. A data URL is encoded image data, not a local file path; for uploading, a blob and FormData avoid that distinction.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use ImageCapture.grabFrame() instead

If the rest of your code works with MediaStreamTrack objects, ImageCapture.grabFrame() can take a snapshot from the live video track and resolve to an ImageBitmap. You can then draw that bitmap to a canvas or pass it to image-processing code.

async function captureTrackFrame(stream) {
  const track = stream.getVideoTracks()[0];
  if (!track) {
    throw new Error("The stream has no video track.");
  }

  if (typeof ImageCapture === "undefined") {
    throw new Error("ImageCapture is not available in this browser.");
  }

  const imageCapture = new ImageCapture(track);
  const bitmap = await imageCapture.grabFrame();

  const canvas = document.querySelector("#photo");
  canvas.width = bitmap.width;
  canvas.height = bitmap.height;
  const context = canvas.getContext("2d");
  if (!context) {
    bitmap.close();
    throw new Error("A 2D canvas context is unavailable.");
  }

  context.drawImage(bitmap, 0, 0);
  bitmap.close();
}

MDN describes grabFrame() as taking a snapshot of live video in a MediaStreamTrack and returning an ImageBitmap if successful: ImageCapture.grabFrame() reference. The track-oriented API has a different output from the canvas route; check support in the browsers you target and handle a rejected promise.

Canvas or ImageCapture: which should you use?

Method Best fit Result Trade-off
Video element and canvas drawImage() Still-photo capture, preview, cropping, or canvas effects Canvas pixels, exportable as a data URL or blob You size the canvas and serialize or convert its contents for export.
ImageCapture.grabFrame() Code that already operates on a MediaStreamTrack ImageBitmap Check target-browser support and incorporate the bitmap into your processing pipeline.

Neither method has a universal latency, frame-rate, or image-quality guarantee established by the cited documentation. Those outcomes depend on the device, browser, camera stream, and application; measure them in the environments that matter to your product.

Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

Request camera access safely

  • Use a secure context. getUserMedia() is available in secure contexts, with HTTPS and localhost as practical examples. On an insecure page, navigator.mediaDevices may be unavailable. See MDN’s getUserMedia() reference.
  • Ask at the point of use. The browser prompts the visitor for permission. Explain why the camera is needed and show a useful state while permission is pending.
  • Stop the stream when finished. Call stream.getTracks().forEach(track => track.stop()) when the camera is no longer needed. This releases the tracks rather than leaving the capture active after the user has left the camera view.
  • Handle unavailable devices and denied access. A permission denial commonly results in NotAllowedError; no matching camera input can result in NotFoundError. Display an actionable message instead of leaving a blank preview.
  • Check iframe policy. A page embedded in an iframe needs camera access delegated by the top-level page through Permissions Policy, such as an appropriate allow="camera" attribute and/or policy header.

Use navigator.mediaDevices.getUserMedia(), not the deprecated callback-style navigator.getUserMedia(). MDN labels the older method legacy and directs developers to the newer MediaDevices method: deprecated Navigator.getUserMedia() reference.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshoot common capture failures

Symptom Likely cause What to check or do
navigator.mediaDevices is missing The page is not in a secure context, or the browser does not expose the API. Load the page over HTTPS or localhost and inspect the target browser and embedding context.
The request rejects with NotAllowedError The visitor denied permission, or browser/site policy prevents access. Explain how to grant camera permission in the browser and confirm an iframe has camera permission delegated.
The request rejects with NotFoundError No camera matches the requested video input. Check that a camera is connected and available to the operating system, then retry with an available input.
The video is black or the captured canvas is empty The stream has not produced usable dimensions or playback has not started. Wait for playback/metadata and confirm both video.videoWidth and video.videoHeight are nonzero before drawing.
ImageCapture is undefined or grabFrame() fails The track-oriented API is unavailable or the snapshot operation failed in that environment. Feature-detect ImageCapture, catch errors, and use the video-plus-canvas path where appropriate.
The camera remains active after capture The stream tracks were not stopped. Stop every track when the user finishes, and clear video.srcObject.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance and reliability considerations

  • Capture only in response to an intentional user action when the workflow is taking a photo; this avoids producing frames the user does not need.
  • Set the canvas dimensions deliberately. A larger canvas holds more pixels and may take more work to encode or upload; use the output dimensions your feature needs.
  • Prefer a blob for upload-oriented workflows. A data URL is convenient for previews but represents the image as encoded text.
  • Keep permission and stream errors visible in the interface. Browser permission, available hardware, and iframe policy are conditions your application cannot assume.
  • Do not promise a fixed capture delay or image quality across devices. Test representative browsers and cameras and define any product requirement from those measurements.

Or skip the browser setup

For screenshots of web pages rather than frames from a visitor’s webcam, ScreenshotNeo is a website screenshot API and MCP server. A single request can return an image or PDF; it is not a replacement for getUserMedia() when you need the user’s camera.

cURL example, documented at ScreenshotNeo docs:

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

ScreenshotNeo removes cookie/consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, failed loads, and cache hits are not billed. Its MCP server lets AI agents take screenshots. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo’s free plan to try it with 1,000 screenshots a month and no card.

Frequently Asked Questions

Does getUserMedia capture the frame by itself?

No. It provides a live MediaStream; draw a ready video frame to a canvas or use ImageCapture.grabFrame() on its video track.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What does ImageCapture.grabFrame() return?

An ImageBitmap when the snapshot succeeds.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.