Expo移动端控制PC鼠标出现卡顿跳帧的问题求助
看起来你已经搭好了Expo移动端到C#服务器的鼠标控制链路,但遇到了卡顿跳帧的糟心事,我帮你排查了代码里的几个关键问题,应该能解决这个情况:
一、JavaScript端节流逻辑的致命疏漏
看你的Pan手势onUpdate代码里的节流判断:你设置了now - lastSentTime > 16来限制60fps的发送频率,但完全没在发送消息后更新lastSentTime的值!
这会导致节流彻底失效:第一次满足条件发完消息后,lastSentTime还是初始值,之后每次onUpdate都会触发发送(因为时间差永远大于16ms),直接导致WebSocket消息暴增,服务器处理不过来、消息堆积,这绝对是卡顿跳帧的核心原因之一。
修复方法很简单,在发送消息的代码块里加上lastSentTime = now;:
const now = Date.now(); if (now - lastSentTime > 16) { // ~60fps throttle if (onMove) runOnJS(onMove)(dx, dy); lastSentTime = now; // 必须加上这行更新时间戳! }
另外要确保lastSentTime有初始值(比如在变量声明时设为let lastSentTime = 0;)。
二、Windows鼠标API的单位误解(大坑)
你的C#代码用SendInput的MOUSEEVENTF_MOVE来移动鼠标,但这里有个很容易踩的坑:MOUSEEVENTF_MOVE的dx/dy参数不是屏幕像素,而是Windows原生的“Mickeys”单位(1 Mickey = 1/200英寸,和屏幕分辨率无关)。直接传像素值会导致实际移动距离和预期完全不符,表现为鼠标跳来跳去、移动不跟手。
这里给你两个修复方案,选一个就行:
方案1:像素转Mickeys(相对移动)
Windows里通常1像素≈2 Mickeys,你可以按这个比例转换,也可以根据系统鼠标速度调整:
private void MoveMouseBy(int dx, int dy) { // 把像素位移转成Mickeys,比例可根据实际手感调整 int mickeyDx = dx * 2; int mickeyDy = dy * 2; INPUT[] inputs = new INPUT[1]; inputs[0].type = INPUT_MOUSE; inputs[0].mi.dx = mickeyDx; inputs[0].mi.dy = mickeyDy; inputs[0].mi.mouseData = 0; inputs[0].mi.dwFlags = MOUSEEVENTF_MOVE; // 相对移动不需要ABSOLUTE标志 inputs[0].mi.time = 0; inputs[0].mi.dwExtraInfo = IntPtr.Zero; uint result = SendInput(1, inputs, Marshal.SizeOf(typeof(INPUT))); if (result == 0) { Console.WriteLine("SendInput failed with error: " + Marshal.GetLastWin32Error()); } }
方案2:绝对位置移动(更精准)
获取当前鼠标位置,加上位移后转换成Windows标准的绝对坐标范围(0到65535),用MOUSEEVENTF_ABSOLUTE标志发送,这种方式手感会更跟手:
private void MoveMouseToAbsolute(int x, int y) { int screenWidth = Screen.PrimaryScreen.Bounds.Width; int screenHeight = Screen.PrimaryScreen.Bounds.Height; // 把屏幕像素坐标转成Windows绝对坐标(范围0-65535) int absoluteX = (x * 65535) / screenWidth; int absoluteY = (y * 65535) / screenHeight; INPUT[] inputs = new INPUT[1]; inputs[0].type = INPUT_MOUSE; inputs[0].mi.dx = absoluteX; inputs[0].mi.dy = absoluteY; inputs[0].mi.mouseData = 0; inputs[0].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; inputs[0].mi.time = 0; inputs[0].mi.dwExtraInfo = IntPtr.Zero; uint result = SendInput(1, inputs, Marshal.SizeOf(typeof(INPUT))); if (result == 0) { Console.WriteLine("SendInput failed with error: " + Marshal.GetLastWin32Error()); } } // 然后修改MoveMouseRelative方法,改用绝对位置移动: public bool MoveMouseRelative(double dx, double dy, MousePadSize padSize) { double mobileWidth = padSize?.Width ?? 300; double mobileHeight = padSize?.Height ?? 500; int screenWidth = Screen.PrimaryScreen.Bounds.Width; int screenHeight = Screen.PrimaryScreen.Bounds.Height; double scaleX = (double)screenWidth / mobileWidth; double scaleY = (double)screenHeight / mobileHeight; double scaledDx = dx * scaleX; double scaledDy = dy * scaleY; accumX += scaledDx * sensitivity; accumY += scaledDy * sensitivity; int moveX = (int)Math.Round(accumX); int moveY = (int)Math.Round(accumY); if (moveX == 0 && moveY == 0) return true; accumX -= moveX; accumY -= moveY; // 获取当前鼠标位置,计算新位置并限制在屏幕范围内 Point currentPos = Cursor.Position; int newX = Math.Clamp(currentPos.X + moveX, 0, screenWidth - 1); int newY = Math.Clamp(currentPos.Y + moveY, 0, screenHeight - 1); MoveMouseToAbsolute(newX, newY); Console.WriteLine($"Moving mouse to: ({newX},{newY})"); return true; }
三、其他小优化建议
- JS端坐标精度:尽量用
e.absoluteX/e.absoluteY替代e.x/e.y,避免组件内坐标的偏移问题,让位移计算更精准。 - 服务器端异步处理:如果WebSocket消息还是有点多,可以在C#端加个小型的异步消息队列,把鼠标移动请求放到队列里异步处理,避免阻塞主线程导致的延迟。
- 关闭手势惯性:你已经注释掉了
onEnd里的惯性代码,这个做得很对——鼠标控制不需要触摸手势的惯性,保持立即停止的逻辑会更跟手。
内容来源于stack exchange




