Remove event listeners with AbortSignal in the GrabToPan class
This commit is contained in:
parent
c88d3a31cf
commit
c3bbeb51e3
@ -23,6 +23,12 @@ const CSS_CLASS_GRAB = "grab-to-pan-grab";
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class GrabToPan {
|
class GrabToPan {
|
||||||
|
#activateAC = null;
|
||||||
|
|
||||||
|
#mouseDownAC = null;
|
||||||
|
|
||||||
|
#scrollAC = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a GrabToPan instance for a given HTML element.
|
* Construct a GrabToPan instance for a given HTML element.
|
||||||
* @param {GrabToPanOptions} options
|
* @param {GrabToPanOptions} options
|
||||||
@ -31,15 +37,6 @@ class GrabToPan {
|
|||||||
this.element = element;
|
this.element = element;
|
||||||
this.document = element.ownerDocument;
|
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
|
// 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.
|
// a grab operation, to ensure that the cursor has the desired appearance.
|
||||||
const overlay = (this.overlay = document.createElement("div"));
|
const overlay = (this.overlay = document.createElement("div"));
|
||||||
@ -50,9 +47,13 @@ class GrabToPan {
|
|||||||
* Bind a mousedown event to the element to enable grab-detection.
|
* Bind a mousedown event to the element to enable grab-detection.
|
||||||
*/
|
*/
|
||||||
activate() {
|
activate() {
|
||||||
if (!this.active) {
|
if (!this.#activateAC) {
|
||||||
this.active = true;
|
this.#activateAC = new AbortController();
|
||||||
this.element.addEventListener("mousedown", this._onMouseDown, true);
|
|
||||||
|
this.element.addEventListener("mousedown", this.#onMouseDown.bind(this), {
|
||||||
|
capture: true,
|
||||||
|
signal: this.#activateAC.signal,
|
||||||
|
});
|
||||||
this.element.classList.add(CSS_CLASS_GRAB);
|
this.element.classList.add(CSS_CLASS_GRAB);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,16 +62,17 @@ class GrabToPan {
|
|||||||
* Removes all events. Any pending pan session is immediately stopped.
|
* Removes all events. Any pending pan session is immediately stopped.
|
||||||
*/
|
*/
|
||||||
deactivate() {
|
deactivate() {
|
||||||
if (this.active) {
|
if (this.#activateAC) {
|
||||||
this.active = false;
|
this.#activateAC.abort();
|
||||||
this.element.removeEventListener("mousedown", this._onMouseDown, true);
|
this.#activateAC = null;
|
||||||
this._endPan();
|
|
||||||
|
this.#endPan();
|
||||||
this.element.classList.remove(CSS_CLASS_GRAB);
|
this.element.classList.remove(CSS_CLASS_GRAB);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle() {
|
toggle() {
|
||||||
if (this.active) {
|
if (this.#activateAC) {
|
||||||
this.deactivate();
|
this.deactivate();
|
||||||
} else {
|
} else {
|
||||||
this.activate();
|
this.activate();
|
||||||
@ -109,12 +111,26 @@ class GrabToPan {
|
|||||||
this.scrollTopStart = this.element.scrollTop;
|
this.scrollTopStart = this.element.scrollTop;
|
||||||
this.clientXStart = event.clientX;
|
this.clientXStart = event.clientX;
|
||||||
this.clientYStart = event.clientY;
|
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
|
// When a scroll event occurs before a mousemove, assume that the user
|
||||||
// dragged a scrollbar (necessary for Opera Presto, Safari and IE)
|
// dragged a scrollbar (necessary for Opera Presto, Safari and IE)
|
||||||
// (not needed for Chrome/Firefox)
|
// (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.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
@ -125,10 +141,12 @@ class GrabToPan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#onMouseMove(event) {
|
#onMouseMove(event) {
|
||||||
this.element.removeEventListener("scroll", this._endPan, true);
|
this.#scrollAC?.abort();
|
||||||
|
this.#scrollAC = null;
|
||||||
|
|
||||||
if (!(event.buttons & 1)) {
|
if (!(event.buttons & 1)) {
|
||||||
// The left mouse button is released.
|
// The left mouse button is released.
|
||||||
this._endPan();
|
this.#endPan();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const xDiff = event.clientX - this.clientXStart;
|
const xDiff = event.clientX - this.clientXStart;
|
||||||
@ -145,9 +163,10 @@ class GrabToPan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endPan() {
|
#endPan() {
|
||||||
this.element.removeEventListener("scroll", this._endPan, true);
|
this.#mouseDownAC?.abort();
|
||||||
this.document.removeEventListener("mousemove", this._onMouseMove, true);
|
this.#mouseDownAC = null;
|
||||||
this.document.removeEventListener("mouseup", this._endPan, true);
|
this.#scrollAC?.abort();
|
||||||
|
this.#scrollAC = null;
|
||||||
// Note: ChildNode.remove doesn't throw if the parentNode is undefined.
|
// Note: ChildNode.remove doesn't throw if the parentNode is undefined.
|
||||||
this.overlay.remove();
|
this.overlay.remove();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user