Repository navigation
Expand file tree
/
Copy pathinput.js
More file actions
186 lines (174 loc) · 5.17 KB
/
Copy pathinput.js
File metadata and controls
186 lines (174 loc) · 5.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
(function (SZ) {
'use strict';
const listeners = { move: [], click: [], shift: [], lockLost: [] };
let element = null;
let rawMode = 'unknown';
let locked = false;
let lastExitTs = -1e9;
let legacyRun = null;
let legacyFinish = null;
let retryTimer = 0;
let tries = 0;
// Chromium 在用户退出 Pointer Lock 后 ~1.25s 内禁止再次锁定(SecurityError)。
// 恢复测试时 ESC → 点"继续"经常落在这个窗口里,必须等待冷却后有界重试。
const COOLDOWN_MS = 1300;
const RETRY_DELAY_MS = 1400;
const MAX_TRIES = 4;
function on(evt, fn) {
listeners[evt].push(fn);
}
function emit(evt, arg) {
for (const fn of listeners[evt]) fn(arg);
}
function init(el) {
element = el;
document.addEventListener('mousemove', handleMove);
document.addEventListener('mousedown', (e) => {
if (document.pointerLockElement === element && e.button === 0) emit('click', e);
});
window.addEventListener('keydown', (e) => {
if (
document.pointerLockElement === element &&
(e.code === 'ShiftLeft' || e.code === 'ShiftRight')
) {
emit('shift', e);
}
});
document.addEventListener('pointerlockchange', () => {
locked = document.pointerLockElement === element;
if (locked) {
if (legacyFinish) {
const f = legacyFinish;
legacyFinish = null;
legacyRun = null;
f(rawMode);
}
} else {
lastExitTs = performance.now();
emit('lockLost');
}
});
document.addEventListener('pointerlockerror', () => {
// legacy 路径(requestPointerLock 不返回 Promise)失败结果只能从这里感知
if (legacyRun) {
const r = legacyRun;
legacyRun = null;
setTimeout(r, RETRY_DELAY_MS);
}
});
el.addEventListener('contextmenu', (e) => e.preventDefault());
}
const EVT = { dx: 0, dy: 0, ts: 0 };
function handleMove(e) {
if (document.pointerLockElement !== element) return;
let evts = [];
try {
evts = e.getCoalescedEvents ? e.getCoalescedEvents() : [];
} catch (_) {
evts = [];
}
if (!evts || !evts.length) evts = [e];
let lastTs = -1;
for (const ev of evts) {
EVT.dx = ev.movementX;
EVT.dy = ev.movementY;
EVT.ts = ev.timeStamp;
emit('move', EVT);
lastTs = ev.timeStamp;
}
if (e.timeStamp > lastTs) {
EVT.dx = e.movementX;
EVT.dy = e.movementY;
EVT.ts = e.timeStamp;
emit('move', EVT);
}
}
function legacyConsume(p, run, finish) {
// 老浏览器 requestPointerLock() 返回 undefined:结果依赖 pointerlockchange/error 事件
legacyRun = run;
legacyFinish = finish;
void p;
}
function lock() {
if (locked) return Promise.resolve(rawMode);
return new Promise((resolve) => {
let settled = false;
const finish = (mode) => {
if (settled) return;
settled = true;
if (mode) rawMode = mode;
resolve(rawMode);
};
tries = 0;
const run = () => {
if (settled) return;
if (locked) {
finish(rawMode);
return;
}
if (tries++ >= MAX_TRIES) {
// 放弃:调用方用 isLocked() 兜底(恢复会退回暂停层,不会裸奔)
finish(rawMode);
return;
}
let p = null;
try {
p = element.requestPointerLock({ unadjustedMovement: true });
} catch (_) {
// 参数同步抛错(老浏览器不支持选项)
try {
p = element.requestPointerLock();
} catch (_2) {
setTimeout(run, RETRY_DELAY_MS);
return;
}
legacyConsume(p, run, finish);
return;
}
if (p && typeof p.then === 'function') {
p.then(() => finish('raw')).catch((err) => {
if (err && err.name === 'NotSupportedError') {
rawMode = 'adjusted';
let p2 = null;
try {
p2 = element.requestPointerLock();
} catch (_3) {
setTimeout(run, RETRY_DELAY_MS);
return;
}
if (p2 && typeof p2.then === 'function') {
p2.then(() => finish('adjusted')).catch(() => setTimeout(run, RETRY_DELAY_MS));
} else {
legacyConsume(p2, run, finish);
}
} else {
// SecurityError(退出冷却期/其他)→ 等待后重试
setTimeout(run, RETRY_DELAY_MS);
}
});
} else {
legacyConsume(p, run, finish);
}
};
const wait = Math.max(0, lastExitTs + COOLDOWN_MS - performance.now());
clearTimeout(retryTimer);
retryTimer = setTimeout(run, wait);
});
}
function unlock() {
try {
document.exitPointerLock();
} catch (_) {}
}
function isLocked() {
return locked;
}
function getRawMode() {
return rawMode;
}
SZ.input = { init, on, lock, unlock, isLocked, getRawMode };
})(
typeof window !== 'undefined'
? (window.SZ = window.SZ || {})
: (globalThis.SZ = globalThis.SZ || {}),
);