92 lines
2.6 KiB
Vue
Raw Permalink Normal View History

2026-05-31 13:21:13 +02:00
<script setup lang="ts">
import { useDraggable, useEventListener, useLocalStorage } from '@vueuse/core'
import { computed, onMounted, ref, watchEffect } from 'vue'
import { recorder } from '../logic/recording'
import { currentCamera } from '../state'
const size = useLocalStorage('slidev-webcam-size', Math.round(Math.min(window.innerHeight, (window.innerWidth) / 8)))
const position = useLocalStorage('slidev-webcam-pos', {
x: window.innerWidth - size.value - 30,
y: window.innerHeight - size.value - 30,
}, { deep: true })
const frame = ref<HTMLDivElement | undefined>()
const handler = ref<HTMLDivElement | undefined>()
const video = ref<HTMLVideoElement | undefined>()
const { streamCamera, showAvatar } = recorder
const { style: containerStyle } = useDraggable(frame, {
initialValue: position,
onMove({ x, y }) {
position.value.x = x
position.value.y = y
},
})
const { isDragging: handlerDown } = useDraggable(handler, {
onMove({ x, y }) {
size.value = Math.max(10, Math.min(x - position.value.x, y - position.value.y) / 0.8536)
},
})
watchEffect(() => {
if (video.value)
video.value.srcObject = streamCamera.value!
}, { flush: 'post' })
const frameStyle = computed(() => ({
width: `${size.value}px`,
height: `${size.value}px`,
}))
const handleStyle = computed(() => ({
width: '14px',
height: '14px',
// 0.5 + 0.5 / sqrt(2)
top: `${size.value * 0.8536 - 7}px`,
left: `${size.value * 0.8536 - 7}px`,
cursor: 'nwse-resize',
}))
function fixPosition() {
// move back if the camera is outside of the canvas
if (position.value.x >= window.innerWidth)
position.value.x = window.innerWidth - size.value - 30
if (position.value.y >= window.innerHeight)
position.value.y = window.innerHeight - size.value - 30
}
useEventListener('resize', fixPosition)
onMounted(fixPosition)
</script>
<template>
<div
v-if="streamCamera && showAvatar && currentCamera !== 'none'"
class="fixed z-camera"
:style="containerStyle"
>
<div
ref="frame"
class="rounded-full shadow bg-gray-400 bg-opacity-10 overflow-hidden object-cover"
:style="frameStyle"
>
<video
ref="video"
autoplay
muted
volume="0"
class="object-cover min-w-full min-h-full rounded-full"
style="transform: rotateY(180deg);"
/>
</div>
<div
ref="handler"
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"
:style="handleStyle"
:class="handlerDown ? '!opacity-100' : ''"
/>
</div>
</template>