返回 slidev
WebCamera.vue
根目录 / packages / client / internals / WebCamera.vue
1 <script setup lang="ts">
2 import { useDraggable, useEventListener, useLocalStorage } from '@vueuse/core'
3 import { computed, onMounted, ref, watchEffect } from 'vue'
4 import { recorder } from '../logic/recording'
5 import { currentCamera } from '../state'
6
7 const size = useLocalStorage('slidev-webcam-size', Math.round(Math.min(window.innerHeight, (window.innerWidth) / 8)))
8 const position = useLocalStorage('slidev-webcam-pos', {
9 x: window.innerWidth - size.value - 30,
10 y: window.innerHeight - size.value - 30,
11 }, { deep: true })
12
13 const frame = ref<HTMLDivElement | undefined>()
14 const handler = ref<HTMLDivElement | undefined>()
15 const video = ref<HTMLVideoElement | undefined>()
16
17 const { streamCamera, showAvatar } = recorder
18
19 const { style: containerStyle } = useDraggable(frame, {
20 initialValue: position,
21 onMove({ x, y }) {
22 position.value.x = x
23 position.value.y = y
24 },
25 })
26 const { isDragging: handlerDown } = useDraggable(handler, {
27 onMove({ x, y }) {
28 size.value = Math.max(10, Math.min(x - position.value.x, y - position.value.y) / 0.8536)
29 },
30 })
31
32 watchEffect(() => {
33 if (video.value)
34 video.value.srcObject = streamCamera.value!
35 }, { flush: 'post' })
36
37 const frameStyle = computed(() => ({
38 width: `${size.value}px`,
39 height: `${size.value}px`,
40 }))
41
42 const handleStyle = computed(() => ({
43 width: '14px',
44 height: '14px',
45 // 0.5 + 0.5 / sqrt(2)
46 top: `${size.value * 0.8536 - 7}px`,
47 left: `${size.value * 0.8536 - 7}px`,
48 cursor: 'nwse-resize',
49 }))
50
51 function fixPosition() {
52 // move back if the camera is outside of the canvas
53 if (position.value.x >= window.innerWidth)
54 position.value.x = window.innerWidth - size.value - 30
55 if (position.value.y >= window.innerHeight)
56 position.value.y = window.innerHeight - size.value - 30
57 }
58
59 useEventListener('resize', fixPosition)
60 onMounted(fixPosition)
61 </script>
62
63 <template>
64 <div
65 v-if="streamCamera && showAvatar && currentCamera !== 'none'"
66 class="fixed z-camera"
67 :style="containerStyle"
68 >
69 <div
70 ref="frame"
71 class="rounded-full shadow bg-gray-400 bg-opacity-10 overflow-hidden object-cover"
72 :style="frameStyle"
73 >
74 <video
75 ref="video"
76 autoplay
77 muted
78 volume="0"
79 class="object-cover min-w-full min-h-full rounded-full"
80 style="transform: rotateY(180deg);"
81 />
82 </div>
83
84 <div
85 ref="handler"
86 class="absolute bottom-0 right-0 rounded-full bg-main shadow opacity-0 shadow z-dragging hover:opacity-100 dark:border dark:border-true-gray-700"
87 :style="handleStyle"
88 :class="handlerDown ? '!opacity-100' : ''"
89 />
90 </div>
91 </template>
92
92 lines Plain Text