Skip to content

Commit

Permalink
Remove event listeners with AbortSignal in the GrabToPan class
Browse files Browse the repository at this point in the history
  • Loading branch information
Snuffleupagus committed Oct 19, 2024
1 parent c88d3a3 commit c3bbeb5
Showing 1 changed file with 44 additions and 25 deletions.
69 changes: 44 additions & 25 deletions web/grab_to_pan.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ const CSS_CLASS_GRAB = "grab-to-pan-grab";
*/

class GrabToPan {
#activateAC = null;

#mouseDownAC = null;

#scrollAC = null;

/**
* Construct a GrabToPan instance for a given HTML element.
* @param {GrabToPanOptions} options
Expand All @@ -31,15 +37,6 @@ class GrabToPan {
this.element = element;
this.document = element.ownerDocument;

// Bind the contexts to ensure that `this` always points to
// the GrabToPan instance.
this.activate = this.activate.bind(this);
this.deactivate = this.deactivate.bind(this);
this.toggle = this.toggle.bind(this);
this._onMouseDown = this.#onMouseDown.bind(this);
this._onMouseMove = this.#onMouseMove.bind(this);
this._endPan = this.#endPan.bind(this);

// This overlay will be inserted in the document when the mouse moves during
// a grab operation, to ensure that the cursor has the desired appearance.
const overlay = (this.overlay = document.createElement("div"));
Expand All @@ -50,9 +47,13 @@ class GrabToPan {
* Bind a mousedown event to the element to enable grab-detection.
*/
activate() {
if (!this.active) {
this.active = true;
this.element.addEventListener("mousedown", this._onMouseDown, true);
if (!this.#activateAC) {
this.#activateAC = new AbortController();

this.element.addEventListener("mousedown", this.#onMouseDown.bind(this), {
capture: true,
signal: this.#activateAC.signal,
});
this.element.classList.add(CSS_CLASS_GRAB);
}
}
Expand All @@ -61,16 +62,17 @@ class GrabToPan {
* Removes all events. Any pending pan session is immediately stopped.
*/
deactivate() {
if (this.active) {
this.active = false;
this.element.removeEventListener("mousedown", this._onMouseDown, true);
this._endPan();
if (this.#activateAC) {
this.#activateAC.abort();
this.#activateAC = null;

this.#endPan();
this.element.classList.remove(CSS_CLASS_GRAB);
}
}

toggle() {
if (this.active) {
if (this.#activateAC) {
this.deactivate();
} else {
this.activate();
Expand Down Expand Up @@ -109,12 +111,26 @@ class GrabToPan {
this.scrollTopStart = this.element.scrollTop;
this.clientXStart = event.clientX;
this.clientYStart = event.clientY;
this.document.addEventListener("mousemove", this._onMouseMove, true);
this.document.addEventListener("mouseup", this._endPan, true);

this.#mouseDownAC = new AbortController();
const boundEndPan = this.#endPan.bind(this),
mouseOpts = { capture: true, signal: this.#mouseDownAC.signal };

this.document.addEventListener(
"mousemove",
this.#onMouseMove.bind(this),
mouseOpts
);
this.document.addEventListener("mouseup", boundEndPan, mouseOpts);
// When a scroll event occurs before a mousemove, assume that the user
// dragged a scrollbar (necessary for Opera Presto, Safari and IE)
// (not needed for Chrome/Firefox)
this.element.addEventListener("scroll", this._endPan, true);
this.#scrollAC = new AbortController();

this.element.addEventListener("scroll", boundEndPan, {
capture: true,
signal: this.#scrollAC.signal,
});
event.preventDefault();
event.stopPropagation();

Expand All @@ -125,10 +141,12 @@ class GrabToPan {
}

#onMouseMove(event) {
this.element.removeEventListener("scroll", this._endPan, true);
this.#scrollAC?.abort();
this.#scrollAC = null;

if (!(event.buttons & 1)) {
// The left mouse button is released.
this._endPan();
this.#endPan();
return;
}
const xDiff = event.clientX - this.clientXStart;
Expand All @@ -145,9 +163,10 @@ class GrabToPan {
}

#endPan() {
this.element.removeEventListener("scroll", this._endPan, true);
this.document.removeEventListener("mousemove", this._onMouseMove, true);
this.document.removeEventListener("mouseup", this._endPan, true);
this.#mouseDownAC?.abort();
this.#mouseDownAC = null;
this.#scrollAC?.abort();
this.#scrollAC = null;
// Note: ChildNode.remove doesn't throw if the parentNode is undefined.
this.overlay.remove();
}
Expand Down

0 comments on commit c3bbeb5

Please sign in to comment.