scrollbalken für wellenform

This commit is contained in:
2026-09-29 09:46:10 +02:00
parent fc093da36b
commit 872f5fcbaa
+77
View File
@@ -288,6 +288,10 @@ h1 { margin:0 0 6px; font-size:28px; }
.playhead { position:absolute; top:0; bottom:0; width:2px; margin-left:-1px; background:#ffcc33; box-shadow:0 0 5px #000; z-index:4; pointer-events:none; }
.handle { position:absolute; top:0; bottom:0; width:18px; margin-left:-9px; cursor:ew-resize; z-index:3; touch-action:none; }
.handle::after { content:""; position:absolute; left:7px; top:0; bottom:0; width:4px; background:#fff; border-radius:2px; box-shadow:0 0 5px #000; }
.view-scroll { position:relative; width:100%; height:14px; margin-top:5px; background:#292929; border-radius:7px; overflow:hidden; touch-action:none; cursor:grab; }
.view-scroll-thumb { position:absolute; top:2px; bottom:2px; left:0; min-width:14px; background:#777; border-radius:5px; cursor:grab; touch-action:none; }
.view-scroll-thumb:hover { background:#999; }
.view-scroll-thumb.dragging { background:#aaa; cursor:grabbing; }
.time-row { display:flex; justify-content:space-between; margin-top:8px; color:#aaa; font-size:14px; }
.controls { display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-top:18px; }
button, a.button { border:0; border-radius:6px; padding:11px 16px; background:#333; color:#fff; cursor:pointer; text-decoration:none; font-size:15px; }
@@ -314,6 +318,8 @@ button:disabled { opacity:.45; cursor:not-allowed; }
body { padding:12px; }
.editor { padding:12px; }
.wave-wrap { height:180px; }
.view-scroll { height:18px; }
.view-scroll-thumb { min-width:28px; }
.zoom { width:100%; margin-left:0; }
.values { grid-template-columns:1fr; }
.position { width:100%; margin-left:0; }
@@ -335,6 +341,9 @@ button:disabled { opacity:.45; cursor:not-allowed; }
<div class="handle" id="startHandle" title="Anfang verschieben"></div>
<div class="handle" id="endHandle" title="Ende verschieben"></div>
</div>
<div class="view-scroll" id="viewScroll" aria-label="Sichtbaren Bereich verschieben">
<div class="view-scroll-thumb" id="viewScrollThumb"></div>
</div>
<div class="time-row">
<span id="viewStart">00:00:00</span>
<span id="viewEnd">{{ duration }}</span>
@@ -392,6 +401,12 @@ const endHandle = document.getElementById('endHandle');
const selection = document.getElementById('selection');
const playhead = document.getElementById('playhead');
const audio = document.getElementById('audio');
const viewScroll = document.getElementById('viewScroll');
const viewScrollThumb = document.getElementById('viewScrollThumb');
let scrollbarDragging = false;
let scrollbarPointerId = null;
let scrollbarStartX = 0;
let scrollbarStartView = 0;
function fmt(sec) {
sec = Math.max(0, Math.round(sec));
@@ -428,6 +443,14 @@ function render() {
document.getElementById('viewStart').textContent = fmt(viewStart);
document.getElementById('viewEnd').textContent = fmt(viewEnd);
document.getElementById('zoomValue').textContent = 'Zoom: '+Math.round(zoom*100)+' %';
// The scrollbar thumb represents the currently visible part of the full recording.
const visiblePercent = Math.max(0, Math.min(100, ((viewEnd-viewStart)/duration)*100));
const offsetPercent = Math.max(0, Math.min(100-visiblePercent, (viewStart/duration)*100));
viewScrollThumb.style.width = visiblePercent+'%';
viewScrollThumb.style.left = offsetPercent+'%';
viewScrollThumb.style.pointerEvents = zoom > 1 ? 'auto' : 'none';
viewScrollThumb.classList.toggle('dragging', scrollbarDragging);
renderPlayhead();
}
function renderPlayhead() {
@@ -461,6 +484,60 @@ function resetZoom() {
viewEnd = duration;
render();
}
function moveViewTo(newStart) {
const span = viewEnd-viewStart;
if (span >= duration) {
viewStart = 0;
viewEnd = duration;
} else {
viewStart = Math.max(0, Math.min(duration-span, newStart));
viewEnd = viewStart+span;
}
render();
}
viewScrollThumb.addEventListener('pointerdown', e => {
if (zoom <= 1) return;
e.preventDefault();
e.stopPropagation();
scrollbarDragging = true;
scrollbarPointerId = e.pointerId;
scrollbarStartX = e.clientX;
scrollbarStartView = viewStart;
viewScrollThumb.setPointerCapture(e.pointerId);
render();
});
viewScrollThumb.addEventListener('pointermove', e => {
if (!scrollbarDragging || e.pointerId !== scrollbarPointerId) return;
const trackWidth = viewScroll.clientWidth;
const thumbWidth = viewScrollThumb.offsetWidth;
const movableWidth = Math.max(1, trackWidth-thumbWidth);
const span = viewEnd-viewStart;
const deltaPixels = e.clientX-scrollbarStartX;
const deltaTime = (deltaPixels/movableWidth) * (duration-span);
moveViewTo(scrollbarStartView+deltaTime);
e.preventDefault();
});
function endScrollbarDrag(e) {
if (!scrollbarDragging || e.pointerId !== scrollbarPointerId) return;
scrollbarDragging = false;
scrollbarPointerId = null;
render();
}
viewScrollThumb.addEventListener('pointerup', endScrollbarDrag);
viewScrollThumb.addEventListener('pointercancel', endScrollbarDrag);
// Clicking/touching the empty part of the scrollbar moves the visible window
// so that the clicked position becomes its center.
viewScroll.addEventListener('pointerdown', e => {
if (zoom <= 1 || e.target === viewScrollThumb) return;
e.preventDefault();
const r = viewScroll.getBoundingClientRect();
const ratio = Math.max(0, Math.min(1, (e.clientX-r.left)/r.width));
const span = viewEnd-viewStart;
moveViewTo(ratio*duration-span/2);
});
wave.addEventListener('wheel', e => {
e.preventDefault();
const factor = e.deltaY < 0 ? ZOOM_FACTOR : 1/ZOOM_FACTOR;