时间线组件优化
- 时间线缩放滑块的数值提示限定小数点后两位 - 时间线缩放时,如果播放头在时间线范围内,以播放头为中心缩放,如果看不到播放头,则以时间线组件中心缩放 - 时间线在加载时自适应最小缩放值
This commit is contained in:
parent
cdff0950d9
commit
aaeb0c4f4f
|
|
@ -39,8 +39,8 @@ export function useTimeline(
|
||||||
) {
|
) {
|
||||||
const FPS = 30
|
const FPS = 30
|
||||||
const pxPerFrame = ref(2)
|
const pxPerFrame = ref(2)
|
||||||
const minPxPerFrame = 0.5
|
const minPxPerFrame = ref(0.5)
|
||||||
const maxPxPerFrame = 3
|
const maxPxPerFrame = ref(3)
|
||||||
const timelineContainer: Ref<HTMLElement | null> = ref(null)
|
const timelineContainer: Ref<HTMLElement | null> = ref(null)
|
||||||
const isDragging = ref(false)
|
const isDragging = ref(false)
|
||||||
const isPlayheadDragging = ref(false)
|
const isPlayheadDragging = ref(false)
|
||||||
|
|
@ -58,15 +58,54 @@ export function useTimeline(
|
||||||
})
|
})
|
||||||
|
|
||||||
function updatePxPerFrame(): void {
|
function updatePxPerFrame(): void {
|
||||||
if (!timelineContainer.value)
|
if (!timelineContainer.value || props.totalFrames === 0)
|
||||||
return
|
return
|
||||||
const containerWidth = timelineContainer.value.clientWidth - 20
|
const containerWidth = timelineContainer.value.clientWidth - 20
|
||||||
const calculatedPxPerFrame = containerWidth / props.totalFrames
|
const calculatedPxPerFrame = containerWidth / props.totalFrames
|
||||||
pxPerFrame.value = Math.max(minPxPerFrame, Math.min(calculatedPxPerFrame, maxPxPerFrame))
|
// 计算最小缩放倍数,确保能看到视频全长
|
||||||
|
const newMinPxPerFrame = Math.max(0.1, containerWidth / props.totalFrames)
|
||||||
|
// 只在必要时更新,避免无限循环
|
||||||
|
if (Math.abs(newMinPxPerFrame - minPxPerFrame.value) > 0.01) {
|
||||||
|
minPxPerFrame.value = newMinPxPerFrame
|
||||||
|
}
|
||||||
|
// 最大值固定为3,不修改
|
||||||
|
pxPerFrame.value = Math.max(minPxPerFrame.value, Math.min(calculatedPxPerFrame, maxPxPerFrame.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
function setZoom(value: number): void {
|
function setZoom(value: number): void {
|
||||||
pxPerFrame.value = Math.max(minPxPerFrame, Math.min(value, maxPxPerFrame))
|
const oldPxPerFrame = pxPerFrame.value
|
||||||
|
const newPxPerFrame = Math.max(minPxPerFrame.value, Math.min(value, maxPxPerFrame.value))
|
||||||
|
|
||||||
|
if (oldPxPerFrame === newPxPerFrame || !timelineContainer.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
const container = timelineContainer.value
|
||||||
|
const containerWidth = container.clientWidth
|
||||||
|
const containerScrollLeft = container.scrollLeft
|
||||||
|
const playheadPosition = props.currentFrame * oldPxPerFrame
|
||||||
|
|
||||||
|
// 检查播放头是否在可见范围内
|
||||||
|
const playheadVisible = playheadPosition >= containerScrollLeft
|
||||||
|
&& playheadPosition <= containerScrollLeft + containerWidth
|
||||||
|
|
||||||
|
// 计算缩放前后的位置偏移
|
||||||
|
if (playheadVisible) {
|
||||||
|
// 以播放头为中心缩放
|
||||||
|
const playheadX = playheadPosition - containerScrollLeft
|
||||||
|
const newPlayheadPosition = props.currentFrame * newPxPerFrame
|
||||||
|
const newScrollLeft = newPlayheadPosition - playheadX
|
||||||
|
container.scrollLeft = newScrollLeft
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 以容器中心为中心缩放
|
||||||
|
const centerX = containerWidth / 2
|
||||||
|
const centerPosition = containerScrollLeft + centerX
|
||||||
|
const newCenterPosition = centerPosition * (newPxPerFrame / oldPxPerFrame)
|
||||||
|
container.scrollLeft = newCenterPosition - centerX
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最后更新缩放值
|
||||||
|
pxPerFrame.value = newPxPerFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeTicks: ComputedRef<TimeTick[]> = computed(() => {
|
const timeTicks: ComputedRef<TimeTick[]> = computed(() => {
|
||||||
|
|
@ -105,10 +144,9 @@ export function useTimeline(
|
||||||
}
|
}
|
||||||
|
|
||||||
const playheadPos: ComputedRef<number> = computed(() => {
|
const playheadPos: ComputedRef<number> = computed(() => {
|
||||||
if (isPlayheadDragging.value) {
|
const frame = isPlayheadDragging.value ? localCurrentFrame.value : props.currentFrame
|
||||||
return localCurrentFrame.value * pxPerFrame.value
|
const clampedFrame = Math.max(0, Math.min(frame, props.totalFrames - 1))
|
||||||
}
|
return clampedFrame * pxPerFrame.value
|
||||||
return props.currentFrame * pxPerFrame.value
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const trackRows: ComputedRef<HazardRow[][]> = computed(() => {
|
const trackRows: ComputedRef<HazardRow[][]> = computed(() => {
|
||||||
|
|
@ -281,7 +319,10 @@ export function useTimeline(
|
||||||
if (!progressContainer)
|
if (!progressContainer)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
// 使用 nextTick 确保容器已渲染
|
||||||
|
nextTick(() => {
|
||||||
updatePxPerFrame()
|
updatePxPerFrame()
|
||||||
|
})
|
||||||
|
|
||||||
progressContainer.addEventListener('wheel', handleWheel, { passive: false })
|
progressContainer.addEventListener('wheel', handleWheel, { passive: false })
|
||||||
progressContainer.addEventListener('mousedown', handleMouseDown)
|
progressContainer.addEventListener('mousedown', handleMouseDown)
|
||||||
|
|
@ -291,10 +332,31 @@ export function useTimeline(
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => props.totalFrames, () => {
|
watch(() => props.totalFrames, () => {
|
||||||
nextTick(() => {
|
// 使用 setTimeout 避免频繁更新
|
||||||
|
setTimeout(() => {
|
||||||
updatePxPerFrame()
|
updatePxPerFrame()
|
||||||
|
}, 100)
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
// 移除 resize 监听,避免无限循环
|
||||||
|
// 监听容器宽度变化
|
||||||
|
// let resizeObserver: ResizeObserver | null = null
|
||||||
|
|
||||||
|
// function handleResize(): void {
|
||||||
|
// updatePxPerFrame()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// watch(() => timelineContainer.value, (newVal, oldVal) => {
|
||||||
|
// if (newVal && !oldVal) {
|
||||||
|
// // 使用 ResizeObserver 代替 window resize 监听
|
||||||
|
// resizeObserver = new ResizeObserver(handleResize)
|
||||||
|
// resizeObserver.observe(newVal)
|
||||||
|
// }
|
||||||
|
// else if (!newVal && oldVal && resizeObserver) {
|
||||||
|
// resizeObserver.disconnect()
|
||||||
|
// resizeObserver = null
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
function initUnmounted(): void {
|
function initUnmounted(): void {
|
||||||
window.removeEventListener('mousemove', handleMouseMove)
|
window.removeEventListener('mousemove', handleMouseMove)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { Arrayable } from '@vueuse/core'
|
||||||
import type { TimelineEmits, TimelineProps } from './timeline'
|
import type { TimelineEmits, TimelineProps } from './timeline'
|
||||||
import { ElScrollbar, ElSlider } from 'element-plus'
|
import { ElScrollbar, ElSlider } from 'element-plus'
|
||||||
import { computed, onMounted, onUnmounted } from 'vue'
|
import { computed, onMounted, onUnmounted } from 'vue'
|
||||||
|
|
@ -55,9 +56,9 @@ onUnmounted(() => {
|
||||||
:max="maxPxPerFrame"
|
:max="maxPxPerFrame"
|
||||||
:step="0.5"
|
:step="0.5"
|
||||||
:show-tooltip="true"
|
:show-tooltip="true"
|
||||||
:format-tooltip="(val: number) => `缩放: ${val}`"
|
:format-tooltip="(val: number) => `缩放: ${val.toFixed(2)}`"
|
||||||
class="zoom-slider"
|
class="zoom-slider"
|
||||||
@change="(val: number | number[]) => setZoom(Array.isArray(val) ? val[0] : val)"
|
@update:model-value="(val: Arrayable<number>) => setZoom(Array.isArray(val) ? val[0] : val)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue