From 67bb27e22ee963b44f9f240d90af81a439888e74 Mon Sep 17 00:00:00 2001 From: chrisweb Date: Tue, 30 Jun 2026 18:47:30 +0200 Subject: [PATCH] fix: correct click percentage and listener removal Two bugs in the waveform click handling: 1. Click position used canvas.width (the internal pixel resolution set in draw(), e.g. 599px for 200 peaks) as the divisor while measuring the click offset in CSS pixels via getBoundingClientRect(). When the canvas is scaled with CSS (e.g. width: 100%) the two units differ, so the reported percent is wrong and the error grows toward the right edge. Now divides by the rendered width from getBoundingClientRect(). 2. _removeClickWaveListener() called .bind(this) again, producing a new function reference, so removeEventListener never matched the listener added in _addClickWaveListener() and the listener leaked on destroy(). The bound handler is now stored and reused for add and remove. Co-Authored-By: Claude Opus 4.8 --- src/library/core.ts | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/library/core.ts b/src/library/core.ts index e0b9bbd..0c6c932 100644 --- a/src/library/core.ts +++ b/src/library/core.ts @@ -45,6 +45,7 @@ export class Waveform { protected _latestRange: number | null = null protected _plugins: [] = [] protected _waveClickCallback: IWaveClickCallback | null = null + protected _boundCanvasElementClick: ((event: MouseEvent) => void) | null = null constructor(waveCoreOptions?: IWaveCoreOptions) { @@ -130,13 +131,24 @@ export class Waveform { protected _addClickWaveListener(): void { - this._canvasElement.addEventListener('click', this._canvasElementClick.bind(this)) + // store the bound handler so removeEventListener can reference the + // exact same function; binding again on removal (as before) creates a + // new function reference and the listener is never actually removed + this._boundCanvasElementClick = this._canvasElementClick.bind(this) + + this._canvasElement.addEventListener('click', this._boundCanvasElementClick) } protected _removeClickWaveListener(): void { - this._canvasElement.removeEventListener('click', this._canvasElementClick.bind(this)) + if (this._boundCanvasElementClick !== null) { + + this._canvasElement.removeEventListener('click', this._boundCanvasElementClick) + + this._boundCanvasElementClick = null + + } } @@ -147,8 +159,19 @@ export class Waveform { event.preventDefault() const canvasHorizontalPositionInPixel = this._getMouseHorizontalPosition(event) - const pixelsPerPercent = this._canvasElement.width / 100 - const clickHorizontalPositionInPercent = canvasHorizontalPositionInPixel / pixelsPerPercent + + // the click position must be relative to the rendered (CSS) width + // of the canvas and not to canvas.width, which is the internal + // pixel resolution set during draw(); when the canvas is scaled + // via CSS (e.g. width: 100%) the two differ and dividing by + // canvas.width makes the error grow towards the right edge + const renderedWidth = this._canvasElement.getBoundingClientRect().width + + if (renderedWidth === 0) { + return + } + + const clickHorizontalPositionInPercent = (canvasHorizontalPositionInPixel / renderedWidth) * 100 this._waveClickCallback(clickHorizontalPositionInPercent)