时间线组件优化
- 时间线缩放滑块的数值提示限定小数点后两位 - 时间线缩放时,如果播放头在时间线范围内,以播放头为中心缩放,如果看不到播放头,则以时间线组件中心缩放 - 时间线在加载时自适应最小缩放值
This commit is contained in:
parent
cdff0950d9
commit
aaeb0c4f4f
|
|
@ -39,8 +39,8 @@ export function useTimeline(
|
|||
) {
|
||||
const FPS = 30
|
||||
const pxPerFrame = ref(2)
|
||||
const minPxPerFrame = 0.5
|
||||
const maxPxPerFrame = 3
|
||||
const minPxPerFrame = ref(0.5)
|
||||
const maxPxPerFrame = ref(3)
|
||||
const timelineContainer: Ref<HTMLElement | null> = ref(null)
|
||||
const isDragging = ref(false)
|
||||
const isPlayheadDragging = ref(false)
|
||||
|
|
@ -58,15 +58,54 @@ export function useTimeline(
|
|||
})
|
||||
|
||||
function updatePxPerFrame(): void {
|
||||
if (!timelineContainer.value)
|
||||
if (!timelineContainer.value || props.totalFrames === 0)
|
||||
return
|
||||
const containerWidth = timelineContainer.value.clientWidth - 20
|
||||
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 {
|
||||
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(() => {
|
||||
|
|
@ -105,10 +144,9 @@ export function useTimeline(
|
|||
}
|
||||
|
||||
const playheadPos: ComputedRef<number> = computed(() => {
|
||||
if (isPlayheadDragging.value) {
|
||||
return localCurrentFrame.value * pxPerFrame.value
|
||||
}
|
||||
return props.currentFrame * pxPerFrame.value
|
||||
const frame = isPlayheadDragging.value ? localCurrentFrame.value : props.currentFrame
|
||||
const clampedFrame = Math.max(0, Math.min(frame, props.totalFrames - 1))
|
||||
return clampedFrame * pxPerFrame.value
|
||||
})
|
||||
|
||||
const trackRows: ComputedRef<HazardRow[][]> = computed(() => {
|
||||
|
|
@ -281,7 +319,10 @@ export function useTimeline(
|
|||
if (!progressContainer)
|
||||
return
|
||||
|
||||
updatePxPerFrame()
|
||||
// 使用 nextTick 确保容器已渲染
|
||||
nextTick(() => {
|
||||
updatePxPerFrame()
|
||||
})
|
||||
|
||||
progressContainer.addEventListener('wheel', handleWheel, { passive: false })
|
||||
progressContainer.addEventListener('mousedown', handleMouseDown)
|
||||
|
|
@ -291,11 +332,32 @@ export function useTimeline(
|
|||
}
|
||||
|
||||
watch(() => props.totalFrames, () => {
|
||||
nextTick(() => {
|
||||
// 使用 setTimeout 避免频繁更新
|
||||
setTimeout(() => {
|
||||
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 {
|
||||
window.removeEventListener('mousemove', handleMouseMove)
|
||||
window.removeEventListener('mousemove', handlePlayheadDrag)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import type { Arrayable } from '@vueuse/core'
|
||||
import type { TimelineEmits, TimelineProps } from './timeline'
|
||||
import { ElScrollbar, ElSlider } from 'element-plus'
|
||||
import { computed, onMounted, onUnmounted } from 'vue'
|
||||
|
|
@ -55,9 +56,9 @@ onUnmounted(() => {
|
|||
:max="maxPxPerFrame"
|
||||
:step="0.5"
|
||||
:show-tooltip="true"
|
||||
:format-tooltip="(val: number) => `缩放: ${val}`"
|
||||
:format-tooltip="(val: number) => `缩放: ${val.toFixed(2)}`"
|
||||
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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue