0
# Window and Document
1
2
Components for tracking window and document state, focus, visibility, and dimensions.
3
4
## Capabilities
5
6
### UseWindowSize Component
7
8
Tracks window dimensions with reactive updates.
9
10
```typescript { .api }
11
/**
12
* Component that tracks window dimensions
13
* @example
14
* <UseWindowSize v-slot="{ width, height }">
15
* <div>Window size: {{ width }} Γ {{ height }}</div>
16
* </UseWindowSize>
17
*/
18
interface UseWindowSizeProps {
19
/** Initial width value @default Infinity */
20
initialWidth?: number;
21
/** Initial height value @default Infinity */
22
initialHeight?: number;
23
/** Listen to orientationchange event @default true */
24
listenOrientation?: boolean;
25
/** Include scrollbars in dimensions @default true */
26
includeScrollbar?: boolean;
27
/** Window object @default defaultWindow */
28
window?: Window;
29
}
30
31
/** Slot data exposed by UseWindowSize component */
32
interface UseWindowSizeReturn {
33
/** Current window width */
34
width: Ref<number>;
35
/** Current window height */
36
height: Ref<number>;
37
}
38
```
39
40
**Usage Examples:**
41
42
```vue
43
<template>
44
<!-- Basic window size tracking -->
45
<UseWindowSize v-slot="{ width, height }">
46
<div class="window-info">
47
<h3>Window Information</h3>
48
<div class="size-display">
49
<div class="dimension">
50
<span class="label">Width:</span>
51
<span class="value">{{ width }}px</span>
52
</div>
53
<div class="dimension">
54
<span class="label">Height:</span>
55
<span class="value">{{ height }}px</span>
56
</div>
57
<div class="dimension">
58
<span class="label">Aspect Ratio:</span>
59
<span class="value">{{ (width / height).toFixed(2) }}</span>
60
</div>
61
</div>
62
</div>
63
</UseWindowSize>
64
65
<!-- Responsive breakpoint detection -->
66
<UseWindowSize v-slot="{ width, height }">
67
<div class="breakpoint-info">
68
<h3>Responsive Breakpoints</h3>
69
<div class="breakpoint" :class="getBreakpointClass(width)">
70
<span class="breakpoint-name">{{ getBreakpointName(width) }}</span>
71
<span class="breakpoint-size">{{ width }}px</span>
72
</div>
73
<div class="orientation">
74
Orientation: {{ width > height ? 'Landscape' : 'Portrait' }}
75
</div>
76
</div>
77
</UseWindowSize>
78
79
<!-- Window size visualization -->
80
<UseWindowSize v-slot="{ width, height }">
81
<div class="size-viz">
82
<h3>Size Visualization</h3>
83
<div
84
class="window-preview"
85
:style="{
86
width: Math.max(50, width / 10) + 'px',
87
height: Math.max(30, height / 10) + 'px'
88
}"
89
>
90
<span class="preview-text">{{ width }}Γ{{ height }}</span>
91
</div>
92
</div>
93
</UseWindowSize>
94
</template>
95
96
<script setup>
97
import { UseWindowSize } from '@vueuse/components';
98
99
function getBreakpointName(width) {
100
if (width < 640) return 'Mobile';
101
if (width < 768) return 'Small Tablet';
102
if (width < 1024) return 'Tablet';
103
if (width < 1280) return 'Desktop';
104
return 'Large Desktop';
105
}
106
107
function getBreakpointClass(width) {
108
if (width < 640) return 'mobile';
109
if (width < 768) return 'sm';
110
if (width < 1024) return 'tablet';
111
if (width < 1280) return 'desktop';
112
return 'xl';
113
}
114
</script>
115
116
<style>
117
.window-info, .breakpoint-info, .size-viz {
118
border: 2px solid #ddd;
119
border-radius: 8px;
120
padding: 20px;
121
margin: 15px 0;
122
}
123
124
.size-display {
125
display: grid;
126
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
127
gap: 15px;
128
margin-top: 15px;
129
}
130
131
.dimension {
132
display: flex;
133
flex-direction: column;
134
align-items: center;
135
padding: 10px;
136
background: #f5f5f5;
137
border-radius: 6px;
138
}
139
140
.label {
141
font-size: 0.9em;
142
color: #666;
143
margin-bottom: 5px;
144
}
145
146
.value {
147
font-size: 1.5em;
148
font-weight: bold;
149
color: #2196f3;
150
}
151
152
.breakpoint {
153
display: flex;
154
justify-content: space-between;
155
align-items: center;
156
padding: 15px;
157
border-radius: 8px;
158
margin: 15px 0;
159
font-weight: bold;
160
}
161
162
.breakpoint.mobile { background: #ffebee; }
163
.breakpoint.sm { background: #e8f5e8; }
164
.breakpoint.tablet { background: #e3f2fd; }
165
.breakpoint.desktop { background: #f3e5f5; }
166
.breakpoint.xl { background: #fff3e0; }
167
168
.window-preview {
169
border: 2px solid #2196f3;
170
background: #e3f2fd;
171
border-radius: 4px;
172
display: flex;
173
align-items: center;
174
justify-content: center;
175
min-width: 50px;
176
min-height: 30px;
177
margin: 15px 0;
178
}
179
180
.preview-text {
181
font-size: 10px;
182
font-weight: bold;
183
color: #1976d2;
184
}
185
</style>
186
```
187
188
### UseWindowFocus Component
189
190
Tracks window focus state.
191
192
```typescript { .api }
193
/**
194
* Component that tracks window focus state
195
* @example
196
* <UseWindowFocus v-slot="{ focused }">
197
* <div>Window focused: {{ focused ? 'Yes' : 'No' }}</div>
198
* </UseWindowFocus>
199
*/
200
interface UseWindowFocusProps {
201
/** Window object @default defaultWindow */
202
window?: Window;
203
}
204
205
/** Slot data exposed by UseWindowFocus component */
206
interface UseWindowFocusReturn {
207
/** Whether window is focused */
208
focused: Ref<boolean>;
209
}
210
```
211
212
**Usage Examples:**
213
214
```vue
215
<template>
216
<!-- Basic focus tracking -->
217
<UseWindowFocus v-slot="{ focused }">
218
<div class="focus-indicator" :class="{ focused, blurred: !focused }">
219
<div class="status-icon">
220
{{ focused ? 'ποΈ' : 'π΄' }}
221
</div>
222
<div class="status-text">
223
Window is {{ focused ? 'focused' : 'blurred' }}
224
</div>
225
<div class="hint">
226
{{ focused ? 'You are viewing this tab' : 'Switch to another tab to see blur state' }}
227
</div>
228
</div>
229
</UseWindowFocus>
230
231
<!-- Focus-based features -->
232
<UseWindowFocus v-slot="{ focused }">
233
<div class="focus-features">
234
<h3>Focus-Aware Features</h3>
235
<div class="feature" :class="{ active: focused }">
236
<span class="feature-name">Auto-refresh</span>
237
<span class="feature-status">{{ focused ? 'Active' : 'Paused' }}</span>
238
</div>
239
<div class="feature" :class="{ active: focused }">
240
<span class="feature-name">Real-time updates</span>
241
<span class="feature-status">{{ focused ? 'Enabled' : 'Disabled' }}</span>
242
</div>
243
<div class="feature" :class="{ active: !focused }">
244
<span class="feature-name">Battery saver</span>
245
<span class="feature-status">{{ !focused ? 'Active' : 'Inactive' }}</span>
246
</div>
247
</div>
248
</UseWindowFocus>
249
250
<!-- Notification helper -->
251
<UseWindowFocus v-slot="{ focused }">
252
<div class="notification-helper">
253
<h3>Smart Notifications</h3>
254
<p v-if="!focused" class="notification-tip">
255
π Perfect time to show notifications - user is not actively viewing
256
</p>
257
<p v-else class="notification-tip">
258
π User is active - consider in-app notifications instead
259
</p>
260
<button @click="simulateNotification(focused)">
261
Test Notification Strategy
262
</button>
263
</div>
264
</UseWindowFocus>
265
</template>
266
267
<script setup>
268
import { UseWindowFocus } from '@vueuse/components';
269
270
function simulateNotification(focused) {
271
if (focused) {
272
alert('In-app notification (user is active)');
273
} else {
274
console.log('Browser notification sent (user is away)');
275
}
276
}
277
</script>
278
279
<style>
280
.focus-indicator {
281
padding: 20px;
282
border-radius: 8px;
283
text-align: center;
284
transition: all 0.3s;
285
border: 2px solid #ddd;
286
}
287
288
.focus-indicator.focused {
289
background: #e8f5e8;
290
border-color: #4caf50;
291
}
292
293
.focus-indicator.blurred {
294
background: #ffebee;
295
border-color: #f44336;
296
}
297
298
.status-icon {
299
font-size: 2em;
300
margin-bottom: 10px;
301
}
302
303
.status-text {
304
font-size: 1.2em;
305
font-weight: bold;
306
margin-bottom: 5px;
307
}
308
309
.hint {
310
font-size: 0.9em;
311
color: #666;
312
font-style: italic;
313
}
314
315
.focus-features {
316
border: 1px solid #ddd;
317
border-radius: 8px;
318
padding: 20px;
319
}
320
321
.feature {
322
display: flex;
323
justify-content: space-between;
324
align-items: center;
325
padding: 10px;
326
margin: 8px 0;
327
border-radius: 6px;
328
background: #f5f5f5;
329
transition: background 0.3s;
330
}
331
332
.feature.active {
333
background: #e8f5e8;
334
}
335
336
.feature-name {
337
font-weight: bold;
338
}
339
340
.feature-status {
341
font-size: 0.9em;
342
color: #666;
343
}
344
345
.notification-helper {
346
border: 1px solid #ddd;
347
border-radius: 8px;
348
padding: 20px;
349
}
350
351
.notification-tip {
352
padding: 10px;
353
border-radius: 6px;
354
background: #f0f8ff;
355
border-left: 4px solid #2196f3;
356
margin: 15px 0;
357
}
358
</style>
359
```
360
361
### UseDocumentVisibility Component
362
363
Tracks document visibility state using the Page Visibility API.
364
365
```typescript { .api }
366
/**
367
* Component that tracks document visibility state
368
* @example
369
* <UseDocumentVisibility v-slot="{ visibility }">
370
* <div>Page visibility: {{ visibility }}</div>
371
* </UseDocumentVisibility>
372
*/
373
interface UseDocumentVisibilityProps {
374
/** Document object @default defaultDocument */
375
document?: Document;
376
}
377
378
/** Slot data exposed by UseDocumentVisibility component */
379
interface UseDocumentVisibilityReturn {
380
/** Current document visibility state */
381
visibility: Ref<DocumentVisibilityState>;
382
}
383
384
type DocumentVisibilityState = 'visible' | 'hidden' | 'prerender';
385
```
386
387
**Usage Examples:**
388
389
```vue
390
<template>
391
<!-- Basic visibility tracking -->
392
<UseDocumentVisibility v-slot="{ visibility }">
393
<div class="visibility-tracker" :class="visibility">
394
<div class="visibility-indicator">
395
<span class="status-dot"></span>
396
<span class="status-text">{{ getVisibilityText(visibility) }}</span>
397
</div>
398
<div class="visibility-description">
399
{{ getVisibilityDescription(visibility) }}
400
</div>
401
</div>
402
</UseDocumentVisibility>
403
404
<!-- Performance optimization guide -->
405
<UseDocumentVisibility v-slot="{ visibility }">
406
<div class="performance-guide">
407
<h3>Performance Optimization</h3>
408
<div class="optimization-item" :class="{ active: visibility === 'visible' }">
409
<span class="opt-name">Animations</span>
410
<span class="opt-status">{{ visibility === 'visible' ? 'Running' : 'Paused' }}</span>
411
</div>
412
<div class="optimization-item" :class="{ active: visibility === 'visible' }">
413
<span class="opt-name">API Polling</span>
414
<span class="opt-status">{{ visibility === 'visible' ? 'Active' : 'Reduced' }}</span>
415
</div>
416
<div class="optimization-item" :class="{ active: visibility === 'hidden' }">
417
<span class="opt-name">Background Tasks</span>
418
<span class="opt-status">{{ visibility === 'hidden' ? 'Optimized' : 'Normal' }}</span>
419
</div>
420
</div>
421
</UseDocumentVisibility>
422
423
<!-- User engagement tracking -->
424
<UseDocumentVisibility v-slot="{ visibility }">
425
<div class="engagement-tracker">
426
<h3>User Engagement</h3>
427
<div class="engagement-stats">
428
<div class="stat">
429
<span class="stat-label">Current Status:</span>
430
<span class="stat-value" :class="visibility">{{ visibility }}</span>
431
</div>
432
<div class="stat">
433
<span class="stat-label">Engagement Level:</span>
434
<span class="stat-value">{{ getEngagementLevel(visibility) }}</span>
435
</div>
436
</div>
437
<div class="engagement-actions">
438
<button
439
@click="logEngagement(visibility)"
440
:disabled="visibility !== 'visible'"
441
>
442
Log User Action
443
</button>
444
</div>
445
</div>
446
</UseDocumentVisibility>
447
</template>
448
449
<script setup>
450
import { UseDocumentVisibility } from '@vueuse/components';
451
452
function getVisibilityText(visibility) {
453
switch (visibility) {
454
case 'visible': return 'Page is Visible';
455
case 'hidden': return 'Page is Hidden';
456
case 'prerender': return 'Page is Pre-rendering';
457
default: return 'Unknown State';
458
}
459
}
460
461
function getVisibilityDescription(visibility) {
462
switch (visibility) {
463
case 'visible':
464
return 'The page is currently visible and active.';
465
case 'hidden':
466
return 'The page is hidden (minimized, in background tab, etc.).';
467
case 'prerender':
468
return 'The page is being pre-rendered in the background.';
469
default:
470
return 'Unable to determine page visibility state.';
471
}
472
}
473
474
function getEngagementLevel(visibility) {
475
switch (visibility) {
476
case 'visible': return 'High';
477
case 'hidden': return 'Low';
478
case 'prerender': return 'None';
479
default: return 'Unknown';
480
}
481
}
482
483
function logEngagement(visibility) {
484
console.log(`User action logged - Page visibility: ${visibility}`);
485
}
486
</script>
487
488
<style>
489
.visibility-tracker {
490
padding: 20px;
491
border-radius: 8px;
492
border: 2px solid #ddd;
493
transition: all 0.3s;
494
}
495
496
.visibility-tracker.visible {
497
background: #e8f5e8;
498
border-color: #4caf50;
499
}
500
501
.visibility-tracker.hidden {
502
background: #ffebee;
503
border-color: #f44336;
504
}
505
506
.visibility-tracker.prerender {
507
background: #fff3e0;
508
border-color: #ff9800;
509
}
510
511
.visibility-indicator {
512
display: flex;
513
align-items: center;
514
gap: 10px;
515
font-size: 1.2em;
516
font-weight: bold;
517
margin-bottom: 10px;
518
}
519
520
.status-dot {
521
width: 12px;
522
height: 12px;
523
border-radius: 50%;
524
background: currentColor;
525
}
526
527
.visibility-description {
528
font-size: 0.9em;
529
color: #666;
530
}
531
532
.performance-guide, .engagement-tracker {
533
border: 1px solid #ddd;
534
border-radius: 8px;
535
padding: 20px;
536
}
537
538
.optimization-item, .stat {
539
display: flex;
540
justify-content: space-between;
541
align-items: center;
542
padding: 8px 12px;
543
margin: 5px 0;
544
border-radius: 6px;
545
background: #f5f5f5;
546
}
547
548
.optimization-item.active {
549
background: #e3f2fd;
550
}
551
552
.engagement-stats {
553
margin: 15px 0;
554
}
555
556
.stat-value.visible {
557
color: #4caf50;
558
font-weight: bold;
559
}
560
561
.stat-value.hidden {
562
color: #f44336;
563
font-weight: bold;
564
}
565
566
.stat-value.prerender {
567
color: #ff9800;
568
font-weight: bold;
569
}
570
</style>
571
```
572
573
### UsePageLeave Component
574
575
Detects when the cursor leaves the page viewport.
576
577
```typescript { .api }
578
/**
579
* Component that detects when cursor leaves page
580
* @example
581
* <UsePageLeave v-slot="{ isLeft }">
582
* <div>Cursor left page: {{ isLeft ? 'Yes' : 'No' }}</div>
583
* </UsePageLeave>
584
*/
585
interface UsePageLeaveProps {
586
/** Window object @default defaultWindow */
587
window?: Window;
588
}
589
590
/** Slot data exposed by UsePageLeave component */
591
interface UsePageLeaveReturn {
592
/** Whether cursor has left the page */
593
isLeft: Ref<boolean>;
594
}
595
```
596
597
**Usage Examples:**
598
599
```vue
600
<template>
601
<!-- Basic page leave detection -->
602
<UsePageLeave v-slot="{ isLeft }">
603
<div class="page-leave-indicator" :class="{ left: isLeft, present: !isLeft }">
604
<div class="cursor-status">
605
<span class="cursor-icon">{{ isLeft ? 'π€' : 'π' }}</span>
606
<span class="cursor-text">
607
Cursor {{ isLeft ? 'left the page' : 'is on the page' }}
608
</span>
609
</div>
610
</div>
611
</UsePageLeave>
612
613
<!-- Exit intent detection -->
614
<UsePageLeave v-slot="{ isLeft }">
615
<div class="exit-intent" v-if="isLeft">
616
<div class="exit-modal">
617
<h3>Wait! Don't leave yet! π</h3>
618
<p>Are you sure you want to leave? You might miss out on:</p>
619
<ul>
620
<li>β¨ Special offers</li>
621
<li>π― Personalized content</li>
622
<li>π Helpful resources</li>
623
</ul>
624
<div class="exit-actions">
625
<button @click="stayOnPage" class="stay-btn">Stay</button>
626
<button @click="dismissModal" class="leave-btn">Leave</button>
627
</div>
628
</div>
629
<div class="exit-overlay" @click="dismissModal"></div>
630
</div>
631
</UsePageLeave>
632
633
<!-- User engagement tracking -->
634
<UsePageLeave v-slot="{ isLeft }">
635
<div class="engagement-monitor">
636
<h3>Engagement Monitor</h3>
637
<div class="engagement-indicator" :class="{ engaged: !isLeft, disengaged: isLeft }">
638
<span class="status">{{ isLeft ? 'β οΈ Potential Exit' : 'β User Engaged' }}</span>
639
</div>
640
<div class="engagement-tips" v-if="isLeft">
641
<p>π‘ Consider:</p>
642
<ul>
643
<li>Showing exit-intent popup</li>
644
<li>Saving user progress</li>
645
<li>Triggering retention campaigns</li>
646
</ul>
647
</div>
648
</div>
649
</UsePageLeave>
650
</template>
651
652
<script setup>
653
import { ref } from 'vue';
654
import { UsePageLeave } from '@vueuse/components';
655
656
const showExitModal = ref(true);
657
658
function stayOnPage() {
659
showExitModal.value = false;
660
// Additional logic to engage user
661
}
662
663
function dismissModal() {
664
showExitModal.value = false;
665
}
666
</script>
667
668
<style>
669
.page-leave-indicator {
670
padding: 20px;
671
border-radius: 8px;
672
border: 2px solid #ddd;
673
text-align: center;
674
transition: all 0.3s;
675
}
676
677
.page-leave-indicator.present {
678
background: #e8f5e8;
679
border-color: #4caf50;
680
}
681
682
.page-leave-indicator.left {
683
background: #fff3cd;
684
border-color: #ffc107;
685
}
686
687
.cursor-status {
688
display: flex;
689
align-items: center;
690
justify-content: center;
691
gap: 10px;
692
font-size: 1.2em;
693
}
694
695
.cursor-icon {
696
font-size: 1.5em;
697
}
698
699
.exit-intent {
700
position: fixed;
701
top: 0;
702
left: 0;
703
right: 0;
704
bottom: 0;
705
z-index: 1000;
706
display: flex;
707
align-items: center;
708
justify-content: center;
709
}
710
711
.exit-overlay {
712
position: absolute;
713
top: 0;
714
left: 0;
715
right: 0;
716
bottom: 0;
717
background: rgba(0, 0, 0, 0.7);
718
}
719
720
.exit-modal {
721
position: relative;
722
background: white;
723
border-radius: 12px;
724
padding: 30px;
725
max-width: 400px;
726
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
727
text-align: center;
728
}
729
730
.exit-modal h3 {
731
color: #333;
732
margin-bottom: 15px;
733
}
734
735
.exit-modal ul {
736
text-align: left;
737
margin: 15px 0;
738
}
739
740
.exit-actions {
741
display: flex;
742
gap: 15px;
743
margin-top: 20px;
744
}
745
746
.stay-btn, .leave-btn {
747
flex: 1;
748
padding: 12px 20px;
749
border: none;
750
border-radius: 6px;
751
font-size: 16px;
752
cursor: pointer;
753
}
754
755
.stay-btn {
756
background: #4caf50;
757
color: white;
758
}
759
760
.leave-btn {
761
background: #f5f5f5;
762
color: #666;
763
}
764
765
.engagement-monitor {
766
border: 1px solid #ddd;
767
border-radius: 8px;
768
padding: 20px;
769
}
770
771
.engagement-indicator {
772
padding: 15px;
773
border-radius: 8px;
774
text-align: center;
775
font-weight: bold;
776
margin: 15px 0;
777
}
778
779
.engagement-indicator.engaged {
780
background: #e8f5e8;
781
color: #2e7d32;
782
}
783
784
.engagement-indicator.disengaged {
785
background: #fff3cd;
786
color: #f57c00;
787
}
788
789
.engagement-tips {
790
background: #f0f8ff;
791
border-left: 4px solid #2196f3;
792
padding: 15px;
793
margin-top: 15px;
794
}
795
796
.engagement-tips ul {
797
margin: 10px 0;
798
padding-left: 20px;
799
}
800
</style>
801
```
802
803
## Type Definitions
804
805
```typescript { .api }
806
/** Common types used across window and document components */
807
type MaybeRefOrGetter<T> = T | Ref<T> | (() => T);
808
809
interface RenderableComponent {
810
/** The element that the component should be rendered as @default 'div' */
811
as?: object | string;
812
}
813
814
/** Document visibility states */
815
type DocumentVisibilityState = 'visible' | 'hidden' | 'prerender';
816
817
/** Window size information */
818
interface WindowSize {
819
width: number;
820
height: number;
821
}
822
823
/** Configuration interfaces */
824
interface ConfigurableWindow {
825
/** Window object @default defaultWindow */
826
window?: Window;
827
}
828
829
interface ConfigurableDocument {
830
/** Document object @default defaultDocument */
831
document?: Document;
832
}
833
```