diff --git a/main.ts b/main.ts index 572f22e..5913625 100644 --- a/main.ts +++ b/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; diff --git a/styles.css b/styles.css index 19b14f1..e8af6ef 100644 --- a/styles.css +++ b/styles.css @@ -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; } -} +} \ No newline at end of file