mirror of
https://github.com/vrtmrz/ninja-cursor.git
synced 2026-07-22 06:40:31 +00:00
Fixed:
- No more hanging even if some DOM looking-up operation has failed. Improved: - Performance has been significantly improved.
This commit is contained in:
parent
c2b042e456
commit
9f598c7c03
2 changed files with 123 additions and 120 deletions
239
main.ts
239
main.ts
|
|
@ -43,123 +43,119 @@ class NinjaCursorForWindow {
|
|||
processing = false;
|
||||
}
|
||||
const __moveCursor = async (e?: Event, noAnimate?: boolean) => {
|
||||
if ([
|
||||
!this.plugin.settings.reactToContentEditable && !this.plugin.settings.reactToInputElement && !this.plugin.settings.reactToVimMode,
|
||||
this.plugin.settings.reactToContentEditable && e && e.target instanceof HTMLElement && e.target.isContentEditable,
|
||||
this.plugin.settings.reactToInputElement && e && e.target instanceof HTMLElement && e.target.tagName == "INPUT",
|
||||
this.plugin.settings.reactToVimMode && e && e.target instanceof HTMLElement && e.target.closest(".cm-vimMode")
|
||||
].every(e => !e)) {
|
||||
// When anything configured and no matched elements.
|
||||
// At here, do not hide the cursor for the smoother animation.
|
||||
return;
|
||||
}
|
||||
if (e && e.target instanceof HTMLElement && (e.target.isContentEditable || e.target.tagName == "INPUT")) {
|
||||
// If it caused by clicking an element and it is editable.
|
||||
datumElement = e.target;
|
||||
if (!cursorVisibility) {
|
||||
styleRoot.style.setProperty("--cursor-visibility", `visible`);
|
||||
cursorVisibility = true;
|
||||
}
|
||||
} else if (e != null) {
|
||||
// If it caused by clicking an element but it is not editable.
|
||||
if (cursorVisibility) {
|
||||
styleRoot.style.setProperty("--cursor-visibility", `hidden`);
|
||||
cursorVisibility = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e && e.target instanceof HTMLElement) {
|
||||
// Memo datum element for scroll.
|
||||
datumElement = e.target;
|
||||
}
|
||||
await waitForReflowComplete();
|
||||
datumTop = datumElement.getBoundingClientRect().top;
|
||||
const selection = aw.getSelection();
|
||||
if (!selection) {
|
||||
console.log("Could not find selection");
|
||||
return;
|
||||
}
|
||||
if (selection.rangeCount == 0) return;
|
||||
const range = selection.getRangeAt(0);
|
||||
let rect = range?.getBoundingClientRect();
|
||||
if (!rect) {
|
||||
console.log("Could not find range");
|
||||
return;
|
||||
}
|
||||
if (rect.x == 0 && rect.y == 0) {
|
||||
const textRange = ad.createRange();
|
||||
textRange.setStart(range.startContainer, range.startOffset);
|
||||
textRange.setEndAfter(range.startContainer);
|
||||
let textRect = textRange.getBoundingClientRect();
|
||||
if (textRect.x == 0 && textRect.y == 0) {
|
||||
const startEndOffset = range.endOffset - 1 < 0 ? 0 : range.endOffset - 1;
|
||||
textRange.setStart(range.endContainer, startEndOffset);
|
||||
textRange.setEnd(range.endContainer, range.endOffset);
|
||||
const textRects = textRange.getClientRects();
|
||||
const tempRect = textRects.item(textRects.length - 1);
|
||||
if (!tempRect) {
|
||||
console.log("Could not found");
|
||||
return;
|
||||
}
|
||||
textRect = tempRect;
|
||||
textRect.x = tempRect.right;
|
||||
textRect.y = tempRect.bottom - tempRect.height;
|
||||
}
|
||||
|
||||
if (textRect.x == 0 && textRect.y == 0) {
|
||||
try {
|
||||
if ([
|
||||
!this.plugin.settings.reactToContentEditable && !this.plugin.settings.reactToInputElement && !this.plugin.settings.reactToVimMode,
|
||||
this.plugin.settings.reactToContentEditable && e && e.target instanceof HTMLElement && e.target.isContentEditable,
|
||||
this.plugin.settings.reactToInputElement && e && e.target instanceof HTMLElement && e.target.tagName == "INPUT",
|
||||
this.plugin.settings.reactToVimMode && e && e.target instanceof HTMLElement && e.target.closest(".cm-vimMode")
|
||||
].every(e => !e)) {
|
||||
// When anything configured and no matched elements.
|
||||
// At here, do not hide the cursor for the smoother animation.
|
||||
return;
|
||||
}
|
||||
rect = textRect;
|
||||
}
|
||||
if (this.lastPos == null) {
|
||||
this.lastPos = rect;
|
||||
return;
|
||||
}
|
||||
if (this.lastPos.x == rect.x && this.lastPos.y == rect.y) {
|
||||
return;
|
||||
}
|
||||
this.styleCount = (this.styleCount + 1) % 2;
|
||||
const dx = rect.x - this.lastPos.x;
|
||||
const dy = this.lastPos.y - rect.y;
|
||||
const cursorDragAngle = Math.atan2(dx, dy) + Math.PI / 2;
|
||||
const cursorDragDistance = Math.sqrt(dx * dx + dy * dy);
|
||||
if (e && e.target instanceof HTMLElement && (e.target.isContentEditable || e.target.tagName == "INPUT")) {
|
||||
// If it caused by clicking an element and it is editable.
|
||||
datumElement = e.target;
|
||||
if (!cursorVisibility) {
|
||||
cursorVisibility = true;
|
||||
}
|
||||
} else if (e != null) {
|
||||
// If it caused by clicking an element but it is not editable.
|
||||
if (cursorVisibility) {
|
||||
styleRoot.style.setProperty("--cursor-visibility", `hidden`);
|
||||
cursorVisibility = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e && e.target instanceof HTMLElement) {
|
||||
// Memo datum element for scroll.
|
||||
datumElement = e.target;
|
||||
}
|
||||
await waitForReflowComplete();
|
||||
datumTop = datumElement.getBoundingClientRect().top;
|
||||
const selection = aw.getSelection();
|
||||
if (!selection) {
|
||||
console.log("Could not find selection");
|
||||
return;
|
||||
}
|
||||
if (selection.rangeCount == 0) return;
|
||||
const range = selection.getRangeAt(0);
|
||||
let rect = range?.getBoundingClientRect();
|
||||
if (!rect) {
|
||||
console.log("Could not find range");
|
||||
return;
|
||||
}
|
||||
if (rect.x == 0 && rect.y == 0) {
|
||||
const textRange = ad.createRange();
|
||||
textRange.setStart(range.startContainer, range.startOffset);
|
||||
textRange.setEndAfter(range.startContainer);
|
||||
let textRect = textRange.getBoundingClientRect();
|
||||
if (textRect.x == 0 && textRect.y == 0) {
|
||||
const startEndOffset = range.endOffset - 1 < 0 ? 0 : range.endOffset - 1;
|
||||
textRange.setStart(range.endContainer, startEndOffset);
|
||||
textRange.setEnd(range.endContainer, range.endOffset);
|
||||
const textRects = textRange.getClientRects();
|
||||
const tempRect = textRects.item(textRects.length - 1);
|
||||
if (!tempRect) {
|
||||
console.log("Could not found");
|
||||
return;
|
||||
}
|
||||
textRect = tempRect;
|
||||
textRect.x = tempRect.right;
|
||||
textRect.y = tempRect.bottom - tempRect.height;
|
||||
}
|
||||
|
||||
const cursorDragHeight =
|
||||
Math.abs(Math.sin(cursorDragAngle)) * 8 +
|
||||
Math.abs(Math.cos(cursorDragAngle)) * rect.height;
|
||||
const cursorDragWidth = cursorDragDistance;
|
||||
if (textRect.x == 0 && textRect.y == 0) {
|
||||
return;
|
||||
}
|
||||
rect = textRect;
|
||||
}
|
||||
if (this.lastPos == null) {
|
||||
this.lastPos = rect;
|
||||
return;
|
||||
}
|
||||
if (this.lastPos.x == rect.x && this.lastPos.y == rect.y) {
|
||||
return;
|
||||
}
|
||||
this.styleCount = (this.styleCount + 1) % 2;
|
||||
const dx = rect.x - this.lastPos.x;
|
||||
const dy = this.lastPos.y - rect.y;
|
||||
const cursorDragAngle = Math.atan2(dx, dy) + Math.PI / 2;
|
||||
const cursorDragDistance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
styleRoot.style.setProperty(
|
||||
"--cursor-drag-height",
|
||||
`${cursorDragHeight}px`
|
||||
);
|
||||
styleRoot.style.setProperty(
|
||||
"--cursor-drag-width",
|
||||
`${cursorDragWidth}px`
|
||||
);
|
||||
styleRoot.style.setProperty(
|
||||
"--cursor-drag-angle",
|
||||
`${cursorDragAngle}rad`
|
||||
);
|
||||
styleRoot.style.setProperty("--cursor-height", `${rect.height}px`);
|
||||
styleRoot.style.setProperty("--cursor-x1", `${this.lastPos.x}px`);
|
||||
styleRoot.style.setProperty("--cursor-y1src", `${this.lastPos.y}px`);
|
||||
styleRoot.style.setProperty("--cursor-x2", `${rect.x}px`);
|
||||
styleRoot.style.setProperty("--cursor-y2src", `${rect.y}px`);
|
||||
styleRoot.style.setProperty("--cursor-offset-y", `${0}px`);
|
||||
if (noAnimate) {
|
||||
this.lastPos = rect;
|
||||
return;
|
||||
const cursorDragHeight =
|
||||
Math.abs(Math.sin(cursorDragAngle)) * 8 +
|
||||
Math.abs(Math.cos(cursorDragAngle)) * rect.height;
|
||||
const cursorDragWidth = cursorDragDistance;
|
||||
|
||||
//Set properties at once.
|
||||
styleRoot.style.cssText = `
|
||||
--cursor-drag-height: ${cursorDragHeight}px;
|
||||
--cursor-drag-width: ${cursorDragWidth}px;
|
||||
--cursor-drag-angle: ${cursorDragAngle}rad;
|
||||
--cursor-height: ${rect.height}px;
|
||||
--cursor-x1: ${this.lastPos.x}px;
|
||||
--cursor-y1src: ${this.lastPos.y}px;
|
||||
--cursor-x2: ${rect.x}px;
|
||||
--cursor-y2src: ${rect.y}px;
|
||||
--cursor-offset-y: ${0}px;
|
||||
--cursor-visibility: visible;
|
||||
`;
|
||||
if (noAnimate) {
|
||||
this.lastPos = rect;
|
||||
return;
|
||||
}
|
||||
// I have not remembered why I have updated datumTop here.
|
||||
// datumTop = datumElement.getBoundingClientRect().top;
|
||||
aw.requestAnimationFrame((time) => {
|
||||
this.cursorElement.className = `x-cursor x-cursor${this.styleCount}`
|
||||
this.lastPos = rect;
|
||||
});
|
||||
} catch (ex) {
|
||||
console.log(ex);
|
||||
//NO OP.
|
||||
}
|
||||
this.cursorElement.removeClass("x-cursor0");
|
||||
this.cursorElement.removeClass("x-cursor1");
|
||||
// this.cursorElement.getAnimations().forEach((anim) => anim.cancel());
|
||||
|
||||
datumTop = datumElement.getBoundingClientRect().top;
|
||||
aw.requestAnimationFrame((time) => {
|
||||
this.cursorElement.addClass(`x-cursor${this.styleCount}`);
|
||||
this.lastPos = rect;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -176,13 +172,18 @@ class NinjaCursorForWindow {
|
|||
if (!triggered) {
|
||||
requestAnimationFrame(() => {
|
||||
if (datumElement) {
|
||||
const curTop = datumElement.getBoundingClientRect().top;
|
||||
const diff = curTop - datumTop;
|
||||
styleRoot.style.setProperty("--cursor-offset-y", `${diff}px`);
|
||||
if (last === false || last != diff) {
|
||||
requestAnimationFrame(() => applyWheelScroll(diff));
|
||||
} else if (last == diff) {
|
||||
moveCursor(undefined, true);
|
||||
try {
|
||||
const curTop = datumElement.getBoundingClientRect().top;
|
||||
const diff = curTop - datumTop;
|
||||
styleRoot.style.setProperty("--cursor-offset-y", `${diff}px`);
|
||||
if (last === false || last != diff) {
|
||||
requestAnimationFrame(() => applyWheelScroll(diff));
|
||||
} else if (last == diff) {
|
||||
moveCursor(undefined, true);
|
||||
}
|
||||
} catch (ex) {
|
||||
// NO OP.
|
||||
console.log(ex);
|
||||
}
|
||||
}
|
||||
triggered = false;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
opacity: 0;
|
||||
pointer-events: none;
|
||||
visibility: var(--cursor-visibility);
|
||||
will-change: transform, opacity, height, width, transform-origin, background-color;
|
||||
}
|
||||
|
||||
.x-cursor:after {
|
||||
|
|
@ -42,6 +43,7 @@
|
|||
transform-origin: 4px center;
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
will-change: transform, opacity, height, width, transform-origin, background-color;
|
||||
}
|
||||
|
||||
.x-cursor0 {
|
||||
|
|
@ -120,4 +122,4 @@
|
|||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue