diff --git a/main.ts b/main.ts index 3fdc4ce..56bcead 100644 --- a/main.ts +++ b/main.ts @@ -23,8 +23,30 @@ class NinjaCursorForWindow { ad.body.appendChild(this.wrapperElement); this.cursorElement.addClass("x-cursor"); const root = ad.documentElement; - - const moveCursor = () => { + let datumTop = 0; + let datumElement: HTMLElement; + let cursorVisibility = false; + const moveCursor = (e?: Event, noAnimate?: boolean) => { + 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) { + root.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) { + root.style.setProperty("--cursor-visibility", `hidden`); + cursorVisibility = false; + } + return; + } + if (e && e.target instanceof HTMLElement) { + // Memo datum element for scroll. + datumElement = e.target; + } + datumTop = datumElement.getBoundingClientRect().top; const selection = aw.getSelection(); if (!selection) { console.log("Could not find selection"); @@ -45,15 +67,15 @@ class NinjaCursorForWindow { if (textRect.x == 0 && textRect.y == 0) { textRange.setStart(range.endContainer, range.endOffset - 1); textRange.setEnd(range.endContainer, range.endOffset); - const textRectx = textRange.getClientRects(); - const txx = textRectx.item(textRectx.length - 1); - if (!txx) { + const textRects = textRange.getClientRects(); + const tempRect = textRects.item(textRects.length - 1); + if (!tempRect) { console.log("Could not found"); return; } - textRect = txx; - textRect.x = txx.right; - textRect.y = txx.bottom - txx.height; + textRect = tempRect; + textRect.x = tempRect.right; + textRect.y = tempRect.bottom - tempRect.height; } if (textRect.x == 0 && textRect.y == 0) { @@ -78,6 +100,7 @@ class NinjaCursorForWindow { Math.abs(Math.sin(cursorDragAngle)) * 8 + Math.abs(Math.cos(cursorDragAngle)) * rect.height; const cursorDragWidth = cursorDragDistance; + root.style.setProperty( "--cursor-drag-height", `${cursorDragHeight}px` @@ -92,13 +115,19 @@ class NinjaCursorForWindow { ); root.style.setProperty("--cursor-height", `${rect.height}px`); root.style.setProperty("--cursor-x1", `${this.lastPos.x}px`); - root.style.setProperty("--cursor-y1", `${this.lastPos.y}px`); + root.style.setProperty("--cursor-y1src", `${this.lastPos.y}px`); root.style.setProperty("--cursor-x2", `${rect.x}px`); - root.style.setProperty("--cursor-y2", `${rect.y}px`); + root.style.setProperty("--cursor-y2src", `${rect.y}px`); + root.style.setProperty("--cursor-offset-y", `${0}px`); + if (noAnimate) { + this.lastPos = rect; + return; + } 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; @@ -106,23 +135,30 @@ class NinjaCursorForWindow { }; - registerDomEvent(aw, "keydown", () => { - moveCursor(); - }); - registerDomEvent(aw, "keyup", () => { - moveCursor(); - }); - registerDomEvent(aw, "mousedown", () => { - moveCursor(); - }); - registerDomEvent(aw, "mouseup", () => { - moveCursor(); - }); - registerDomEvent(aw, "touchend", () => { - moveCursor(); - }); - registerDomEvent(aw, "touchstart", () => { - moveCursor(); + const supportVIMMode = true; + const eventNames = ["keydown", "mousedown", "touchend", ...(supportVIMMode ? ["keyup", "mouseup", "touchstart"] : [])]; + for (const event of eventNames) { + registerDomEvent(aw, event, (ev: Event) => { + moveCursor(ev); + }); + } + // Handles scroll till scroll is finish. + const applyWheelScroll = (last?: number | boolean) => { + requestAnimationFrame(() => { + if (datumElement) { + const curTop = datumElement.getBoundingClientRect().top; + const diff = curTop - datumTop; + root.style.setProperty("--cursor-offset-y", `${diff}px`); + if (last === false || last != diff) { + requestAnimationFrame(() => applyWheelScroll(diff)); + } else if (last == diff) { + moveCursor(undefined, true); + } + } + }); + } + registerDomEvent(aw, "wheel", (e: WheelEvent) => { + applyWheelScroll(false); }); } diff --git a/styles.css b/styles.css index b424487..56e17c8 100644 --- a/styles.css +++ b/styles.css @@ -1,114 +1,120 @@ .x-cursor { - position: absolute; - top: 0; - left: 0; - height: var(--cursor-height); - width: 8px; - display: inline-block; - user-select: none; - background: var(--text-normal); - transform: translate3d(var(--cursor-x2), var(--cursor-y2), 0); - opacity: 0; - pointer-events: none; + position: relative; + top: 0; + left: 0; + height: var(--cursor-height); + width: 8px; + display: inline-block; + user-select: none; + background: var(--text-normal); + transform: translate3d(var(--cursor-x2), var(--cursor-y2), 0); + opacity: 0; + pointer-events: none; + visibility: var(--cursor-visibility); } .x-cursor:after { - content: ""; - height: var(--cursor-drag-height); - width: var(--cursor-drag-width); - display: inline-block; - user-select: none; - - background: linear-gradient(to right, var(--text-normal), transparent); - transform: rotate(var(--cursor-drag-angle)); - transform-origin: 4px center; - opacity: 0.5; - pointer-events: none; + content: ""; + height: var(--cursor-drag-height); + width: var(--cursor-drag-width); + display: inline-block; + user-select: none; + background: linear-gradient(to right, var(--text-normal), transparent); + transform: rotate(var(--cursor-drag-angle)); + transform-origin: 4px center; + opacity: 0.5; + pointer-events: none; } .x-cursor0 { - animation: a1 75ms cubic-bezier(0.34, 1.26, 0.84, 1) 0s both; + animation: a1 75ms cubic-bezier(0.34, 1.26, 0.84, 1) 0s both; } .x-cursor1 { - animation: a2 75ms cubic-bezier(0.34, 1.26, 0.84, 1) 0s both; + animation: a2 75ms cubic-bezier(0.34, 1.26, 0.84, 1) 0s both; } :root { - --cursor-x1: "0px"; - --cursor-x2: "0px"; - --cursor-y1: "0px"; - --cursor-y2: "0px"; - --cursor-drag-distance: "0px"; - --cursor-drag-angle: "0deg"; - --cursor-drag-width: "0px"; - --cursor-drag-height: "0px"; + --cursor-x1: "0px"; + --cursor-x2: "0px"; + --cursor-y1src: "0px"; + --cursor-y2src: "0px"; + --cursor-offset-y: "0px"; + --cursor-drag-distance: "0px"; + --cursor-drag-angle: "0deg"; + --cursor-drag-width: "0px"; + --cursor-drag-height: "0px"; - --cursor-height: 18px; + --cursor-height: 18px; + --cursor-y1: calc(var(--cursor-y1src) + var(--cursor-offset-y)); + --cursor-y2: calc(var(--cursor-y2src) + var(--cursor-offset-y)); + --cursor-visibility: "hidden"; + /* --cursor-color: --text-normal; */ } + .cursorWrapper { - display: block; - overflow: hidden; - user-select: none; - pointer-events: none; - background-color: transparent; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - margin: 0; - padding: 0; + display: block; + overflow: hidden; + user-select: none; + pointer-events: none; + background-color: transparent; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: 0; + padding: 0; } @keyframes a1 { - 0% { - transform: translate3d(var(--cursor-x1), var(--cursor-y1), 0); - background-color: var(--text-normal); - opacity: 0; - } + 0% { + transform: translate3d(var(--cursor-x1), var(--cursor-y1), 0); + background-color: var(--text-normal); + opacity: 0; + } - 4% { - opacity: 1; - } + 4% { + opacity: 1; + } - 80% { - opacity: 0.95; - } + 80% { + opacity: 0.95; + } - 90% { - transform: translate3d(var(--cursor-x2), var(--cursor-y2), 0); - background-color: var(--text-normal); - opacity: 0.9; - } + 90% { + transform: translate3d(var(--cursor-x2), var(--cursor-y2), 0); + background-color: var(--text-normal); + opacity: 0.9; + } - 100% { - opacity: 0; - } + 100% { + opacity: 0; + } } @keyframes a2 { - 0% { - transform: translate3d(var(--cursor-x1), var(--cursor-y1), 0); - background-color: var(--text-normal); - opacity: 0; - } + 0% { + transform: translate3d(var(--cursor-x1), var(--cursor-y1), 0); + background-color: var(--text-normal); + opacity: 0; + } - 4% { - opacity: 1; - } + 4% { + opacity: 1; + } - 80% { - opacity: 0.95; - } + 80% { + opacity: 0.95; + } - 90% { - transform: translate3d(var(--cursor-x2), var(--cursor-y2), 0); - background-color: var(--text-normal); - opacity: 0.9; - } + 90% { + transform: translate3d(var(--cursor-x2), var(--cursor-y2), 0); + background-color: var(--text-normal); + opacity: 0.9; + } - 100% { - opacity: 0; - } -} + 100% { + opacity: 0; + } +} \ No newline at end of file