zoom funktion hinzugefügt
This commit is contained in:
@@ -283,7 +283,7 @@ h1 { margin:0 0 6px; font-size:28px; }
|
||||
.subtitle { color:#aaa; margin-bottom:22px; }
|
||||
.editor { background:#181818; border:1px solid #333; border-radius:10px; padding:20px; }
|
||||
.wave-wrap { position:relative; width:100%; height:240px; background:#0b0b0b; border-radius:6px; overflow:hidden; user-select:none; touch-action:none; }
|
||||
.wave-wrap img { width:100%; height:100%; object-fit:fill; display:block; opacity:.9; }
|
||||
.wave-image { position:absolute; top:0; left:0; width:100%; height:100%; object-fit:fill; display:block; opacity:.9; pointer-events:none; }
|
||||
.selection { position:absolute; top:0; bottom:0; background:rgba(80,150,255,.16); border-left:2px solid #fff; border-right:2px solid #fff; pointer-events:none; }
|
||||
.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; }
|
||||
@@ -299,6 +299,7 @@ button.primary { background:#1769aa; }
|
||||
button.danger { background:#8b0000; }
|
||||
button:disabled { opacity:.45; cursor:not-allowed; }
|
||||
.position { margin-left:auto; color:#ccc; font-variant-numeric:tabular-nums; }
|
||||
.zoom { color:#aaa; font-variant-numeric:tabular-nums; }
|
||||
.values { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; margin-top:20px; }
|
||||
.value { background:#222; border-radius:6px; padding:12px; }
|
||||
.value span { display:block; color:#999; font-size:13px; margin-bottom:5px; }
|
||||
@@ -313,6 +314,7 @@ button:disabled { opacity:.45; cursor:not-allowed; }
|
||||
body { padding:12px; }
|
||||
.editor { padding:12px; }
|
||||
.wave-wrap { height:180px; }
|
||||
.zoom { width:100%; margin-left:0; }
|
||||
.values { grid-template-columns:1fr; }
|
||||
.position { width:100%; margin-left:0; }
|
||||
button, a.button { flex:1 1 auto; }
|
||||
@@ -327,15 +329,15 @@ button:disabled { opacity:.45; cursor:not-allowed; }
|
||||
|
||||
<div class="editor">
|
||||
<div class="wave-wrap" id="wave">
|
||||
<img src="{{ waveform_url }}" alt="Wellenform">
|
||||
<img class="wave-image" id="waveImage" src="{{ waveform_url }}" alt="Wellenform">
|
||||
<div class="selection" id="selection"></div>
|
||||
<div class="playhead" id="playhead"></div>
|
||||
<div class="handle" id="startHandle" title="Anfang verschieben"></div>
|
||||
<div class="handle" id="endHandle" title="Ende verschieben"></div>
|
||||
</div>
|
||||
<div class="time-row">
|
||||
<span>00:00:00</span>
|
||||
<span>{{ duration }}</span>
|
||||
<span id="viewStart">00:00:00</span>
|
||||
<span id="viewEnd">{{ duration }}</span>
|
||||
</div>
|
||||
|
||||
<audio id="audio" controls preload="metadata" style="width:100%;margin-top:18px">
|
||||
@@ -351,6 +353,8 @@ button:disabled { opacity:.45; cursor:not-allowed; }
|
||||
<button onclick="playFromStart()">▶ Ab Auswahlstart</button>
|
||||
<button onclick="setStartAtCurrent()">⏱ Anfang = Position</button>
|
||||
<button onclick="setEndAtCurrent()">⏱ Ende = Position</button>
|
||||
<button onclick="resetZoom()">↺ Gesamtansicht</button>
|
||||
<span class="zoom" id="zoomValue">Zoom: 100 %</span>
|
||||
<span class="position" id="position">00:00:00</span>
|
||||
</div>
|
||||
|
||||
@@ -375,7 +379,14 @@ const filename = {{ filename|tojson }};
|
||||
let start = 0;
|
||||
let end = duration;
|
||||
let dragging = null;
|
||||
let zoom = 1;
|
||||
let viewStart = 0;
|
||||
let viewEnd = duration;
|
||||
const MIN_ZOOM = 1;
|
||||
const MAX_ZOOM = 16;
|
||||
const ZOOM_FACTOR = 1.5;
|
||||
const wave = document.getElementById('wave');
|
||||
const waveImage = document.getElementById('waveImage');
|
||||
const startHandle = document.getElementById('startHandle');
|
||||
const endHandle = document.getElementById('endHandle');
|
||||
const selection = document.getElementById('selection');
|
||||
@@ -391,24 +402,70 @@ function fmt(sec) {
|
||||
}
|
||||
function xToTime(clientX) {
|
||||
const r = wave.getBoundingClientRect();
|
||||
return Math.max(0, Math.min(duration, ((clientX-r.left)/r.width)*duration));
|
||||
const ratio = Math.max(0, Math.min(1, (clientX-r.left)/r.width));
|
||||
return viewStart + ratio * (viewEnd-viewStart);
|
||||
}
|
||||
function timeToPercent(t) {
|
||||
const span = viewEnd-viewStart;
|
||||
if (span <= 0) return 0;
|
||||
return ((t-viewStart)/span)*100;
|
||||
}
|
||||
function render() {
|
||||
const a = (start/duration)*100;
|
||||
const b = (end/duration)*100;
|
||||
const a = timeToPercent(start);
|
||||
const b = timeToPercent(end);
|
||||
startHandle.style.left = a+'%';
|
||||
endHandle.style.left = b+'%';
|
||||
selection.style.left = a+'%';
|
||||
selection.style.width = (b-a)+'%';
|
||||
selection.style.width = Math.max(0, b-a)+'%';
|
||||
|
||||
const imageWidth = zoom * 100;
|
||||
waveImage.style.width = imageWidth+'%';
|
||||
waveImage.style.left = (-(viewStart/duration)*imageWidth)+'%';
|
||||
|
||||
document.getElementById('startValue').textContent = fmt(start);
|
||||
document.getElementById('endValue').textContent = fmt(end);
|
||||
document.getElementById('keepValue').textContent = fmt(end-start);
|
||||
document.getElementById('viewStart').textContent = fmt(viewStart);
|
||||
document.getElementById('viewEnd').textContent = fmt(viewEnd);
|
||||
document.getElementById('zoomValue').textContent = 'Zoom: '+Math.round(zoom*100)+' %';
|
||||
renderPlayhead();
|
||||
}
|
||||
function renderPlayhead() {
|
||||
const t = Math.max(0, Math.min(duration, audio.currentTime || 0));
|
||||
playhead.style.left = ((t/duration)*100)+'%';
|
||||
playhead.style.left = timeToPercent(t)+'%';
|
||||
}
|
||||
function setZoom(newZoom, clientX) {
|
||||
newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, newZoom));
|
||||
if (newZoom === zoom) return;
|
||||
|
||||
const r = wave.getBoundingClientRect();
|
||||
let focusRatio = 0.5;
|
||||
if (typeof clientX === 'number' && r.width > 0) {
|
||||
focusRatio = Math.max(0, Math.min(1, (clientX-r.left)/r.width));
|
||||
}
|
||||
|
||||
const oldSpan = viewEnd-viewStart;
|
||||
const focusTime = viewStart + focusRatio*oldSpan;
|
||||
const newSpan = duration/newZoom;
|
||||
let newStart = focusTime-focusRatio*newSpan;
|
||||
newStart = Math.max(0, Math.min(duration-newSpan, newStart));
|
||||
|
||||
zoom = newZoom;
|
||||
viewStart = newStart;
|
||||
viewEnd = newStart+newSpan;
|
||||
render();
|
||||
}
|
||||
function resetZoom() {
|
||||
zoom = 1;
|
||||
viewStart = 0;
|
||||
viewEnd = duration;
|
||||
render();
|
||||
}
|
||||
wave.addEventListener('wheel', e => {
|
||||
e.preventDefault();
|
||||
const factor = e.deltaY < 0 ? ZOOM_FACTOR : 1/ZOOM_FACTOR;
|
||||
setZoom(zoom*factor, e.clientX);
|
||||
}, {passive:false});
|
||||
function beginDrag(which, e) {
|
||||
e.preventDefault();
|
||||
dragging = which;
|
||||
|
||||
Reference in New Issue
Block a user