207 lines
4.8 KiB
Vue
207 lines
4.8 KiB
Vue
<template>
|
|
<div
|
|
v-show="isVisible"
|
|
:class="['floating-video', { minimized: isMinimized }]"
|
|
class="!aspect-video"
|
|
:style="[positionStyle, containerStyle]"
|
|
@touchstart.prevent="handleTouchStart"
|
|
@touchmove.prevent="handleTouchMove"
|
|
@touchend="handleTouchEnd"
|
|
>
|
|
<video
|
|
ref="videoRef"
|
|
class="video-player "
|
|
controls
|
|
@loadedmetadata="handleVideoMetadata"
|
|
>
|
|
<source src="@/static/video/example.mp4" type="video/mp4" />
|
|
您的浏览器不支持 HTML5 视频。
|
|
</video>
|
|
<div class="control-bar">
|
|
<div class="minimize-btn" @click="toggleMinimize">
|
|
<span v-if="isMinimized">⤢</span>
|
|
<span v-else>⤓</span>
|
|
</div>
|
|
<div class="close-btn" @click="closeVideo">✕</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
|
|
|
const props = defineProps({
|
|
isVisible: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
})
|
|
|
|
const emit = defineEmits(['close'])
|
|
|
|
const videoRef = ref(null)
|
|
const aspectRatio = ref(16 / 9) // 默认宽高比
|
|
const isMinimized = ref(false)
|
|
const position = ref({ x: 20, y: 20 })
|
|
const isDragging = ref(false)
|
|
const dragStart = ref({ x: 0, y: 0 })
|
|
|
|
// 初始化窗口尺寸为0
|
|
const windowSize = ref({
|
|
width: 0,
|
|
height: 0
|
|
})
|
|
|
|
const handleVideoMetadata = () => {
|
|
const video = videoRef.value
|
|
if (video.videoWidth && video.videoHeight) {
|
|
aspectRatio.value = video.videoWidth / video.videoHeight
|
|
}
|
|
}
|
|
|
|
const containerDimensions = computed(() => {
|
|
const baseWidth = isMinimized.value ? 150 : 300
|
|
const height = baseWidth / aspectRatio.value
|
|
return { width: baseWidth, height }
|
|
})
|
|
|
|
const containerStyle = computed(() => {
|
|
return {
|
|
width: `${containerDimensions.value.width}px`,
|
|
height: `${containerDimensions.value.height}px`
|
|
}
|
|
})
|
|
|
|
const positionStyle = computed(() => ({
|
|
transform: `translate3d(${position.value.x}px, ${position.value.y}px, 0)`
|
|
}))
|
|
|
|
const handleTouchStart = (event) => {
|
|
isDragging.value = true
|
|
const touch = event.touches[0]
|
|
dragStart.value = {
|
|
x: touch.clientX - position.value.x,
|
|
y: touch.clientY - position.value.y
|
|
}
|
|
}
|
|
|
|
const handleTouchMove = (event) => {
|
|
if (!isDragging.value) return
|
|
|
|
const touch = event.touches[0]
|
|
let newX = touch.clientX - dragStart.value.x
|
|
let newY = touch.clientY - dragStart.value.y
|
|
|
|
// 添加边界检测
|
|
const maxX = windowSize.value.width - containerDimensions.value.width
|
|
const maxY = windowSize.value.height - containerDimensions.value.height
|
|
|
|
newX = Math.max(0, Math.min(newX, maxX))
|
|
newY = Math.max(0, Math.min(newY, maxY))
|
|
|
|
requestAnimationFrame(() => {
|
|
position.value = { x: newX, y: newY }
|
|
})
|
|
}
|
|
|
|
const handleTouchEnd = () => {
|
|
isDragging.value = false
|
|
}
|
|
|
|
const toggleMinimize = () => {
|
|
isMinimized.value = !isMinimized.value
|
|
|
|
const maxX = windowSize.value.width - containerDimensions.value.width
|
|
const maxY = windowSize.value.height - containerDimensions.value.height
|
|
|
|
position.value = {
|
|
x: Math.max(0, Math.min(position.value.x, maxX)),
|
|
y: Math.max(0, Math.min(position.value.y, maxY))
|
|
}
|
|
}
|
|
|
|
const closeVideo = () => {
|
|
emit('close')
|
|
}
|
|
|
|
const handleResize = () => {
|
|
windowSize.value = {
|
|
width: window.innerWidth,
|
|
height: window.innerHeight
|
|
}
|
|
|
|
const maxX = windowSize.value.width - containerDimensions.value.width
|
|
const maxY = windowSize.value.height - containerDimensions.value.height
|
|
|
|
position.value = {
|
|
x: Math.max(0, Math.min(position.value.x, maxX)),
|
|
y: Math.max(0, Math.min(position.value.y, maxY))
|
|
}
|
|
}
|
|
|
|
// 在组件挂载后初始化窗口尺寸和事件监听
|
|
onMounted(() => {
|
|
// 初始化窗口尺寸
|
|
windowSize.value = {
|
|
width: window.innerWidth,
|
|
height: window.innerHeight
|
|
}
|
|
|
|
window.addEventListener('resize', handleResize)
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('resize', handleResize)
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* 样式部分保持不变 */
|
|
.floating-video {
|
|
position: fixed;
|
|
z-index: 1000;
|
|
background: #000;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
|
transition: width 0.3s ease, height 0.3s ease;
|
|
overflow: hidden;
|
|
transform: translate3d(0, 0, 0);
|
|
will-change: transform;
|
|
touch-action: none;
|
|
}
|
|
|
|
.video-player {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: fill!important;
|
|
}
|
|
|
|
.control-bar {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
display: flex;
|
|
gap: 8px;
|
|
padding: 8px;
|
|
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), transparent);
|
|
}
|
|
|
|
.minimize-btn,
|
|
.close-btn {
|
|
width: 24px;
|
|
height: 24px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
color: white;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.minimize-btn:hover,
|
|
.close-btn:hover {
|
|
background: rgba(255, 255, 255, 0.3);
|
|
}
|
|
</style> |