
## 💡 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()`)*.
