ui交互
This commit is contained in:
1 parent
6f7230458d
commit
ee7b411a01
1 file changed
+35
-12
+35
-12
@@ -25,7 +25,11 @@
|
|||||||
--border: #ddd;
|
--border: #ddd;
|
||||||
--error: #d13438;
|
--error: #d13438;
|
||||||
}
|
}
|
||||||
* { box-sizing: border-box; }
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
html, body {
|
html, body {
|
||||||
margin: 0; padding: 0; height: 100%; overflow: hidden;
|
margin: 0; padding: 0; height: 100%; overflow: hidden;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||||
@@ -135,6 +139,11 @@
|
|||||||
#timelineBar .tl-wrap { position: relative; flex: 1; padding-top: 20px; }
|
#timelineBar .tl-wrap { position: relative; flex: 1; padding-top: 20px; }
|
||||||
#timelineBar input[type="range"] {
|
#timelineBar input[type="range"] {
|
||||||
display: block; width: 100%; margin: 0; accent-color: var(--accent);
|
display: block; width: 100%; margin: 0; accent-color: var(--accent);
|
||||||
|
outline: none; box-shadow: none; -webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
#timelineBar input[type="range"]:focus,
|
||||||
|
#timelineBar input[type="range"]:focus-visible {
|
||||||
|
outline: none; box-shadow: none; border-color: var(--border);
|
||||||
}
|
}
|
||||||
#timeRuler { position: absolute; top: 0; left: 7px; right: 7px; height: 19px; }
|
#timeRuler { position: absolute; top: 0; left: 7px; right: 7px; height: 19px; }
|
||||||
#timeRuler .tick {
|
#timeRuler .tick {
|
||||||
@@ -324,7 +333,6 @@
|
|||||||
let seeking = false; // 用户正在拖动时间轴
|
let seeking = false; // 用户正在拖动时间轴
|
||||||
let duration = 0; // 当前动画时长
|
let duration = 0; // 当前动画时长
|
||||||
let isStaticAnimation = false;
|
let isStaticAnimation = false;
|
||||||
let wasPlayingBeforeSeek = false; // 记录拖动前是否在播放
|
|
||||||
const FPS = 30;
|
const FPS = 30;
|
||||||
const STATIC_EPSILON = 1e-6;
|
const STATIC_EPSILON = 1e-6;
|
||||||
let statusHoldUntil = 0;
|
let statusHoldUntil = 0;
|
||||||
@@ -634,6 +642,12 @@
|
|||||||
return Math.round((seconds || 0) * FPS);
|
return Math.round((seconds || 0) * FPS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function snapTimeToFrame(seconds) {
|
||||||
|
const lastWholeFrame = Math.max(0, Math.floor((duration + 1e-8) * FPS));
|
||||||
|
const frame = Math.max(0, Math.min(Math.round((Number(seconds) || 0) * FPS), lastWholeFrame));
|
||||||
|
return frame / FPS;
|
||||||
|
}
|
||||||
|
|
||||||
function formatSeconds(seconds) {
|
function formatSeconds(seconds) {
|
||||||
const value = Math.max(0, Number(seconds) || 0);
|
const value = Math.max(0, Number(seconds) || 0);
|
||||||
if (value < 10) return value.toFixed(2) + ' 秒';
|
if (value < 10) return value.toFixed(2) + ' 秒';
|
||||||
@@ -1116,29 +1130,38 @@
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
document.getElementById('thumbBtn').addEventListener('click', generateThumbnail);
|
document.getElementById('thumbBtn').addEventListener('click', generateThumbnail);
|
||||||
// 时间轴:拖动时实时跳帧
|
function seekTimelineToNearestFrame() {
|
||||||
|
const snappedTime = snapTimeToFrame(parseFloat(timeline.value));
|
||||||
|
timeline.value = String(snappedTime);
|
||||||
|
seekTo(snappedTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间轴:拖动时按 30 FPS 整数帧实时预览。
|
||||||
timeline.addEventListener('input', (e) => {
|
timeline.addEventListener('input', (e) => {
|
||||||
const t = parseFloat(e.target.value);
|
if (state) seekTimelineToNearestFrame();
|
||||||
if (state) seekTo(t);
|
|
||||||
});
|
});
|
||||||
// 时间轴:按下时暂停动画,拖动结束(松开)恢复播放(若之前是播放状态)
|
// 时间轴:开始拖动即暂停,松开后吸附到最近的 30 FPS 整数帧并保持暂停。
|
||||||
timeline.addEventListener('pointerdown', () => {
|
timeline.addEventListener('pointerdown', () => {
|
||||||
seeking = true;
|
seeking = true;
|
||||||
wasPlayingBeforeSeek = playing;
|
|
||||||
playing = false;
|
playing = false;
|
||||||
updatePlayButton();
|
updatePlayButton();
|
||||||
});
|
});
|
||||||
const endTimelineSeek = () => {
|
const endTimelineSeek = () => {
|
||||||
if (!seeking) return;
|
if (!seeking) return;
|
||||||
|
seekTimelineToNearestFrame();
|
||||||
seeking = false;
|
seeking = false;
|
||||||
if (wasPlayingBeforeSeek) {
|
playing = false;
|
||||||
playing = true;
|
updatePlayButton();
|
||||||
updatePlayButton();
|
timeline.blur();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
timeline.addEventListener('pointerup', endTimelineSeek);
|
timeline.addEventListener('pointerup', endTimelineSeek);
|
||||||
// 即便指针移出滑块松开,也能恢复
|
// 指针移出滑块后松开时也要完成吸附。
|
||||||
window.addEventListener('pointerup', endTimelineSeek);
|
window.addEventListener('pointerup', endTimelineSeek);
|
||||||
|
timeline.addEventListener('change', () => {
|
||||||
|
if (seeking || timeline.disabled) return;
|
||||||
|
seekTimelineToNearestFrame();
|
||||||
|
timeline.blur();
|
||||||
|
});
|
||||||
|
|
||||||
// ---------- 画布交互:左键拖拽平移,右键回中心,滚轮缩放 ----------
|
// ---------- 画布交互:左键拖拽平移,右键回中心,滚轮缩放 ----------
|
||||||
canvas.addEventListener('pointerdown', (e) => {
|
canvas.addEventListener('pointerdown', (e) => {
|
||||||
|
|||||||
Reference in new issue
Block a user