0
# Device and Sensors
1
2
Components for accessing device information, sensors, and hardware capabilities.
3
4
## Capabilities
5
6
### UseBattery Component
7
8
Provides battery status information from the Battery Status API.
9
10
```typescript { .api }
11
/**
12
* Component that provides battery status information
13
* @example
14
* <UseBattery v-slot="{ charging, level, dischargingTime }">
15
* <div>Battery: {{ Math.round(level * 100) }}% {{ charging ? 'charging' : 'discharging' }}</div>
16
* </UseBattery>
17
*/
18
interface UseBatteryProps {
19
// No props - uses browser Battery API directly
20
}
21
22
/** Slot data exposed by UseBattery component */
23
interface BatteryState {
24
/** Whether the battery is charging */
25
charging: Ref<boolean>;
26
/** Time remaining until charged (seconds) */
27
chargingTime: Ref<number>;
28
/** Time remaining until discharged (seconds) */
29
dischargingTime: Ref<number>;
30
/** Battery charge level (0-1) */
31
level: Ref<number>;
32
/** Whether the Battery API is supported */
33
isSupported: Ref<boolean>;
34
}
35
```
36
37
**Usage Examples:**
38
39
```vue
40
<template>
41
<!-- Basic battery info -->
42
<UseBattery v-slot="{ charging, level, isSupported }">
43
<div v-if="isSupported" class="battery-info">
44
<div class="battery-level" :style="{ width: level * 100 + '%' }"></div>
45
<span>{{ Math.round(level * 100) }}% {{ charging ? '🔌' : '🔋' }}</span>
46
</div>
47
<div v-else>Battery API not supported</div>
48
</UseBattery>
49
50
<!-- Detailed battery info -->
51
<UseBattery v-slot="{ charging, level, chargingTime, dischargingTime }">
52
<div class="detailed-battery">
53
<h3>Battery Status</h3>
54
<p>Level: {{ Math.round(level * 100) }}%</p>
55
<p>Status: {{ charging ? 'Charging' : 'Discharging' }}</p>
56
<p v-if="charging && chargingTime !== Infinity">
57
Time to full: {{ Math.round(chargingTime / 60) }} minutes
58
</p>
59
<p v-if="!charging && dischargingTime !== Infinity">
60
Time remaining: {{ Math.round(dischargingTime / 60) }} minutes
61
</p>
62
</div>
63
</UseBattery>
64
</template>
65
66
<script setup>
67
import { UseBattery } from '@vueuse/components';
68
</script>
69
70
<style>
71
.battery-info {
72
position: relative;
73
width: 200px;
74
height: 30px;
75
border: 2px solid #333;
76
border-radius: 4px;
77
overflow: hidden;
78
display: flex;
79
align-items: center;
80
justify-content: center;
81
}
82
83
.battery-level {
84
position: absolute;
85
left: 0;
86
top: 0;
87
height: 100%;
88
background: linear-gradient(90deg, #4caf50, #8bc34a);
89
transition: width 0.3s ease;
90
}
91
</style>
92
```
93
94
### UseDeviceMotion Component
95
96
Provides device motion sensor data including acceleration and rotation.
97
98
```typescript { .api }
99
/**
100
* Component that provides device motion sensor data
101
* @example
102
* <UseDeviceMotion v-slot="{ acceleration, rotationRate }">
103
* <div>Acceleration: {{ acceleration.x.toFixed(2) }}, {{ acceleration.y.toFixed(2) }}</div>
104
* </UseDeviceMotion>
105
*/
106
interface UseDeviceMotionProps {
107
// No props - uses browser DeviceMotion API directly
108
}
109
110
/** Slot data exposed by UseDeviceMotion component */
111
interface DeviceMotionState {
112
/** Device acceleration data */
113
acceleration: Ref<DeviceMotionAcceleration | null>;
114
/** Device acceleration including gravity */
115
accelerationIncludingGravity: Ref<DeviceMotionAcceleration | null>;
116
/** Device rotation rate data */
117
rotationRate: Ref<DeviceMotionRotationRate | null>;
118
/** Motion interval in milliseconds */
119
interval: Ref<number>;
120
/** Whether the DeviceMotion API is supported */
121
isSupported: Ref<boolean>;
122
}
123
124
interface DeviceMotionAcceleration {
125
/** Acceleration along x-axis (m/s²) */
126
x: number | null;
127
/** Acceleration along y-axis (m/s²) */
128
y: number | null;
129
/** Acceleration along z-axis (m/s²) */
130
z: number | null;
131
}
132
133
interface DeviceMotionRotationRate {
134
/** Rotation around x-axis (degrees/second) */
135
alpha: number | null;
136
/** Rotation around y-axis (degrees/second) */
137
beta: number | null;
138
/** Rotation around z-axis (degrees/second) */
139
gamma: number | null;
140
}
141
```
142
143
**Usage Examples:**
144
145
```vue
146
<template>
147
<!-- Basic motion tracking -->
148
<UseDeviceMotion v-slot="{ acceleration, rotationRate, isSupported }">
149
<div v-if="isSupported" class="motion-display">
150
<h3>Device Motion</h3>
151
<div v-if="acceleration">
152
<h4>Acceleration (m/s²):</h4>
153
<p>X: {{ acceleration.x?.toFixed(2) ?? 'N/A' }}</p>
154
<p>Y: {{ acceleration.y?.toFixed(2) ?? 'N/A' }}</p>
155
<p>Z: {{ acceleration.z?.toFixed(2) ?? 'N/A' }}</p>
156
</div>
157
<div v-if="rotationRate">
158
<h4>Rotation Rate (°/s):</h4>
159
<p>Alpha: {{ rotationRate.alpha?.toFixed(2) ?? 'N/A' }}</p>
160
<p>Beta: {{ rotationRate.beta?.toFixed(2) ?? 'N/A' }}</p>
161
<p>Gamma: {{ rotationRate.gamma?.toFixed(2) ?? 'N/A' }}</p>
162
</div>
163
</div>
164
<div v-else>Device motion not supported</div>
165
</UseDeviceMotion>
166
167
<!-- Motion with gravity -->
168
<UseDeviceMotion v-slot="{ accelerationIncludingGravity, interval }">
169
<div class="gravity-motion">
170
<h3>Motion with Gravity</h3>
171
<p>Update interval: {{ interval }}ms</p>
172
<div v-if="accelerationIncludingGravity">
173
<p>X: {{ accelerationIncludingGravity.x?.toFixed(3) }}</p>
174
<p>Y: {{ accelerationIncludingGravity.y?.toFixed(3) }}</p>
175
<p>Z: {{ accelerationIncludingGravity.z?.toFixed(3) }}</p>
176
</div>
177
</div>
178
</UseDeviceMotion>
179
</template>
180
181
<script setup>
182
import { UseDeviceMotion } from '@vueuse/components';
183
</script>
184
```
185
186
### UseDeviceOrientation Component
187
188
Provides device orientation data including compass heading and device tilt.
189
190
```typescript { .api }
191
/**
192
* Component that provides device orientation data
193
* @example
194
* <UseDeviceOrientation v-slot="{ alpha, beta, gamma, absolute }">
195
* <div>Heading: {{ alpha }}° Tilt: {{ beta }}°, {{ gamma }}°</div>
196
* </UseDeviceOrientation>
197
*/
198
interface UseDeviceOrientationProps {
199
// No props - uses browser DeviceOrientation API directly
200
}
201
202
/** Slot data exposed by UseDeviceOrientation component */
203
interface DeviceOrientationState {
204
/** Compass heading (0-360°) */
205
alpha: Ref<number | null>;
206
/** Front-to-back tilt (-180 to 180°) */
207
beta: Ref<number | null>;
208
/** Left-to-right tilt (-90 to 90°) */
209
gamma: Ref<number | null>;
210
/** Whether orientation is absolute (true north) */
211
absolute: Ref<boolean>;
212
/** Whether the DeviceOrientation API is supported */
213
isSupported: Ref<boolean>;
214
}
215
```
216
217
**Usage Examples:**
218
219
```vue
220
<template>
221
<!-- Basic orientation -->
222
<UseDeviceOrientation v-slot="{ alpha, beta, gamma, absolute, isSupported }">
223
<div v-if="isSupported" class="orientation-display">
224
<h3>Device Orientation</h3>
225
<div class="compass" :style="{ transform: `rotate(${alpha || 0}deg)` }">
226
<div class="compass-needle"></div>
227
</div>
228
<p>Compass: {{ alpha?.toFixed(1) ?? 'N/A' }}° {{ absolute ? '(True North)' : '(Magnetic)' }}</p>
229
<p>Pitch: {{ beta?.toFixed(1) ?? 'N/A' }}°</p>
230
<p>Roll: {{ gamma?.toFixed(1) ?? 'N/A' }}°</p>
231
</div>
232
<div v-else>Device orientation not supported</div>
233
</UseDeviceOrientation>
234
235
<!-- Tilt indicator -->
236
<UseDeviceOrientation v-slot="{ beta, gamma }">
237
<div class="tilt-indicator">
238
<div
239
class="tilt-ball"
240
:style="{
241
transform: `translateX(${(gamma || 0) * 2}px) translateY(${(beta || 0) * 2}px)`
242
}"
243
></div>
244
<p>Tilt: {{ beta?.toFixed(1) ?? 'N/A' }}°, {{ gamma?.toFixed(1) ?? 'N/A' }}°</p>
245
</div>
246
</UseDeviceOrientation>
247
</template>
248
249
<script setup>
250
import { UseDeviceOrientation } from '@vueuse/components';
251
</script>
252
253
<style>
254
.compass {
255
width: 100px;
256
height: 100px;
257
border: 2px solid #333;
258
border-radius: 50%;
259
position: relative;
260
margin: 20px auto;
261
}
262
263
.compass-needle {
264
position: absolute;
265
top: 10%;
266
left: 50%;
267
width: 2px;
268
height: 40%;
269
background: red;
270
transform: translateX(-50%);
271
transform-origin: bottom center;
272
}
273
274
.tilt-indicator {
275
width: 200px;
276
height: 200px;
277
border: 2px solid #333;
278
border-radius: 10px;
279
position: relative;
280
margin: 20px auto;
281
overflow: hidden;
282
}
283
284
.tilt-ball {
285
position: absolute;
286
top: 50%;
287
left: 50%;
288
width: 20px;
289
height: 20px;
290
background: #2196f3;
291
border-radius: 50%;
292
transform: translate(-50%, -50%);
293
transition: transform 0.1s ease;
294
}
295
</style>
296
```
297
298
### UseDevicePixelRatio Component
299
300
Tracks device pixel ratio changes.
301
302
```typescript { .api }
303
/**
304
* Component that tracks device pixel ratio
305
* @example
306
* <UseDevicePixelRatio v-slot="{ pixelRatio }">
307
* <div>Pixel Ratio: {{ pixelRatio }}</div>
308
* </UseDevicePixelRatio>
309
*/
310
interface UseDevicePixelRatioProps {
311
// No props - uses browser devicePixelRatio API
312
}
313
314
/** Slot data exposed by UseDevicePixelRatio component */
315
interface UseDevicePixelRatioReturn {
316
/** Current device pixel ratio */
317
pixelRatio: Ref<number>;
318
}
319
```
320
321
**Usage Examples:**
322
323
```vue
324
<template>
325
<!-- Basic pixel ratio -->
326
<UseDevicePixelRatio v-slot="{ pixelRatio }">
327
<div class="pixel-ratio-info">
328
<h3>Display Information</h3>
329
<p>Device Pixel Ratio: {{ pixelRatio }}</p>
330
<p>Display Type: {{ getDisplayType(pixelRatio) }}</p>
331
</div>
332
</UseDevicePixelRatio>
333
334
<!-- Responsive images based on pixel ratio -->
335
<UseDevicePixelRatio v-slot="{ pixelRatio }">
336
<img
337
:src="getImageSrc(pixelRatio)"
338
:alt="`Image for ${pixelRatio}x display`"
339
class="responsive-image"
340
/>
341
</UseDevicePixelRatio>
342
</template>
343
344
<script setup>
345
import { UseDevicePixelRatio } from '@vueuse/components';
346
347
function getDisplayType(ratio) {
348
if (ratio >= 3) return 'Ultra High DPI';
349
if (ratio >= 2) return 'High DPI (Retina)';
350
if (ratio >= 1.5) return 'Medium DPI';
351
return 'Standard DPI';
352
}
353
354
function getImageSrc(ratio) {
355
if (ratio >= 2) return '/images/logo@2x.png';
356
return '/images/logo.png';
357
}
358
</script>
359
```
360
361
### UseDevicesList Component
362
363
Lists available media devices (cameras, microphones, speakers).
364
365
```typescript { .api }
366
/**
367
* Component that lists available media devices
368
* @example
369
* <UseDevicesList v-slot="{ videoInputs, audioInputs, audioOutputs }">
370
* <div>Cameras: {{ videoInputs.length }}, Mics: {{ audioInputs.length }}</div>
371
* </UseDevicesList>
372
*/
373
interface UseDevicesListProps {
374
/** Request permissions for device access @default false */
375
requestPermissions?: boolean;
376
/** Device constraints for permission request */
377
constraints?: MediaStreamConstraints;
378
/** Device change callback */
379
onUpdated?: (devices: MediaDeviceInfo[]) => void;
380
}
381
382
/** Slot data exposed by UseDevicesList component */
383
interface UseDevicesListReturn {
384
/** All available devices */
385
devices: Ref<MediaDeviceInfo[]>;
386
/** Video input devices (cameras) */
387
videoInputs: Ref<MediaDeviceInfo[]>;
388
/** Audio input devices (microphones) */
389
audioInputs: Ref<MediaDeviceInfo[]>;
390
/** Audio output devices (speakers) */
391
audioOutputs: Ref<MediaDeviceInfo[]>;
392
/** Whether the MediaDevices API is supported */
393
isSupported: Ref<boolean>;
394
/** Permissions granted status */
395
permissionGranted: Ref<boolean>;
396
/** Refresh device list */
397
ensurePermissions: () => Promise<boolean>;
398
/** Update device list */
399
update: () => Promise<void>;
400
}
401
402
interface MediaDeviceInfo {
403
deviceId: string;
404
kind: 'videoinput' | 'audioinput' | 'audiooutput';
405
label: string;
406
groupId: string;
407
}
408
409
interface MediaStreamConstraints {
410
video?: boolean | MediaTrackConstraints;
411
audio?: boolean | MediaTrackConstraints;
412
}
413
```
414
415
**Usage Examples:**
416
417
```vue
418
<template>
419
<!-- Basic device list -->
420
<UseDevicesList v-slot="{ videoInputs, audioInputs, audioOutputs, isSupported }">
421
<div v-if="isSupported" class="devices-list">
422
<h3>Available Devices</h3>
423
424
<div class="device-category">
425
<h4>Cameras ({{ videoInputs.length }})</h4>
426
<ul>
427
<li v-for="device in videoInputs" :key="device.deviceId">
428
{{ device.label || `Camera ${device.deviceId.slice(0, 8)}...` }}
429
</li>
430
</ul>
431
</div>
432
433
<div class="device-category">
434
<h4>Microphones ({{ audioInputs.length }})</h4>
435
<ul>
436
<li v-for="device in audioInputs" :key="device.deviceId">
437
{{ device.label || `Microphone ${device.deviceId.slice(0, 8)}...` }}
438
</li>
439
</ul>
440
</div>
441
442
<div class="device-category">
443
<h4>Speakers ({{ audioOutputs.length }})</h4>
444
<ul>
445
<li v-for="device in audioOutputs" :key="device.deviceId">
446
{{ device.label || `Speaker ${device.deviceId.slice(0, 8)}...` }}
447
</li>
448
</ul>
449
</div>
450
</div>
451
<div v-else>Media devices not supported</div>
452
</UseDevicesList>
453
454
<!-- With permissions -->
455
<UseDevicesList
456
:request-permissions="true"
457
:constraints="{ video: true, audio: true }"
458
v-slot="{ devices, permissionGranted, ensurePermissions, update }"
459
>
460
<div class="permissions-devices">
461
<div v-if="!permissionGranted" class="permission-request">
462
<p>Camera and microphone access required</p>
463
<button @click="ensurePermissions">Grant Permissions</button>
464
</div>
465
<div v-else class="devices-granted">
466
<p>Found {{ devices.length }} devices</p>
467
<button @click="update">Refresh List</button>
468
<div v-for="device in devices" :key="device.deviceId" class="device-item">
469
<span class="device-type">{{ device.kind }}</span>
470
<span class="device-label">{{ device.label }}</span>
471
</div>
472
</div>
473
</div>
474
</UseDevicesList>
475
</template>
476
477
<script setup>
478
import { UseDevicesList } from '@vueuse/components';
479
</script>
480
481
<style>
482
.device-category {
483
margin: 15px 0;
484
}
485
486
.device-category h4 {
487
margin: 5px 0;
488
color: #666;
489
}
490
491
.device-category ul {
492
list-style: none;
493
padding: 0;
494
}
495
496
.device-category li {
497
padding: 5px 0;
498
border-bottom: 1px solid #eee;
499
}
500
501
.device-item {
502
display: flex;
503
justify-content: space-between;
504
padding: 8px;
505
border: 1px solid #ddd;
506
margin: 5px 0;
507
border-radius: 4px;
508
}
509
510
.device-type {
511
font-weight: bold;
512
text-transform: capitalize;
513
}
514
515
.permission-request {
516
text-align: center;
517
padding: 20px;
518
border: 2px dashed #ccc;
519
border-radius: 8px;
520
}
521
</style>
522
```
523
524
## Type Definitions
525
526
```typescript { .api }
527
/** Common types used across device and sensor components */
528
type MaybeRefOrGetter<T> = T | Ref<T> | (() => T);
529
530
interface RenderableComponent {
531
/** The element that the component should be rendered as @default 'div' */
532
as?: object | string;
533
}
534
535
/** Battery API types */
536
interface BatteryManager {
537
charging: boolean;
538
chargingTime: number;
539
dischargingTime: number;
540
level: number;
541
onchargingchange: ((this: BatteryManager, ev: Event) => any) | null;
542
onchargingtimechange: ((this: BatteryManager, ev: Event) => any) | null;
543
ondischargingtimechange: ((this: BatteryManager, ev: Event) => any) | null;
544
onlevelchange: ((this: BatteryManager, ev: Event) => any) | null;
545
}
546
547
/** Device motion and orientation types */
548
interface DeviceMotionEvent extends Event {
549
acceleration: DeviceMotionAcceleration | null;
550
accelerationIncludingGravity: DeviceMotionAcceleration | null;
551
rotationRate: DeviceMotionRotationRate | null;
552
interval: number;
553
}
554
555
interface DeviceOrientationEvent extends Event {
556
alpha: number | null;
557
beta: number | null;
558
gamma: number | null;
559
absolute: boolean;
560
}
561
```