Skip to content

Commit df82b8f

Browse files
committed
feat(Mouse): 优化自定义光标的功能和性能
- 添加初始样式和硬件加速设置,提升光标性能 - 使用 requestAnimationFrame 优化鼠标移动动画 - 增加光标状态缓存,减少不必要的 DOM 更新 - 改进光标交互逻辑,支持更多可交互元素 - 优化光标隐藏和显示效果,提升用户体验
1 parent 55a946a commit df82b8f

1 file changed

Lines changed: 103 additions & 17 deletions

File tree

scripts/common/Mouse.js

Lines changed: 103 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,29 @@ class Mouse {
55
}) {
66
this.cursor = document.createElement('div');
77
this.cursor.className = 'custom-cursor';
8+
9+
// 设置初始样式和硬件加速
10+
this.cursor.style.cssText = `
11+
position: fixed;
12+
top: 0;
13+
left: 0;
14+
width: 48px;
15+
height: 48px;
16+
background-size: contain;
17+
background-repeat: no-repeat;
18+
background-position: center;
19+
pointer-events: none;
20+
z-index: 10000;
21+
transform-origin: center;
22+
will-change: transform;
23+
transition: none;
24+
`;
25+
826
document.body.appendChild(this.cursor);
927

1028
this.defaultCursor = defaultCursor;
1129
this.clickCursor = clickCursor;
30+
this.currentCursor = null; // 缓存当前光标状态
1231

1332
this.visible = true;
1433

@@ -18,52 +37,119 @@ class Mouse {
1837
init() {
1938
this.setCursorImage(this.defaultCursor);
2039

21-
// Move
22-
document.addEventListener('mousemove', (e) => {
23-
this.showCursor();
24-
const isLink = e.target.tagName === 'A' || e.target.tagName === 'BUTTON';
25-
const cursorImg = isLink ? this.clickCursor : this.defaultCursor;
26-
const transform = isLink ? 'scale(1.5)' : 'scale(1.5) rotate(15deg)';
40+
// 使用 requestAnimationFrame 优化鼠标移动
41+
let animationId;
42+
const updateCursor = (e) => {
43+
if (animationId) {
44+
cancelAnimationFrame(animationId);
45+
}
2746

28-
this.setCursorImage(cursorImg);
29-
this.cursor.style.transform = transform;
30-
this.cursor.style.left = `${e.clientX}px`;
31-
this.cursor.style.top = `${e.clientY}px`;
32-
});
47+
animationId = requestAnimationFrame(() => {
48+
this.showCursor();
49+
50+
// 检查是否为可交互元素
51+
const isInteractive = this.isInteractiveElement(e.target);
52+
const cursorImg = isInteractive ? this.clickCursor : this.defaultCursor;
53+
54+
// 只在需要时更新光标图片
55+
this.setCursorImage(cursorImg);
56+
57+
// 使用 translate3d 进行硬件加速的位置更新
58+
const x = e.clientX - 24; // 居中偏移
59+
const y = e.clientY - 24;
60+
const scale = isInteractive ? 1.2 : 1.0;
61+
const rotation = isInteractive ? 0 : 15;
62+
63+
this.cursor.style.transform = `translate3d(${x}px, ${y}px, 0) scale(${scale}) rotate(${rotation}deg)`;
64+
});
65+
};
66+
67+
// Move
68+
document.addEventListener('mousemove', updateCursor, { passive: true });
3369

3470
// Down
3571
document.addEventListener('mousedown', () => {
3672
this.setCursorImage(this.clickCursor);
37-
this.cursor.style.transform = 'scale(1.5)';
73+
// 保持当前位置,只改变缩放
74+
const currentTransform = this.cursor.style.transform;
75+
const scaleMatch = currentTransform.match(/scale\([\d.]+\)/);
76+
const rotateMatch = currentTransform.match(/rotate\([\d.-]+deg\)/);
77+
const translateMatch = currentTransform.match(/translate3d\([^)]+\)/);
78+
79+
if (translateMatch) {
80+
this.cursor.style.transform = `${translateMatch[0]} scale(1.1) rotate(0deg)`;
81+
}
3882
});
3983

4084
// Up
41-
document.addEventListener('mouseup', () => {
42-
this.setCursorImage(this.defaultCursor);
43-
this.cursor.style.transform = 'scale(1.5) rotate(15deg)';
85+
document.addEventListener('mouseup', (e) => {
86+
const isInteractive = this.isInteractiveElement(e.target);
87+
const cursorImg = isInteractive ? this.clickCursor : this.defaultCursor;
88+
const scale = isInteractive ? 1.2 : 1.0;
89+
const rotation = isInteractive ? 0 : 15;
90+
91+
this.setCursorImage(cursorImg);
92+
93+
const currentTransform = this.cursor.style.transform;
94+
const translateMatch = currentTransform.match(/translate3d\([^)]+\)/);
95+
96+
if (translateMatch) {
97+
this.cursor.style.transform = `${translateMatch[0]} scale(${scale}) rotate(${rotation}deg)`;
98+
}
4499
});
45100

46101
// Hide native cursor
47102
document.body.style.cursor = 'none';
48103

49-
// Handle leave/enter
104+
// Handle leave/enter with proper visibility
50105
document.addEventListener('mouseleave', () => this.hideCursor());
51106
document.addEventListener('mouseenter', () => this.showCursor());
52107
}
53108

109+
// 检查元素是否可交互
110+
isInteractiveElement(element) {
111+
if (!element) return false;
112+
113+
// 检查标签类型
114+
const interactiveTags = ['A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'];
115+
if (interactiveTags.includes(element.tagName)) {
116+
return true;
117+
}
118+
119+
// 检查是否有点击事件或cursor样式
120+
const styles = window.getComputedStyle(element);
121+
if (styles.cursor === 'pointer') {
122+
return true;
123+
}
124+
125+
// 检查是否有特定的类名
126+
const interactiveClasses = ['option', 'start-btn', 'submit', 'analysis', 'continue-btn', 'next', 'prev'];
127+
if (interactiveClasses.some(cls => element.classList.contains(cls))) {
128+
return true;
129+
}
130+
131+
return false;
132+
}
133+
54134
setCursorImage(src) {
55-
if (this.cursor.style.backgroundImage !== `url("${src}")`) {
135+
// 只在光标图片真正需要改变时更新
136+
if (this.currentCursor !== src) {
56137
this.cursor.style.backgroundImage = `url("${src}")`;
138+
this.currentCursor = src;
57139
}
58140
}
59141

60142
hideCursor() {
61143
this.visible = false;
144+
this.cursor.style.opacity = '0';
145+
this.cursor.style.pointerEvents = 'none';
62146
}
63147

64148
showCursor() {
65149
if (!this.visible) {
66150
this.visible = true;
151+
this.cursor.style.opacity = '1';
152+
this.cursor.style.pointerEvents = 'none';
67153
}
68154
}
69155
}

0 commit comments

Comments
 (0)