Skip to content

Commit f1f018e

Browse files
committed
Fixed:
- Now cursor is right position while scrolling. - Hide if non-editable element.
1 parent 37a17b1 commit f1f018e

File tree

2 files changed

+162
-120
lines changed

2 files changed

+162
-120
lines changed

main.ts

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,30 @@ class NinjaCursorForWindow {
2323
ad.body.appendChild(this.wrapperElement);
2424
this.cursorElement.addClass("x-cursor");
2525
const root = ad.documentElement;
26-
27-
const moveCursor = () => {
26+
let datumTop = 0;
27+
let datumElement: HTMLElement;
28+
let cursorVisibility = false;
29+
const moveCursor = (e?: Event, noAnimate?: boolean) => {
30+
if (e && e.target instanceof HTMLElement && (e.target.isContentEditable || e.target.tagName == "INPUT")) {
31+
// If it caused by clicking an element and it is editable.
32+
datumElement = e.target;
33+
if (!cursorVisibility) {
34+
root.style.setProperty("--cursor-visibility", `visible`);
35+
cursorVisibility = true;
36+
}
37+
} else if (e != null) {
38+
// If it caused by clicking an element but it is not editable.
39+
if (cursorVisibility) {
40+
root.style.setProperty("--cursor-visibility", `hidden`);
41+
cursorVisibility = false;
42+
}
43+
return;
44+
}
45+
if (e && e.target instanceof HTMLElement) {
46+
// Memo datum element for scroll.
47+
datumElement = e.target;
48+
}
49+
datumTop = datumElement.getBoundingClientRect().top;
2850
const selection = aw.getSelection();
2951
if (!selection) {
3052
console.log("Could not find selection");
@@ -45,15 +67,15 @@ class NinjaCursorForWindow {
4567
if (textRect.x == 0 && textRect.y == 0) {
4668
textRange.setStart(range.endContainer, range.endOffset - 1);
4769
textRange.setEnd(range.endContainer, range.endOffset);
48-
const textRectx = textRange.getClientRects();
49-
const txx = textRectx.item(textRectx.length - 1);
50-
if (!txx) {
70+
const textRects = textRange.getClientRects();
71+
const tempRect = textRects.item(textRects.length - 1);
72+
if (!tempRect) {
5173
console.log("Could not found");
5274
return;
5375
}
54-
textRect = txx;
55-
textRect.x = txx.right;
56-
textRect.y = txx.bottom - txx.height;
76+
textRect = tempRect;
77+
textRect.x = tempRect.right;
78+
textRect.y = tempRect.bottom - tempRect.height;
5779
}
5880

5981
if (textRect.x == 0 && textRect.y == 0) {
@@ -78,6 +100,7 @@ class NinjaCursorForWindow {
78100
Math.abs(Math.sin(cursorDragAngle)) * 8 +
79101
Math.abs(Math.cos(cursorDragAngle)) * rect.height;
80102
const cursorDragWidth = cursorDragDistance;
103+
81104
root.style.setProperty(
82105
"--cursor-drag-height",
83106
`${cursorDragHeight}px`
@@ -92,37 +115,50 @@ class NinjaCursorForWindow {
92115
);
93116
root.style.setProperty("--cursor-height", `${rect.height}px`);
94117
root.style.setProperty("--cursor-x1", `${this.lastPos.x}px`);
95-
root.style.setProperty("--cursor-y1", `${this.lastPos.y}px`);
118+
root.style.setProperty("--cursor-y1src", `${this.lastPos.y}px`);
96119
root.style.setProperty("--cursor-x2", `${rect.x}px`);
97-
root.style.setProperty("--cursor-y2", `${rect.y}px`);
120+
root.style.setProperty("--cursor-y2src", `${rect.y}px`);
121+
root.style.setProperty("--cursor-offset-y", `${0}px`);
122+
if (noAnimate) {
123+
this.lastPos = rect;
124+
return;
125+
}
98126
this.cursorElement.removeClass("x-cursor0");
99127
this.cursorElement.removeClass("x-cursor1");
100128
this.cursorElement.getAnimations().forEach((anim) => anim.cancel());
101129

130+
datumTop = datumElement.getBoundingClientRect().top;
102131
aw.requestAnimationFrame((time) => {
103132
this.cursorElement.addClass(`x-cursor${this.styleCount}`);
104133
this.lastPos = rect;
105134
});
106135
};
107136

108137

109-
registerDomEvent(aw, "keydown", () => {
110-
moveCursor();
111-
});
112-
registerDomEvent(aw, "keyup", () => {
113-
moveCursor();
114-
});
115-
registerDomEvent(aw, "mousedown", () => {
116-
moveCursor();
117-
});
118-
registerDomEvent(aw, "mouseup", () => {
119-
moveCursor();
120-
});
121-
registerDomEvent(aw, "touchend", () => {
122-
moveCursor();
123-
});
124-
registerDomEvent(aw, "touchstart", () => {
125-
moveCursor();
138+
const supportVIMMode = true;
139+
const eventNames = ["keydown", "mousedown", "touchend", ...(supportVIMMode ? ["keyup", "mouseup", "touchstart"] : [])];
140+
for (const event of eventNames) {
141+
registerDomEvent(aw, event, (ev: Event) => {
142+
moveCursor(ev);
143+
});
144+
}
145+
// Handles scroll till scroll is finish.
146+
const applyWheelScroll = (last?: number | boolean) => {
147+
requestAnimationFrame(() => {
148+
if (datumElement) {
149+
const curTop = datumElement.getBoundingClientRect().top;
150+
const diff = curTop - datumTop;
151+
root.style.setProperty("--cursor-offset-y", `${diff}px`);
152+
if (last === false || last != diff) {
153+
requestAnimationFrame(() => applyWheelScroll(diff));
154+
} else if (last == diff) {
155+
moveCursor(undefined, true);
156+
}
157+
}
158+
});
159+
}
160+
registerDomEvent(aw, "wheel", (e: WheelEvent) => {
161+
applyWheelScroll(false);
126162
});
127163
}
128164

styles.css

Lines changed: 99 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,120 @@
11
.x-cursor {
2-
position: absolute;
3-
top: 0;
4-
left: 0;
5-
height: var(--cursor-height);
6-
width: 8px;
7-
display: inline-block;
8-
user-select: none;
9-
background: var(--text-normal);
10-
transform: translate3d(var(--cursor-x2), var(--cursor-y2), 0);
11-
opacity: 0;
12-
pointer-events: none;
2+
position: relative;
3+
top: 0;
4+
left: 0;
5+
height: var(--cursor-height);
6+
width: 8px;
7+
display: inline-block;
8+
user-select: none;
9+
background: var(--text-normal);
10+
transform: translate3d(var(--cursor-x2), var(--cursor-y2), 0);
11+
opacity: 0;
12+
pointer-events: none;
13+
visibility: var(--cursor-visibility);
1314
}
1415

1516
.x-cursor:after {
16-
content: "";
17-
height: var(--cursor-drag-height);
18-
width: var(--cursor-drag-width);
19-
display: inline-block;
20-
user-select: none;
21-
22-
background: linear-gradient(to right, var(--text-normal), transparent);
23-
transform: rotate(var(--cursor-drag-angle));
24-
transform-origin: 4px center;
25-
opacity: 0.5;
26-
pointer-events: none;
17+
content: "";
18+
height: var(--cursor-drag-height);
19+
width: var(--cursor-drag-width);
20+
display: inline-block;
21+
user-select: none;
22+
background: linear-gradient(to right, var(--text-normal), transparent);
23+
transform: rotate(var(--cursor-drag-angle));
24+
transform-origin: 4px center;
25+
opacity: 0.5;
26+
pointer-events: none;
2727
}
2828

2929
.x-cursor0 {
30-
animation: a1 75ms cubic-bezier(0.34, 1.26, 0.84, 1) 0s both;
30+
animation: a1 75ms cubic-bezier(0.34, 1.26, 0.84, 1) 0s both;
3131
}
3232

3333
.x-cursor1 {
34-
animation: a2 75ms cubic-bezier(0.34, 1.26, 0.84, 1) 0s both;
34+
animation: a2 75ms cubic-bezier(0.34, 1.26, 0.84, 1) 0s both;
3535
}
3636

3737
:root {
38-
--cursor-x1: "0px";
39-
--cursor-x2: "0px";
40-
--cursor-y1: "0px";
41-
--cursor-y2: "0px";
42-
--cursor-drag-distance: "0px";
43-
--cursor-drag-angle: "0deg";
44-
--cursor-drag-width: "0px";
45-
--cursor-drag-height: "0px";
46-
47-
--cursor-height: 18px;
38+
--cursor-x1: "0px";
39+
--cursor-x2: "0px";
40+
--cursor-y1src: "0px";
41+
--cursor-y2src: "0px";
42+
--cursor-offset-y: "0px";
43+
--cursor-drag-distance: "0px";
44+
--cursor-drag-angle: "0deg";
45+
--cursor-drag-width: "0px";
46+
--cursor-drag-height: "0px";
47+
48+
--cursor-height: 18px;
49+
--cursor-y1: calc(var(--cursor-y1src) + var(--cursor-offset-y));
50+
--cursor-y2: calc(var(--cursor-y2src) + var(--cursor-offset-y));
51+
--cursor-visibility: "hidden";
52+
/* --cursor-color: --text-normal; */
4853
}
54+
4955
.cursorWrapper {
50-
display: block;
51-
overflow: hidden;
52-
user-select: none;
53-
pointer-events: none;
54-
background-color: transparent;
55-
position: fixed;
56-
top: 0;
57-
left: 0;
58-
right: 0;
59-
bottom: 0;
60-
margin: 0;
61-
padding: 0;
56+
display: block;
57+
overflow: hidden;
58+
user-select: none;
59+
pointer-events: none;
60+
background-color: transparent;
61+
position: fixed;
62+
top: 0;
63+
left: 0;
64+
right: 0;
65+
bottom: 0;
66+
margin: 0;
67+
padding: 0;
6268
}
6369

6470
@keyframes a1 {
65-
0% {
66-
transform: translate3d(var(--cursor-x1), var(--cursor-y1), 0);
67-
background-color: var(--text-normal);
68-
opacity: 0;
69-
}
70-
71-
4% {
72-
opacity: 1;
73-
}
74-
75-
80% {
76-
opacity: 0.95;
77-
}
78-
79-
90% {
80-
transform: translate3d(var(--cursor-x2), var(--cursor-y2), 0);
81-
background-color: var(--text-normal);
82-
opacity: 0.9;
83-
}
84-
85-
100% {
86-
opacity: 0;
87-
}
71+
0% {
72+
transform: translate3d(var(--cursor-x1), var(--cursor-y1), 0);
73+
background-color: var(--text-normal);
74+
opacity: 0;
75+
}
76+
77+
4% {
78+
opacity: 1;
79+
}
80+
81+
80% {
82+
opacity: 0.95;
83+
}
84+
85+
90% {
86+
transform: translate3d(var(--cursor-x2), var(--cursor-y2), 0);
87+
background-color: var(--text-normal);
88+
opacity: 0.9;
89+
}
90+
91+
100% {
92+
opacity: 0;
93+
}
8894
}
8995

9096
@keyframes a2 {
91-
0% {
92-
transform: translate3d(var(--cursor-x1), var(--cursor-y1), 0);
93-
background-color: var(--text-normal);
94-
opacity: 0;
95-
}
96-
97-
4% {
98-
opacity: 1;
99-
}
100-
101-
80% {
102-
opacity: 0.95;
103-
}
104-
105-
90% {
106-
transform: translate3d(var(--cursor-x2), var(--cursor-y2), 0);
107-
background-color: var(--text-normal);
108-
opacity: 0.9;
109-
}
110-
111-
100% {
112-
opacity: 0;
113-
}
114-
}
97+
0% {
98+
transform: translate3d(var(--cursor-x1), var(--cursor-y1), 0);
99+
background-color: var(--text-normal);
100+
opacity: 0;
101+
}
102+
103+
4% {
104+
opacity: 1;
105+
}
106+
107+
80% {
108+
opacity: 0.95;
109+
}
110+
111+
90% {
112+
transform: translate3d(var(--cursor-x2), var(--cursor-y2), 0);
113+
background-color: var(--text-normal);
114+
opacity: 0.9;
115+
}
116+
117+
100% {
118+
opacity: 0;
119+
}
120+
}

0 commit comments

Comments
 (0)