Webcam Capture
Headless camera capture: preview, device switching, countdown shutter, and photos.
|
|
| Package |
@vizejs/ui/webcam-capture |
| Maturity |
stable |
| Own the source |
vize lib pull webcam-capture |
| Requires |
context, controllable-state, id |
| Aliases |
webcam, camera capture, photo booth, selfie camera, getUserMedia |
| Covers |
MediaDevices.getUserMedia, HTMLCanvasElement.toBlob, @vizejs/composable useUserMedia, react-webcam |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { WebcamCaptureDeviceSelect, WebcamCapturePhoto, WebcamCapture, WebcamCaptureRoot, WebcamCaptureShutter, WebcamCaptureStartButton, WebcamCaptureStatusMessage, WebcamCaptureStopButton, WebcamCaptureSwitchCamera, WebcamCaptureVideo } from "@vizejs/ui/webcam-capture";
Or copy the source into your project with vize lib pull webcam-capture (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Camera booth with start, stop, switch, and countdown shutter controls; it renders idle and only requests the camera when the user presses Start. -->
<script setup lang="ts">
import {
WebcamCapture,
WebcamCaptureDeviceSelect,
WebcamCapturePhoto,
WebcamCaptureShutter,
WebcamCaptureStartButton,
WebcamCaptureStatusMessage,
WebcamCaptureStopButton,
WebcamCaptureSwitchCamera,
WebcamCaptureVideo,
} from "@vizejs/ui/webcam-capture";
</script>
<template>
<WebcamCapture capture-type="image/jpeg" :capture-quality="0.9">
<WebcamCaptureVideo />
<WebcamCaptureStartButton />
<WebcamCaptureStopButton />
<WebcamCaptureSwitchCamera />
<WebcamCaptureDeviceSelect />
<WebcamCaptureShutter :countdown="3" />
<WebcamCaptureStatusMessage />
<WebcamCapturePhoto alt="Captured photo" />
</WebcamCapture>
</template>
API
WebcamCaptureDeviceSelect
Source: src/families/media/webcam-capture/webcam-capture-device-select.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string |
undefined |
Accessible name. Defaults to messages.deviceSelect. |
Slots
| Slot |
Slot props |
Description |
option |
{ readonly device: WebcamCaptureDevice; readonly selected: boolean } |
Option content per camera. Defaults to the device label. |
Exposed
| Member |
Type |
Description |
element |
HTMLSelectElement | null |
Rendered native select. |
WebcamCapturePhoto
Source: src/families/media/webcam-capture/webcam-capture-photo.vue
Props
| Prop |
Type |
Default |
Description |
alt |
string |
undefined |
Alternative text. Defaults to messages.photo. |
Slots
| Slot |
Slot props |
Description |
empty |
Record<string, never> |
Rendered instead of nothing while no photo exists, e.g. a placeholder. |
default |
{ readonly photo: WebcamCapturePhotoResult; readonly url: string } |
Extra content after the image, e.g. a download link. Receives the photo and its URL. |
Exposed
| Member |
Type |
Description |
element |
HTMLImageElement | null |
Rendered image, or null without a photo. |
WebcamCapture
Source: src/families/media/webcam-capture/webcam-capture-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned root id. null and undefined select a deterministic fallback. |
stream |
WebcamMediaStreamLike | null |
undefined |
Consumer-owned stream, e.g. useUserMedia().stream.value. Any value other than undefined (including null) selects external mode: the root never acquires or stops tracks itself. |
constraints |
MediaTrackConstraints |
undefined |
Extra video track constraints for owned acquisition (resolution, frame rate…). |
audio |
boolean |
false |
Also request the microphone for owned acquisition. |
facingMode |
WebcamFacingMode |
undefined |
Controlled facing direction (v-model:facingMode). undefined selects uncontrolled use. |
defaultFacingMode |
WebcamFacingMode |
"user" |
Initial facing direction for uncontrolled use. |
deviceId |
string | null |
undefined |
Controlled camera (v-model:deviceId). null clears the selection; undefined selects uncontrolled use. |
defaultDeviceId |
string |
undefined |
Initial camera for uncontrolled use. |
autoStart |
boolean |
false |
Request the camera on mount. Otherwise permission is only requested by start(). |
mirrored |
boolean |
undefined |
Mirror the preview and captures. undefined mirrors the user camera only. |
host |
WebcamMediaHost | null |
navigator.mediaDevices |
MediaDevices capability for alternate runtimes and tests. |
captureType |
WebcamCaptureImageType |
"image/png" |
Encoded capture type. |
captureQuality |
number |
undefined |
Encoder quality for lossy capture types, from 0 to 1. |
aspectRatio |
number |
undefined |
Center-crop captures to this width / height ratio. |
maxWidth |
number |
undefined |
Downscale captures to at most this width in pixels. |
countdownInterval |
number |
1000 |
Milliseconds per shutter countdown step. |
messages |
Partial<WebcamCaptureMessages> |
undefined |
Localized labels and announcements. Omitted entries use English defaults. |
Events
| Event |
Payload |
Description |
update:facingMode |
[facingMode: WebcamFacingMode] |
Fired when the facing direction requests a new controlled value. |
update:deviceId |
[deviceId: string | null] |
Fired when the selected camera requests a new controlled value. |
statusChange |
[status: WebcamCaptureStatus, previous: WebcamCaptureStatus] |
Fired after every distinct status transition. |
error |
[failure: WebcamCaptureFailure] |
Fired when owned acquisition fails. |
streamChange |
[stream: WebcamMediaStreamLike | undefined] |
Fired when an owned stream starts or stops. |
capture |
[photo: WebcamCapturePhotoResult] |
Fired after a photo is captured. |
captureError |
[error: unknown] |
Fired when a capture fails to draw or encode. |
Slots
| Slot |
Slot props |
Description |
default |
WebcamCaptureSlotState |
Video, controls, photo, and status parts. Receives the camera state. |
Exposed
| Member |
Type |
Description |
capture |
— |
|
capturing |
— |
|
clearPhoto |
— |
|
countdown |
— |
|
deviceId |
— |
|
devices |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
error |
— |
|
external |
— |
|
facingMode |
— |
|
mirrored |
— |
|
photo |
— |
|
photoUrl |
— |
|
selectDevice |
— |
|
start |
— |
|
status |
— |
|
stop |
— |
|
stream |
— |
|
switchCamera |
— |
|
WebcamCaptureRoot
Source: src/families/media/webcam-capture/webcam-capture-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned root id. null and undefined select a deterministic fallback. |
stream |
WebcamMediaStreamLike | null |
undefined |
Consumer-owned stream, e.g. useUserMedia().stream.value. Any value other than undefined (including null) selects external mode: the root never acquires or stops tracks itself. |
constraints |
MediaTrackConstraints |
undefined |
Extra video track constraints for owned acquisition (resolution, frame rate…). |
audio |
boolean |
false |
Also request the microphone for owned acquisition. |
facingMode |
WebcamFacingMode |
undefined |
Controlled facing direction (v-model:facingMode). undefined selects uncontrolled use. |
defaultFacingMode |
WebcamFacingMode |
"user" |
Initial facing direction for uncontrolled use. |
deviceId |
string | null |
undefined |
Controlled camera (v-model:deviceId). null clears the selection; undefined selects uncontrolled use. |
defaultDeviceId |
string |
undefined |
Initial camera for uncontrolled use. |
autoStart |
boolean |
false |
Request the camera on mount. Otherwise permission is only requested by start(). |
mirrored |
boolean |
undefined |
Mirror the preview and captures. undefined mirrors the user camera only. |
host |
WebcamMediaHost | null |
navigator.mediaDevices |
MediaDevices capability for alternate runtimes and tests. |
captureType |
WebcamCaptureImageType |
"image/png" |
Encoded capture type. |
captureQuality |
number |
undefined |
Encoder quality for lossy capture types, from 0 to 1. |
aspectRatio |
number |
undefined |
Center-crop captures to this width / height ratio. |
maxWidth |
number |
undefined |
Downscale captures to at most this width in pixels. |
countdownInterval |
number |
1000 |
Milliseconds per shutter countdown step. |
messages |
Partial<WebcamCaptureMessages> |
undefined |
Localized labels and announcements. Omitted entries use English defaults. |
Events
| Event |
Payload |
Description |
update:facingMode |
[facingMode: WebcamFacingMode] |
Fired when the facing direction requests a new controlled value. |
update:deviceId |
[deviceId: string | null] |
Fired when the selected camera requests a new controlled value. |
statusChange |
[status: WebcamCaptureStatus, previous: WebcamCaptureStatus] |
Fired after every distinct status transition. |
error |
[failure: WebcamCaptureFailure] |
Fired when owned acquisition fails. |
streamChange |
[stream: WebcamMediaStreamLike | undefined] |
Fired when an owned stream starts or stops. |
capture |
[photo: WebcamCapturePhotoResult] |
Fired after a photo is captured. |
captureError |
[error: unknown] |
Fired when a capture fails to draw or encode. |
Slots
| Slot |
Slot props |
Description |
default |
WebcamCaptureSlotState |
Video, controls, photo, and status parts. Receives the camera state. |
Exposed
| Member |
Type |
Description |
status |
WebcamCaptureStatus |
Stream lifecycle. |
error |
WebcamCaptureFailure | undefined |
Last acquisition failure, or undefined. |
external |
boolean |
Whether the stream is supplied by the consumer through the stream prop. |
facingMode |
WebcamFacingMode |
Requested facing direction. |
deviceId |
string | undefined |
Selected device id, or undefined for the facing-mode default. |
devices |
readonly WebcamCaptureDevice[] |
Known cameras (filled after permission is granted). |
mirrored |
boolean |
Whether the preview and captures are mirrored. |
countdown |
number |
Seconds left in a running shutter countdown, or 0. |
capturing |
boolean |
Whether a capture is being encoded. |
element |
HTMLDivElement | null |
Rendered root element. |
stream |
WebcamMediaStreamLike | undefined |
Current stream (owned or external), or undefined. |
photo |
WebcamCapturePhotoResult | null |
Last captured photo, or null. |
photoUrl |
string | undefined |
Object URL of the last captured photo, owned and revoked by the root. |
start |
() => Promise<WebcamMediaStreamLike | undefined> |
Acquire the camera. Resolves with the stream, or undefined on failure or external mode. |
stop |
() => void |
Stop and release an owned stream. External streams are never stopped. |
switchCamera |
() => Promise<void> |
Toggle between user and environment, restarting an active owned stream. |
selectDevice |
(deviceId: string | undefined) => Promise<void> |
Select one camera, restarting an active owned stream. |
capture |
() => Promise<WebcamCapturePhotoResult | null> |
Capture one frame from the preview. Resolves with null when no frame is available. |
clearPhoto |
() => void |
Forget the last photo and revoke its object URL. |
WebcamCaptureShutter
Source: src/families/media/webcam-capture/webcam-capture-shutter.vue
Props
| Prop |
Type |
Default |
Description |
countdown |
number |
0 |
Seconds to count down (announced through WebcamCaptureStatusMessage) before capturing. |
ariaLabel |
string |
undefined |
Accessible name for icon-only buttons. The default slot renders messages.shutter. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before capturing. Call preventDefault() to cancel. |
Slots
| Slot |
Slot props |
Description |
default |
WebcamCaptureSlotState |
Button content. Receives the camera state, including the running countdown. |
Exposed
| Member |
Type |
Description |
disabled |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
Source: src/families/media/webcam-capture/webcam-capture-start-button.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string |
undefined |
Accessible name for icon-only buttons. The default slot renders messages.start. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before the action. Call preventDefault() to cancel it. |
Slots
| Slot |
Slot props |
Description |
default |
WebcamCaptureSlotState |
Button content. Receives the camera state. |
Exposed
| Member |
Type |
Description |
disabled |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
WebcamCaptureStatusMessage
Source: src/families/media/webcam-capture/webcam-capture-status-message.vue
Slots
| Slot |
Slot props |
Description |
default |
WebcamCaptureSlotState & { readonly message: string } |
Announcement content. Defaults to the localized status, countdown, or capture text. |
Exposed
| Member |
Type |
Description |
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
message |
— |
|
Source: src/families/media/webcam-capture/webcam-capture-stop-button.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string |
undefined |
Accessible name for icon-only buttons. The default slot renders messages.stop. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before the action. Call preventDefault() to cancel it. |
Slots
| Slot |
Slot props |
Description |
default |
WebcamCaptureSlotState |
Button content. Receives the camera state. |
Exposed
| Member |
Type |
Description |
disabled |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
WebcamCaptureSwitchCamera
Source: src/families/media/webcam-capture/webcam-capture-switch-camera.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string |
undefined |
Accessible name for icon-only buttons. The default slot renders messages.switchCamera. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before the action. Call preventDefault() to cancel it. |
Slots
| Slot |
Slot props |
Description |
default |
WebcamCaptureSlotState |
Button content. Receives the camera state. |
Exposed
| Member |
Type |
Description |
disabled |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
WebcamCaptureVideo
Source: src/families/media/webcam-capture/webcam-capture-video.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string |
undefined |
Accessible preview name. Defaults to messages.preview. |
Slots
| Slot |
Slot props |
Description |
default |
WebcamCaptureSlotState |
Fallback content for browsers without video support. |
Exposed
| Member |
Type |
Description |
element |
HTMLVideoElement | null |
Rendered native video element. |
Behavior
Normative state x input -> outcome table for webcam-capture-root.vue,
webcam-capture-video.vue, webcam-capture-start-button.vue,
webcam-capture-stop-button.vue, webcam-capture-switch-camera.vue,
webcam-capture-device-select.vue, webcam-capture-shutter.vue,
webcam-capture-photo.vue, and webcam-capture-status-message.vue
(@vizejs/ui/webcam-capture). Every row is proven by the named test.
The root either owns a camera stream (acquired through getUserMedia only after
start() or autoStart on mount) or displays a consumer stream passed through
stream, such as useUserMedia().stream from @vizejs/composable. Status values
and error codes are identical to the composable's MediaStreamStatus and
MediaStreamErrorCode. Consumer streams are never acquired, restarted, or stopped.
| ID |
State |
Input |
Outcome |
Evidence |
| W1 |
idle |
mount |
renders idle parts with localized defaults, a mirrored muted inline preview, a polite live region, and never requests permission |
renders an idle camera without requesting permission |
| W2 |
idle |
Start |
requests { audio, video: { ...constraints, facingMode } }, announces requesting/active, binds srcObject, and lists video inputs |
start acquires the camera, binds the preview, and lists devices |
| W3 |
requesting |
rejection / missing API |
maps errors with the composable table (permission-denied, unsupported, …), emits error, announces the localized reason, allows retry |
acquisition failures are normalized like useUserMedia |
| W4 |
active (owned) |
Stop / all tracks end / late stream |
stops owned tracks, unbinds the preview, returns to idle; a stream resolved after stop is released immediately |
stop and ended tracks release owned streams, and late streams are discarded |
| W5 |
active (owned) |
Switch camera / device select |
toggles facing mode (mirroring follows user) or selects an exact device, emits update:*, and restarts the owned stream |
switching cameras and selecting devices restart an owned stream |
| W6 |
external stream |
render / start / stop / unmount |
shows the consumer stream as active, disables owned controls, never calls getUserMedia, never stops consumer tracks |
external streams are displayed but never acquired or stopped |
| W7 |
active |
Shutter |
draws a mirrored, center-cropped, downscaled frame, encodes with captureType/captureQuality, renders the photo, revokes old URLs |
the shutter captures a mirrored, cropped frame and owns the photo URL |
| W8 |
idle / no frame |
capture |
captures nothing while idle; draw and encode failures emit captureError with typed diagnostics |
capture failures emit captureError and a missing frame captures nothing |
| W9 |
active |
Shutter with countdown |
announces each remaining second, disables the shutter, captures after the countdown; stop cancels it |
countdowns announce each second, block the shutter, and cancel on stop |
| W10 |
any |
messages |
every default label and announcement is replaced by the typed messages prop |
messages localize every default label and announcement |
| W11 |
autoStart |
mount / devicechange |
requests on mount (client only) and refreshes the device list on devicechange; mirrored overrides the facing-mode default |
autoStart requests on mount and devicechange refreshes the list |
| W12 |
active with photo |
unmount |
stops owned tracks and revokes the photo object URL |
unmounting stops owned tracks and revokes the photo URL |
| W13 |
controls |
click with preventDefault() |
leaves the camera untouched |
buttons honor preventDefault from click listeners |
| W14 |
missing provider |
setup |
parts fail closed with the shared context diagnostic |
compound parts require a matching root provider |
| W15 |
helpers |
pure functions |
error normalization, constraints, device lists, message merging, and capture geometry are deterministic |
webcam-capture-media.test.ts |
| W16 |
SSR |
isolated requests |
idle markup is byte-identical and autoStart never touches devices on the server |
renders byte-identical idle markup without requesting the camera on the server |
| W17 |
SSR / hydration |
hydrate |
server markup hydrates without warnings or node replacement |
hydrates idle camera markup without warnings or node replacement |
| W18 |
types |
compile |
statuses and codes match the composable, native MediaStream/MediaDevices are structurally accepted, options are closed |
webcam-capture.types.test-d.ts |
Mirroring is published as data-mirrored and --vize-ui-webcam-capture-scale-x
(-1 or 1); apply it with transform: scaleX(var(--vize-ui-webcam-capture-scale-x)).
Browsers only accept native MediaStreams as srcObject; the root binds the raw
(unproxied) stream and skips structural stand-ins.