## 💡 Nguyên lý tính toán đúng (Zoom to Mouse Pointer) Để điểm dưới con trỏ chuột đứng yên tại đúng vị trí đó sau khi zoom, bạn cần giữ nguyên **tỷ lệ thời gian (time ratio)** tại điểm con trỏ chuột so với chiều rộng hiện tại của vùng hiển thị (Viewport). ### **Công thức chuyển đổi:** Giả sử thanh cuộn (Scrollbar) có vị trí xả hiện tại là `scrollLeft`: 1. **Tìm điểm thời gian tương đối tại vị trí chuột ($T_{mouse}$):** $$T_{mouse} = \text{scrollLeft} + X_{mouse\_in\_canvas}$$ 2. **Tính tỷ lệ zoom mới ($S_{new} / S_{old}$):** $$\text{ratio} = \frac{\text{scale}_{new}}{\text{scale}_{old}}$$ 3. **Cập nhật vị trí cuộn mới (`scrollLeft_{new}`):** $$\text{scrollLeft}_{new} = (T_{mouse} \times \text{ratio}) - X_{mouse\_in\_canvas}$$ --- ## 🛠️ Code mẫu ngắn gọn (Pure JS / Canvas) Dưới đây là đoạn code lắng nghe sự kiện `wheel` (lăn chuột) trên Waveform Canvas/Container để xử lý zoom đúng chuẩn các phần mềm DAW: ```javascript const container = document.getElementById('waveform-container'); let pixelsPerSecond = 100; // Tỉ lệ Zoom ban đầu (mức Zoom) container.addEventListener('wheel', (e) => { // Chỉ thực hiện zoom khi giữ phím Ctrl (hoặc bạn có thể bỏ condition này nếu muốn lăn chuột là zoom) if (!e.ctrlKey) return; e.preventDefault(); // 1. Lấy vị trí con trỏ chuột so với viền trái của Waveform Container (Viewport) const rect = container.getBoundingClientRect(); const mouseX = e.clientX - rect.left; // 2. Tính tọa độ thời gian (giây) tại điểm con trỏ chuột đang chỉ vào const currentScrollLeft = container.scrollLeft; const timeAtMouse = (currentScrollLeft + mouseX) / pixelsPerSecond; // 3. Tính tỉ lệ zoom mới (Phóng to / Thu nhỏ) const zoomFactor = e.deltaY < 0 ? 1.2 : 0.8; // Lăn lên = phóng to, lăn xuống = thu nhỏ const newPixelsPerSecond = Math.max(10, Math.min(2000, pixelsPerSecond * zoomFactor)); // 4. Cập nhật tỉ lệ zoom mới vào ứng dụng pixelsPerSecond = newPixelsPerSecond; // (Thực hiện render lại Waveform với pixelsPerSecond mới tại đây) renderWaveform(); // 5. CẬP NHẬT SCROLLBAR: Cuộn lại sao cho điểm 'timeAtMouse' vẫn nằm đúng ở 'mouseX' container.scrollLeft = (timeAtMouse * pixelsPerSecond) - mouseX; }, { passive: false }); ``` --- ## 📌 Nhắc nhở thêm nếu dùng thư viện: * **Nếu bạn dùng Canvas thuần:** Đảm bảo hàm `renderWaveform()` vẽ lại waveform dựa theo `pixelsPerSecond` mới trước khi cập nhật `container.scrollLeft`. * **Nếu bạn đang dùng `wavesurfer.js`:** Thư viện này đã hỗ trợ sẵn logic này, bạn chỉ cần dùng method: ```javascript wavesurfer.zoom(newPxPerSec); ``` *(Nếu WaveSurfer bản cũ bị trôi, bạn áp dụng lại công thức tính `scrollLeft` ở trên sau khi gọi lệnh `zoom()`)*.