在调用getDisplayMedia实现屏幕共享时,光标是否出现在采集画面里,其实是由Media Stream Constraints中的一个cursor约束项决定的。这个约束支持三种取值:always表示始终捕获光标,motion表示仅在光标移动时捕获,none则完全不捕获。如果直接在TypeScript项目里写这些字符串,很容易因为拼写错误或浏览器差异导致运行时问题。本文将围绕如何在TypeScript中为光标捕获定义严谨的类型展开,从基础联合类型到接口扩展,再到运行时校验,给出一套完整的实践方案。

光标捕获约束的基本概念与取值含义
先明确cursor约束的语义。根据W3C的Screen Capture规范草案,cursor是MediaTrackConstraintSet中针对显示设备采集的可选约束,类型为一个字符串枚举。always是最常见的取值,录屏教学、演示类场景基本都用它,保证观众任何时候都能看到操作者的鼠标位置。motion则是一个折中方案:光标静止时不出现在画面中,一旦移动就会显示,适合希望画面干净但又不想丢失光标指示信息的场景。none完全隐藏光标,常用于自动播放演示或画面需要后期合成的场合。
需要注意的是,cursor属于getDisplayMedia使用的Display Media约束体系,而不是getUserMedia那套普通媒体约束。两者的类型定义在lib.dom.d.ts中是分开的。此外,cursor在规范中是非标准约束(Non-standard),Chrome和Edge基于旧的Screen Capture规范实现了它,Firefox则长期不支持该约束,传入后会被静默忽略。理解这一点很重要:TypeScript类型只能保证编译期的正确性,运行时的兼容性仍需自行处理。
另一个细节是cursor只能出现在video约束对象内部,不能写在顶层。写成{ cursor: 'always' }是无效的,正确位置是{ video: { cursor: 'always' } }。这种嵌套结构也直接影响后面类型定义的写法。
在TypeScript中定义光标捕获类型的三种方式
方式一:基础联合类型
最直接的做法是定义一个字符串字面量联合类型,明确列出所有合法取值。这种写法简单直观,任何拼写错误都会在编译期被捕获。
// 定义光标捕获模式的联合类型
type CursorCaptureMode = 'always' | 'motion' | 'none';
// 屏幕共享的约束类型
interface ScreenShareConstraints {
video: {
cursor: CursorCaptureMode;
displaySurface?: 'monitor' | 'window' | 'browser';
frameRate?: number;
};
audio?: boolean;
}
// 使用示例
const constraints: ScreenShareConstraints = {
video: {
cursor: 'always',
displaySurface: 'monitor',
frameRate: 30
},
audio: false
};这种方式的缺点是与浏览器内置的DisplayMediaStreamConstraints类型脱节,如果你还需要传入其他标准约束项(比如width、height),就得手动补充所有字段,维护成本较高。
方式二:扩展官方DisplayMediaStreamConstraints接口
更推荐的做法是复用lib.dom.d.ts中的官方类型,通过接口继承或类型交叉来补充cursor字段。这样既能获得标准约束的完整类型提示,又能加上自定义的光标约束。
// 光标捕获模式
type CursorCaptureMode = 'always' | 'motion' | 'none';
// 扩展官方的 MediaTrackConstraintSet,补充 cursor 字段
interface DisplayMediaTrackConstraintSetWithCursor
extends MediaTrackConstraintSet {
cursor?: CursorCaptureMode;
displaySurface?: ConstrainDOMString;
}
// 组装完整的屏幕共享约束类型
type ScreenShareConstraintsWithCursor = DisplayMediaStreamConstraints & {
video?: boolean | DisplayMediaTrackConstraintSetWithCursor;
};
// 封装调用函数
async function startScreenShare(
constraints: ScreenShareConstraintsWithCursor
): Promise<MediaStream> {
const stream = await navigator.mediaDevices.getDisplayMedia(constraints);
return stream;
}
// 调用时获得完整类型提示
startScreenShare({
video: {
cursor: 'motion',
width: { ideal: 1920 },
height: { ideal: 1080 }
}
});通过extends MediaTrackConstraintSet,所有标准约束(分辨率、帧率、deviceId等)的类型提示都得以保留,cursor作为额外字段叠加进去。这是类型安全性与开发体验兼顾的方案。
方式三:利用声明合并补充全局类型
如果希望整个项目里getDisplayMedia的原生参数就直接支持cursor,可以利用TypeScript的声明合并特性,对全局命名空间做模块补充。这种方式改动一次,全项目生效。
// cursor-types.d.ts
declare global {
interface MediaTrackConstraintSet {
/** 屏幕共享时的光标捕获模式 */
cursor?: 'always' | 'motion' | 'none';
}
}
export {};只要在tsconfig的include范围内引入这个声明文件,任何地方写getDisplayMedia({ video: { cursor: 'always' } })都能通过类型检查。缺点是它改变了全局类型的行为,如果团队对全局污染比较敏感,建议优先使用方式二。
运行时校验与浏览器兼容处理
类型定义解决的是编译期问题,但用户传入的值可能来自配置文件或接口返回,类型系统无法拦截。例如某个配置中心下发了一个cursor: 'auto',编译期不会报错(如果用了any断言),运行时浏览器则会抛出OverconstrainedError或者静默降级。因此对动态来源的值做运行时校验是必要的。
const VALID_CURSOR_MODES = ['always', 'motion', 'none'] as const;
type CursorCaptureMode = typeof VALID_CURSOR_MODES[number];
// 运行时守卫函数,同时充当类型收窄
function isCursorMode(value: unknown): value is CursorCaptureMode {
return (
typeof value === 'string' &&
(VALID_CURSOR_MODES as readonly string[]).includes(value)
);
}
// 安全地构建约束对象
function buildConstraints(rawCursor: unknown): DisplayMediaStreamConstraints {
const video: Record<string, unknown> = {
width: { ideal: 1920 },
frameRate: 30
};
// 只有校验通过才写入 cursor,避免非法值进入约束
if (isCursorMode(rawCursor)) {
video.cursor = rawCursor;
}
return { video };
}
// 检测浏览器是否实际生效了 cursor 约束
async function checkCursorSupport(): Promise<boolean> {
try {
const stream = await navigator.mediaDevices.getDisplayMedia({
video: { cursor: 'none' }
});
const track = stream.getVideoTracks()[0];
const settings = track.getSettings();
track.stop();
return 'cursor' in settings;
} catch {
return false;
}
}上面的checkCursorSupport利用了track.getSettings()返回实际生效的采集参数这一特性。在Chrome中,如果cursor约束被接受,settings里会出现cursor字段及其生效值;而在Firefox中该字段根本不存在,据此可以判断是否需要提示用户或调整产品逻辑。这种“下发约束再回读设置”的模式,是处理所有非标准媒体约束的通用手段。
最后还有一点实践经验:cursor的取值在不同浏览器上的支持程度一直在演进,规范层面它也可能被新的CursorDisplayTrait之类的机制替代。因此在架构上建议把光标模式做成可配置项而非硬编码,配合上面提到的校验与能力检测,即使未来取值集合变化,也只需改一处类型定义和校验数组,业务代码完全不用动。
TypeScriptMedia Stream Constraints屏幕共享修改时间:2026-09-12 07:52:32