① HTML — 网页的骨架
HTML(HyperText Markup Language)是一种标记语言,用语义化标签描述网页结构。 浏览器解析 HTML 生成 DOM 树,是渲染流程的起点。
1.1 渲染原理
- 解析 HTML → 构建 DOM 树
- 解析 CSS → 构建 CSSOM 树
- 合并生成 渲染树(Render Tree)
- 布局(Layout / Reflow):计算节点几何信息
- 绘制(Paint):将像素填充到图层
- 合成(Composite):交给 GPU 合成为最终画面
1.2 语义化标签示例
<header>头部</header>
<nav>导航</nav>
<main>
<article>文章</article>
<aside>侧栏</aside>
</main>
<footer>底部</footer>
语义化的优势:可访问性(无障碍)、SEO 友好、代码可读性强。
1.3 实战演示:表单与验证
② CSS — 视觉与布局
CSS(Cascading Style Sheets)控制元素的外观与排版。核心机制: 层叠(Cascade)、继承(Inheritance)、特异性(Specificity)。
2.1 盒模型(Box Model)
每个元素由 content → padding → border → margin 四层组成。
通过 box-sizing: border-box 可让 width 包含 padding+border,更易布局。
2.2 Flexbox 演示(可调)
2.3 Grid 网格布局
.grid-stage {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-areas:
"a a"
"b c"
"d d";
}
2.4 动画与过渡
原理:transform 与 opacity 由合成线程处理,不触发重排,性能最佳。
③ JavaScript — 让页面活起来
JavaScript 是一门单线程、基于原型、动态弱类型的语言,运行于 V8 等引擎之上,通过事件循环实现并发。
3.1 数据类型
- 原始类型:
string / number / boolean / null / undefined / symbol / bigint - 引用类型:
object(函数、数组、Date 等都是对象) - 检测:
typeof(无法区分 null / 数组);推荐Object.prototype.toString.call(x)
3.2 闭包(Closure)
函数与其词法作用域的组合。即使外部函数已返回,内部函数仍能访问其变量。
function counter() {
let n = 0;
return () => ++n;
}
const inc = counter();
inc(); // 1
inc(); // 2
0
3.3 原型链
每个对象都有一个隐式原型 __proto__ 指向其构造函数的 prototype,访问属性时沿链向上查找直到 null。
function Animal(name) { this.name = name; }
Animal.prototype.say = function () { return 'I am ' + this.name; };
const a = new Animal('Tom');
a.say(); // I am Tom
a.__proto__ === Animal.prototype; // true
3.4 事件循环(Event Loop)
同步代码 → 微任务(Promise.then / queueMicrotask)→ 宏任务(setTimeout / I/O)→ 渲染 → 下一轮。
3.5 异步:Promise & async/await
async function loadUser(id) {
const res = await fetch('/api/user/' + id);
if (!res.ok) throw new Error('Network');
return res.json();
}
3.6 防抖 & 节流
移动鼠标查看触发次数,对比效果:
④ 现代框架 — React / Vue
框架本质:用声明式 UI + 响应式数据替代手动操作 DOM。底层都基于虚拟 DOM 与 Diff 算法。
4.1 虚拟 DOM 原理
- 用 JS 对象描述 UI 结构(VNode)
- 状态变化时生成新的 VNode 树
- 通过 Diff 算法对比新旧树,得到最小变更集(Patch)
- 仅更新真实 DOM 中变更的节点
4.2 React 示例(JSX)
import { useState } from 'react';
export function Counter() {
const [n, setN] = useState(0);
return (
<button onClick={() => setN(n + 1)}>
点击次数:{n}
</button>
);
}
4.3 Vue 3 示例(Composition API)
<script setup>
import { ref } from 'vue';
const n = ref(0);
</script>
<template>
<button @click="n++">点击次数:{{ n }}</button>
</template>
4.4 用原生 JS 实现一个迷你响应式
双向绑定输入框:
实时输出:
function reactive(obj, onChange) {
return new Proxy(obj, {
set(t, k, v) { t[k] = v; onChange(k, v); return true; }
});
}
const state = reactive({ msg: '' }, (k, v) => render(v));
⑤ 工程化 — 让项目可维护
现代前端工程涉及:包管理、模块化、构建打包、代码规范、测试、CI/CD。
5.1 模块化演进
- IIFE 立即执行函数 → 全局污染解决方案
- CommonJS(Node.js)→
require / module.exports - AMD / CMD(浏览器异步)
- ES Modules(标准)→
import / export
5.2 构建工具
- Webpack:万物皆 Loader / Plugin
- Vite:基于 ESM + esbuild,秒级冷启动
- Rollup:库打包首选,Tree-shaking 出色
- esbuild / SWC:底层 Go/Rust 加速器
5.3 包管理
npm/yarn/pnpm- pnpm 通过硬链接节省磁盘 + 严格依赖隔离
package.json中^/~的语义
5.4 代码规范
- ESLint:JS/TS 静态检查
- Prettier:统一格式化
- Husky + lint-staged:提交前自动校验
- TypeScript:类型安全的 JavaScript
⑥ 性能优化 — 极致体验
6.1 关键指标(Core Web Vitals)
- LCP(最大内容绘制)≤ 2.5s
- FID / INP(交互延迟)≤ 100ms / 200ms
- CLS(累积布局偏移)≤ 0.1
6.2 优化清单
加载优化
- 资源压缩(gzip / brotli)
- HTTP 缓存(强缓存 + 协商缓存)
- CDN 分发 / HTTP/2 多路复用
- 代码分割 + 路由懒加载
- 图片懒加载 + WebP/AVIF
运行时优化
- 虚拟列表渲染长列表
- 避免强制同步布局
- 使用
transform实现动画 - Web Worker 卸载主线程
- 合理使用
requestIdleCallback
6.3 实测:requestAnimationFrame 帧率监测
FPS:--⑦ 进阶与新技术 — 现代 Web 能力
浏览器早已不只是「文档查看器」。下面这些 API 让 Web 拥有了实时通信、多线程、原生级性能、离线能力与硬件访问。
7.1 WebSocket — 全双工实时通信
基于 TCP 的持久化双向协议,握手通过 HTTP Upgrade 完成,之后客户端与服务器均可主动推送消息,
典型场景:聊天、协同编辑、实时行情、游戏同步。
const ws = new WebSocket('wss://echo.example.com');
ws.onopen = () => ws.send('hello');
ws.onmessage = (e) => console.log('收到:', e.data);
ws.onclose = () => console.log('连接关闭');
ws.onerror = (err) => console.error(err);
下方演示使用本地模拟服务端(Promise + setTimeout),离线也能体验消息往返:
对比:HTTP 轮询(高延迟、资源浪费)→ 长轮询 → SSE(服务端单向推送)→ WebSocket(双向实时)。
7.2 Server-Sent Events (SSE) — 服务端单向推送
基于 HTTP,服务器持续向客户端推送 text/event-stream。比 WebSocket 简单,
自动断线重连。AI 流式输出(如 ChatGPT 打字效果)就是典型应用。
const es = new EventSource('/api/stream');
es.onmessage = (e) => console.log(e.data);
es.addEventListener('done', () => es.close());
7.3 Web Workers — 浏览器多线程
JavaScript 主线程负责 UI 与脚本,耗时计算会卡顿页面。Worker 在独立线程运行,通过
postMessage 与主线程通信,无 DOM 访问权限。
// main.js
const worker = new Worker('/worker.js');
worker.postMessage(40);
worker.onmessage = (e) => console.log('结果:', e.data);
// worker.js
self.onmessage = (e) => {
const fib = (n) => (n < 2 ? n : fib(n - 1) + fib(n - 2));
self.postMessage(fib(e.data));
};
对比时观察右上角小球是否依然丝滑:
7.4 Service Worker & PWA — 离线可用 / 可安装
Service Worker 是浏览器与网络之间的代理层,可拦截请求、读写 Cache,实现离线访问、推送通知、后台同步。
配合 manifest.json 即可让网站像原生 App 一样安装到桌面。
// 注册
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
// sw.js
self.addEventListener('install', (e) => {
e.waitUntil(
caches.open('v1').then((c) => c.addAll(['/', '/main.js', '/style.css']))
);
});
self.addEventListener('fetch', (e) => {
e.respondWith(caches.match(e.request).then((r) => r || fetch(e.request)));
});
- 缓存策略:Cache First / Network First / Stale-While-Revalidate
- 生命周期:install → activate → fetch / push / sync
- 限制:仅 HTTPS(localhost 除外)、独立线程、无 DOM
7.5 WebAssembly (Wasm) — 接近原生的性能
一种二进制指令格式,可由 C / C++ / Rust / Go 编译而来,在浏览器中以接近原生速度运行。 适合:音视频处理、3D 引擎、游戏、加密、AI 推理。
const { instance } = await WebAssembly.instantiateStreaming(
fetch('/add.wasm')
);
console.log(instance.exports.add(1, 2)); // 3
代表项目:FFmpeg.wasm、Figma 渲染引擎、AutoCAD Web、Photoshop Web。
7.6 Canvas — 像素级绘图
<canvas> 通过 JS 绘制 2D / 3D 图形。下方为粒子动画演示(点击切换):
WebGL / WebGPU:在 Canvas 之上提供 GPU 加速 API,是 Three.js、Babylon.js、Unity Web 的底层。 WebGPU(2023 起)是更现代、更接近 Vulkan/Metal 的下一代标准。
7.7 Web Components — 浏览器原生组件化
三大基石:Custom Elements(自定义元素)、Shadow DOM(样式封装)、HTML Templates。 无需框架即可写出可复用、样式隔离的组件。
class FancyBtn extends HTMLElement {
constructor() {
super();
const root = this.attachShadow({ mode: 'open' });
root.innerHTML = `
<style>button{padding:8px 16px;border-radius:8px;}</style>
<button><slot></slot></button>`;
}
}
customElements.define('fancy-btn', FancyBtn);
真实的自定义元素(已注册):
7.8 浏览器存储全景
| 方案 | 容量 | 结构 | 适用场景 |
|---|---|---|---|
| Cookie | ~4KB | 键值(随请求发送) | 会话、身份 |
| localStorage | ~5MB | 同步键值 | 用户偏好 |
| sessionStorage | ~5MB | 同步键值,会话级 | 临时数据 |
| IndexedDB | 数百 MB+ | 异步对象数据库 | 大数据、离线 |
| Cache Storage | 大 | Request/Response | PWA 离线 |
下方使用 localStorage 演示笔记持久化(刷新仍在):
已保存:--
7.9 File API & 拖拽上传
支持 FileReader、Blob、URL.createObjectURL,无需上传即可在前端预览图片。
7.10 还有更多现代 Web API
实时与媒体
- WebRTC:浏览器之间 P2P 音视频/数据
- Media Capture:getUserMedia 摄像头/麦克风
- Web Audio API:实时音频合成与可视化
- WebCodecs:低层级编解码(4K 实时)
设备与硬件
- WebUSB / Web Serial / Web Bluetooth
- Web NFC:读写 NFC 标签
- Geolocation:定位
- Sensor API:陀螺仪/加速度
性能与体验
- View Transitions API:原生页面过渡动画
- Container Queries:基于容器的响应式
- Popover API / Dialog:原生弹层
- Speculation Rules:预渲染下一页
AI 与未来
- WebGPU:下一代 GPU 接口,可跑本地 LLM
- Built-in AI (Chrome):浏览器内置 Gemini Nano
- WebTransport:基于 HTTP/3 的低延迟通道
- Origin Private File System:高性能文件系统
7.11 视频通话 — 媒体捕获 + WebRTC + WebSocket 信令
视频通话由三件套协作完成:
- 📹 getUserMedia:调用摄像头/麦克风,拿到
MediaStream - 🔗 WebRTC(RTCPeerConnection):在两端建立 P2P 媒体通道,自动处理编解码、丢包、抖动、NAT 穿透
- 📡 WebSocket(信令):交换
SDP(offer/answer)与ICE candidate,建连后媒体流不经服务器
协议交互流程
- 双方用
getUserMedia获取本地音视频轨道 - A 创建
RTCPeerConnection→createOffer()→ 通过 WS 把 SDP 发给 B - B 收到 offer →
setRemoteDescription→createAnswer()→ 回传 SDP - 双方互相通过 WS 推送
icecandidate,直到找到最佳网络路径 - 连通后通过
ontrack拿到对端MediaStream,挂到<video>即可
// === 1. 采集媒体 ===
const localStream = await navigator.mediaDevices.getUserMedia({
video: { width: 1280, height: 720, facingMode: 'user' },
audio: { echoCancellation: true, noiseSuppression: true },
});
localVideo.srcObject = localStream;
// === 2. 建立连接(含 STUN/TURN) ===
const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});
// 把本地轨道加入连接
localStream.getTracks().forEach((t) => pc.addTrack(t, localStream));
// 收到对端轨道
pc.ontrack = (e) => (remoteVideo.srcObject = e.streams[0]);
// 收集 ICE 候选并通过信令发出去
pc.onicecandidate = (e) => {
if (e.candidate) signaling.send({ type: 'ice', candidate: e.candidate });
};
// === 3. WebSocket 信令 ===
const signaling = new WebSocket('wss://your-server/signal');
signaling.onmessage = async ({ data }) => {
const msg = JSON.parse(data);
if (msg.type === 'offer') {
await pc.setRemoteDescription(msg.sdp);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
signaling.send({ type: 'answer', sdp: answer });
} else if (msg.type === 'answer') {
await pc.setRemoteDescription(msg.sdp);
} else if (msg.type === 'ice') {
await pc.addIceCandidate(msg.candidate);
}
};
// 主叫方发起
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
signaling.send({ type: 'offer', sdp: offer });
实战演示(本地回环 P2P)
下方在同一页面建立两个 RTCPeerConnection 互连,把摄像头画面通过真实的 WebRTC 链路传到「远端」窗口。
首次点击会请求摄像头权限。
⚠️ 浏览器要求 HTTPS 或 localhost 才能调用摄像头。
关键知识点
SDP(Session Description Protocol)
- 描述「我支持的编解码、分辨率、加密方式」
- 双方协商出共同支持的能力
- 是纯文本,可在 console 打印 offer 看一下
ICE / STUN / TURN
- ICE:枚举所有可能的网络路径
- STUN:帮你查到自己的公网 IP
- TURN:双方都在严格 NAT 后时的中继服务器
媒体约束(Constraints)
video: { width, height, frameRate, facingMode }audio: { echoCancellation, noiseSuppression, autoGainControl }getDisplayMedia():屏幕共享
常见扩展能力
- DataChannel:基于同一连接的低延迟数据通道(聊天/文件)
- SFU 架构:多人会议必备(Janus / mediasoup / LiveKit)
- 录制:MediaRecorder + Blob
- 美颜/虚拟背景:Canvas/WebGL/MediaStreamTrackProcessor
7.12 多人会议 — SFU 中转架构
上一节的 P2P 只适合 1v1 或最多 3 人。人数变多时,每人都要把自己的流上传给 (N-1) 个人, 上行带宽呈 O(N²) 爆炸。生产级多人会议必须走中转服务器。
三种架构对比
| 架构 | 拓扑 | 上行带宽 | 服务器压力 | 画质 | 适用 |
|---|---|---|---|---|---|
| Mesh (P2P) | 人人互连 | O(N-1) 份 | 无 | 高 | ≤3 人 |
| SFU 转发 | 上传 1 份,服务器复制分发 | O(1) 份 | 中(只转发) | 高 | 会议、直播连麦 |
| MCU 混流 | 服务器解码/合成后再编码下发 | O(1) 份 | 高(算力贵) | 受损 | 兼容老设备/电话接入 |
SFU 协议流程
- 每个参会者通过 WebSocket 连到信令服务器,发送
join(roomId) - 服务器返回当前房间成员列表
- 参会者与 SFU 建立一条
RTCPeerConnection:- 上行 (publish):把本地轨道
addTrack给 SFU - 下行 (subscribe):SFU 把其他成员的轨道
addTrack推给自己
- 上行 (publish):把本地轨道
- 有人加入/离开时,SFU 通过 WS 通知所有人,动态
addTrack / removeTrack - 离开时关闭 PC,停止轨道
// 参会者侧伪代码
const ws = new WebSocket('wss://sfu.example/ws');
const pc = new RTCPeerConnection({ iceServers: [...] });
ws.onopen = () => ws.send(JSON.stringify({ cmd: 'join', room: 'R-1', uid }));
ws.onmessage = async ({ data }) => {
const msg = JSON.parse(data);
switch (msg.cmd) {
case 'peer-join':
await subscribe(msg.uid); // 订阅新成员的流
break;
case 'peer-leave':
removeVideo(msg.uid); // 清理 UI
break;
case 'answer':
await pc.setRemoteDescription(msg.sdp);
break;
}
};
// 上行:发布自己
localStream.getTracks().forEach((t) => pc.addTrack(t, localStream));
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
ws.send(JSON.stringify({ cmd: 'publish', sdp: offer }));
// 下行:收到别人的轨道时渲染
pc.ontrack = (e) => renderTile(e.streams[0], e.track.id);
实战演示(模拟 SFU Hub)
下方在本页面内用一个 SfuHub 纯 JS 类模拟中转服务器:真实摄像头只打开一份(
getUserMedia),然后通过 Hub "分发"给多个虚拟参会者,每个参会者独立渲染一个画面格。
真实项目里把 Hub 替换成 mediasoup/Janus/LiveKit 服务端即可。
- 我的上行:0 Mbps(始终 1 份)
- 我的下行:0 Mbps(随人数线性增长)
- SFU 服务器转发总量:0 Mbps
生产级要点
Simulcast(分层编码)
- 同一路视频编多个码率(高/中/低)
- SFU 根据订阅者网络自动选层
- 主讲人高清,小窗口低清,省带宽
SVC 可伸缩编码
- 一路码流包含多时域/空域层
- 比 Simulcast 更省上行
- VP9 / AV1 / H.265 支持
QoS 保障
- NACK / FEC / PLI:丢包补偿
- JitterBuffer:抖动平滑
- 拥塞控制:GCC / BBR
开源方案
- mediasoup:Node.js,灵活,社区最活
- Janus:C 写的老牌稳
- LiveKit:Go 写的云原生,自带客户端 SDK
- Pion:纯 Go WebRTC 栈
7.13 WebRTC 深度解析 — 协议栈 / DataChannel / 质量监控
WebRTC 不是一个 API,而是一整套实时通信协议栈。它把复杂的音视频采集、编解码、
网络传输、NAT 穿透、加密、QoS 全部封装成三个核心对象:
MediaStream、RTCPeerConnection、RTCDataChannel。
协议栈全景
┌──────────────────────────────────────────────────┐
│ JavaScript API:getUserMedia / RTCPeerConnection │
├──────────────────────────────────────────────────┤
│ SRTP(加密音视频) │ SCTP(数据) │
├──────────────────────────────────────────────────┤
│ DTLS(密钥协商 / 加密) │
├──────────────────────────────────────────────────┤
│ ICE(候选枚举) + STUN(打洞) + TURN(中继) │
├──────────────────────────────────────────────────┤
│ UDP │
└──────────────────────────────────────────────────┘
- ICE:综合调度 STUN/TURN,产出可达的"候选对"
- DTLS:握手生成密钥;SRTP/SCTP 基于它加密
- 音视频走 SRTP;任意数据走 SCTP(DataChannel)
- WebRTC 默认全程 E2E 加密,无法关闭
NAT 类型与穿透成功率
| NAT 类型 | 映射规则 | 穿透成功率 | 备注 |
|---|---|---|---|
| Full Cone | IP+端口外网固定 | ✅ 很高 | 家用路由器常见 |
| Restricted Cone | 按远端 IP 限制 | ✅ 高 | 大部分办公网 |
| Port Restricted | 按 IP+端口限制 | ⚠️ 中 | STUN 仍可穿 |
| Symmetric NAT | 每个目的地址映射不同端口 | ❌ 低 | 必须 TURN 中继(对称型) |
经验值:纯 STUN ≈ 70%~80%,加 TURN 可达 99%+,生产必须部署 TURN。
RTCDataChannel — 被忽视的利器
同一条 PeerConnection 上可开多个 DataChannel,用于任意二进制/文本数据。 相比 WebSocket 的优势:
- ✅ P2P:数据不过服务器,零延迟、零费用
- ✅ 可选 UDP 或 TCP 语义:游戏用 unordered + maxRetransmits=0,文件用 ordered reliable
- ✅ 默认加密(DTLS-SRTP)
- ⚠️ 受 MTU 限制(~16KB),大文件需分片
// 创建可靠有序(默认,类似 TCP)
const chat = pc.createDataChannel('chat');
// 游戏用 "不可靠+无序"(类似 UDP,延迟最低)
const game = pc.createDataChannel('game', {
ordered: false,
maxRetransmits: 0,
});
chat.onopen = () => chat.send('hi');
chat.onmessage = (e) => console.log('收到:', e.data);
// 发二进制
const ab = new Uint8Array([1, 2, 3]).buffer;
chat.send(ab);
实战 A:DataChannel P2P 聊天 & 文件传输
在同页面建立 dcA ↔ dcB,通过 DataChannel 传输文本和文件(自动分片)。 真实场景下这就是协同编辑、游戏同步、WebRTC 网盘(如 snapdrop.net)的底层。
实战 B:实时质量监控(getStats API)
生产级 WebRTC 必须监控 pc.getStats(),用来观察:RTT、丢包率、抖动、码率、帧率、分辨率。
下方面板每秒采样一次并折线展示:
💡 getStats() 返回的是 RTCStatsReport(Map)。
常用 type:inbound-rtp、outbound-rtp、candidate-pair、remote-inbound-rtp。
实战 C:MediaRecorder 本地录制
WebRTC 的 MediaStream 可直接交给 MediaRecorder 编码成 webm 文件,
实现"本地录制"而无需走服务端,广泛用于在线面试录像、Loom 式屏幕录制。
const recorder = new MediaRecorder(stream, { mimeType: 'video/webm;codecs=vp9' });
const chunks = [];
recorder.ondataavailable = (e) => chunks.push(e.data);
recorder.onstop = () => {
const blob = new Blob(chunks, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
// 触发下载
};
recorder.start(1000); // 每 1s 触发一次 ondataavailable
安全 / 合规关键点
E2E 加密
- DTLS 握手后所有流量加密,SFU 也看不到明文
- SFU 架构下可用 Insertable Streams 做业务层二次加密(如 Zoom 真端到端)
- 证书指纹通过 SDP 交换,防中间人
隐私泄漏
- 默认 ICE 会暴露本机内网 IP("WebRTC IP Leak")
iceTransportPolicy: 'relay'强制走 TURN,隐藏真实 IP- mDNS 候选(Chrome 默认)已缓解该问题
常见坑
- 必须 HTTPS(localhost 除外)
- Safari 对编解码支持较新版才全
- 移动端后台会挂起 PeerConnection
- TURN 费用:流量按出口计,生产要限流
进阶主题
- Perfect Negotiation:应对双方同时 offer 的协商模式
- RTCRtpTransceiver:细粒度控制收发方向
- Trickle ICE:候选产出即发送,不等全部完成
- RTP Header Extensions:扩展 RTP 头,实现带宽估计等
7.14 现代 Web API 实战集 — 12 个让网页像原生 App 的能力
浏览器正在加速补齐"原生应用才有"的能力:底层网络、编解码、硬件、支付、认证、XR…… 下面每个 API 都配能力检测 + 代码示例 + 实测按钮。 ⚠️ 部分 API 仅在 Chrome / Edge 支持,Firefox/Safari 会显示"未支持"。
基于 HTTP/3 (QUIC) 的多路复用通道,同时提供可靠流(类 TCP)与不可靠数据报(类 UDP)。相比 WebSocket,队头阻塞更少、连接迁移更快。
const wt = new WebTransport('https://example.com/wt');
await wt.ready;
// 1) 不可靠数据报(游戏状态同步)
const writer = wt.datagrams.writable.getWriter();
writer.write(new Uint8Array([1, 2, 3]));
// 2) 可靠双向流(大文件/信令)
const stream = await wt.createBidirectionalStream();
const w = stream.writable.getWriter();
w.write(new TextEncoder().encode('hello'));
直接访问浏览器的硬件编解码器,拿到逐帧 VideoFrame,可做 Web 版剪映、帧级合成、WebRTC 前处理。
const decoder = new VideoDecoder({
output: (frame) => {
canvasCtx.drawImage(frame, 0, 0);
frame.close(); // 必须手动释放!
},
error: (e) => console.error(e),
});
decoder.configure({ codec: 'vp09.00.10.08' });
decoder.decode(chunk);
--
近原生速度执行,承载 ffmpeg / Photoshop / Pyodide / SQLite / AutoCAD。下方实测:用 WASM 字节码算斐波那契,对比 JS 版本。
// fib.wat 编译后加载
const { instance } = await WebAssembly.instantiate(wasmBytes);
instance.exports.fib(30); // ~1ms vs JS 10ms
--
跨 Tab / iframe / Worker 的异步互斥锁。最典型:多 Tab 打开时只让一个 Tab 刷 Token。
await navigator.locks.request('auth-token', async (lock) => {
// 此块代码同一 origin 下同时只有一个在执行
const token = await refreshToken();
localStorage.setItem('token', token);
});
--
调起系统/浏览器原生支付面板(Apple Pay / Google Pay / 信用卡),无需手动填表。
const req = new PaymentRequest(
[{ supportedMethods: 'basic-card' }],
{
total: { label: '订单', amount: { currency: 'CNY', value: '99.00' } },
}
);
const response = await req.show();
await response.complete('success');
用指纹 / Face ID / 硬件密钥代替密码。底层是公私钥签名,网站只存公钥。
// 注册(生成密钥对)
const cred = await navigator.credentials.create({
publicKey: {
challenge,
rp: { name: 'My App' },
user: { id, name: 'zhangke', displayName: 'ZK' },
pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
},
});
// 登录(验证签名)
await navigator.credentials.get({ publicKey: { challenge } });
--
让 PWA 注册为系统上某类文件(.md / .txt)的默认打开方式,双击即用网页打开。需要在 manifest.json 配置:
{
"file_handlers": [{
"action": "/open",
"accept": { "text/markdown": [".md"] }
}]
}
if ('launchQueue' in window) {
launchQueue.setConsumer(async (launchParams) => {
for (const handle of launchParams.files) {
const file = await handle.getFile();
editor.setValue(await file.text());
}
});
}
感知用户是否离开电脑(系统级空闲),聊天/网银场景自动改"离开"或退出登录。
const idle = new IdleDetector();
idle.addEventListener('change', () => {
console.log(idle.userState, idle.screenState);
// active / idle unlocked / locked
});
await idle.start({ threshold: 60_000 });
除纯文本外还能读写 HTML / 图片 / 自定义 MIME,实现 Office/Docs 那种"保留格式粘贴"。
// 写入富文本 + 纯文本
await navigator.clipboard.write([
new ClipboardItem({
'text/html': new Blob(['<b>hi</b>'], { type: 'text/html' }),
'text/plain': new Blob(['hi'], { type: 'text/plain' }),
}),
]);
// 读取图片
const items = await navigator.clipboard.read();
for (const it of items) {
if (it.types.includes('image/png')) {
const blob = await it.getType('image/png');
}
}
--
直连 HID 设备:游戏手柄、绘图板侧键、条码枪、专业键盘。
const [device] = await navigator.hid.requestDevice({ filters: [] });
await device.open();
device.oninputreport = (e) => {
const data = new Uint8Array(e.data.buffer);
console.log(device.productName, data);
};
--
直接与 Arduino / STM32 / GPS / 工业仪表通信,浏览器里做 3D 打印机控制台、烧录工具。
const port = await navigator.serial.requestPort();
await port.open({ baudRate: 115200 });
const reader = port.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
console.log(new TextDecoder().decode(value));
}
--
连接电子琴/鼓垫/MIDI 控制器。按键触发时收到 [status, note, velocity] 三字节消息。
const midi = await navigator.requestMIDIAccess();
midi.inputs.forEach((input) => {
input.onmidimessage = (e) => {
const [cmd, note, vel] = e.data;
if (cmd === 144 && vel > 0) playNote(note);
};
});
--
在 VR 头显 (Quest) / AR 眼镜 (HoloLens) 里渲染 Web 内容,获取手柄与手势。免下载 App 的 Web VR。
if (navigator.xr) {
const ok = await navigator.xr.isSessionSupported('immersive-vr');
if (ok) {
const session = await navigator.xr.requestSession('immersive-vr');
// 接入 Three.js: renderer.xr.setSession(session)
}
}
--
📌 以上 API 中,涉及硬件或需要用户手势授权的(HID/Serial/MIDI/Payment/WebAuthn)会弹出浏览器原生选择框; 硬件类在没有连接对应设备时会返回空列表,这是正常行为。
7.15 流程图 & 画布编排 — 可视化工作流的核心技术
低代码平台、工作流引擎、白板、数据血缘、DAG 编排、节点式 Shader 编辑器……背后都是同一套能力: 节点(Node)+ 连线(Edge)+ 画布(Canvas)。本节系统拆解三种主流实现方案。
三大渲染方案对比
| 方案 | 定位 | 性能上限 | 优势 | 劣势 | 适合 |
|---|---|---|---|---|---|
| DOM + SVG | 元素级 | ≤ 千级节点 | 可用 CSS 样式/事件/无障碍,矢量清晰 | 大量节点时 DOM 树重 | 流程图、ER 图、思维导图 |
| Canvas 2D | 栅格绘制 | 万级节点 | 绘制快、内存省 | 没有 DOM 节点,命中测试要自己算 | 白板、绘图、大规模 DAG |
| WebGL / Pixi | GPU 加速 | 十万级节点 | 性能天花板最高,支持粒子/滤镜 | 学习曲线陡,调试难 | 地图、游戏、可视化大屏 |
主流开源库
流程图 / DAG
- AntV X6:阿里,功能完善,SVG + 插件生态
- LogicFlow:滴滴,更轻量,BPMN 场景
- React Flow:React 生态首选,社区最活
- Drawflow:Vanilla JS,一键集成
- jsPlumb:老牌,连线算法强
- Dagre:只做自动布局,常与上述配合
画布 / 白板 / 图形
- Konva.js:Canvas 2D 图形框架,事件系统完整
- Fabric.js:图像编辑,类 Photoshop
- tldraw:现代白板库,Figma 级交互
- Excalidraw:手绘风白板,内嵌友好
- Rough.js:手绘风 SVG/Canvas
- PixiJS:WebGL 2D 渲染引擎
核心算法
- 自动布局:Dagre(层次)、ELK(Eclipse Layout Kernel)、Force(力导向)
- 连线路由:曼哈顿路由、贝塞尔曲线、正交避让
- 命中测试:AABB 包围盒、四叉树(Quadtree)加速
- 缩放:CTM(Current Transform Matrix)+ 矩阵逆运算
典型应用
- 低代码:钉钉宜搭、腾讯微搭、Mendix
- 工作流:审批流、BPMN
- 数据编排:Airflow UI、Dify、n8n、LangFlow
- 白板协作:Figma、Miro、腾讯文档画板
- AI 流水线:ComfyUI、Rivet
核心原理
- 数据模型:
{ nodes: [...], edges: [...] },节点存坐标/类型,边存 source/target - 渲染:根据数据绘制节点与连线(声明式),状态变化 → Diff → 最小重绘
- 交互:拖拽移动节点 / 从锚点拖出连线 / 框选 / 右键菜单 / 键盘删除
- 连线路径:贝塞尔曲线
M x1 y1 C cx1 cy1, cx2 cy2, x2 y2 - 命中测试:
elementFromPoint(DOM) 或几何判断(Canvas) - Undo/Redo:命令模式 + 栈,或直接保存快照
- 协同:CRDT (Yjs / Automerge) 或 OT 算法
/** 贝塞尔连线 · 横向流程图最常用 */
function bezierPath(s, t) {
const dx = Math.max(Math.abs(t.x - s.x) * 0.5, 50);
return `M ${s.x} ${s.y} C ${s.x + dx} ${s.y}, ${t.x - dx} ${t.y}, ${t.x} ${t.y}`;
}
/** 正交连线(曼哈顿) · BPMN 风格 */
function orthogonalPath(s, t) {
const mx = (s.x + t.x) / 2;
return `M ${s.x} ${s.y} L ${mx} ${s.y} L ${mx} ${t.y} L ${t.x} ${t.y}`;
}
🧩 实战 A:SVG 流程编辑器(纯原生,零依赖)
完整支持:左侧拖入节点 · 拖动移动 · 锚点拖出连线 · 点击选中 · Delete 删除 · 导出 JSON · 导入 JSON · Undo/Redo。
📤 导出/导入 JSON(点击展开)
🎨 实战 B:Canvas 无限画布(Pan / Zoom / 坐标系)
所有画布工具(Figma / Miro / Excalidraw)的共同基础:一个可平移、可缩放的虚拟坐标系。 操作:鼠标拖拽平移 · 滚轮缩放(以光标为中心)· 点击节点选中 · 双击空白处新建。
关键代码片段
/** 屏幕坐标 → 世界坐标(Pan/Zoom 的核心) */
function screenToWorld(sx, sy, scale, offsetX, offsetY) {
return { x: (sx - offsetX) / scale, y: (sy - offsetY) / scale };
}
/** 以光标为中心缩放 —— Figma/Miro 的标准做法 */
function zoomAt(cx, cy, delta) {
const factor = delta > 0 ? 0.9 : 1.1;
const wx = (cx - offsetX) / scale;
const wy = (cy - offsetY) / scale;
scale *= factor;
offsetX = cx - wx * scale;
offsetY = cy - wy * scale;
}
选型建议
🟢 业务项目推荐
- React 项目 → React Flow(API 设计优秀)
- Vue/多技术栈 → AntV X6(功能最全)
- BPMN 流程 → LogicFlow 或 bpmn-js
- 白板 → 直接魔改 tldraw 或 Excalidraw
🛠 自研时的常见坑
- 节点多时频繁 setState 会卡顿,建议 batch + 惰性渲染
- SVG 连线用
will-change/transform替代 d 属性频繁更新 - 拖拽用 Pointer Events 统一鼠标/触摸
- 撤销栈不要存全量快照,存 diff/命令
- 坐标系要区分"屏幕坐标" vs "世界坐标",导出时用世界坐标
⑧ 小程序开发 — 原生 & UniApp 跨端
小程序是介于 H5 与原生之间的产物:用类 Web 技术栈开发,运行在宿主 App 提供的 受限 WebView + 原生渲染容器中,享受「免安装、秒开、可调用原生能力」的体验。
8.1 原生微信小程序 — 双线程架构
小程序最大特色是逻辑层(AppService)与渲染层(WebView)物理隔离,两者通过
Native 桥通信。这样 JS 无法直接操作 DOM,既保证了安全沙箱,也为多端渲染(Skyline)打基础。
┌────────────────┐ postMessage ┌────────────────┐
│ 渲染层 View │ <───────────────────> │ 逻辑层 Service │
│ (WebView) │ JSBridge │ (JSCore/V8) │
│ WXML / WXSS │ │ app.js / 业务 │
└────────┬───────┘ └────────┬───────┘
│ │
└─────────── Native 层 ──────────────────┘
(网络 / 文件 / 相机 / 蓝牙 / 支付…)
- 逻辑层:执行业务 JS,调用
wx.*API,通过setData把数据传给渲染层 - 渲染层:每个页面一个独立 WebView,解析 WXML → 虚拟 DOM → 真实 DOM
- 通信代价:
setData是跨线程 JSON 序列化,避免频繁大对象 - Skyline:新一代渲染引擎,合并双线程,提升性能
8.2 原生小程序四件套
app.json(全局配置)
{
"pages": ["pages/index/index", "pages/my/my"],
"window": {
"navigationBarTitleText": "我的小程序",
"navigationBarBackgroundColor": "#6c8cff"
},
"tabBar": {
"list": [
{ "pagePath": "pages/index/index", "text": "首页" },
{ "pagePath": "pages/my/my", "text": "我的" }
]
}
}
index.wxml(模板)
<view class="wrap">
<text>{{ title }}</text>
<button bindtap="onTap">点击 +1</button>
<view wx:for="{{ list }}" wx:key="id">
{{ item.name }}
</view>
</view>
index.wxss(样式,rpx 自适配)
.wrap {
padding: 32rpx;
background: #f7f8fc;
}
/* 750rpx = 屏幕宽,自动按比例换算 */
button { margin-top: 24rpx; }
index.js(页面逻辑)
Page({
data: { title: '你好小程序', count: 0, list: [] },
onLoad() {
wx.request({
url: 'https://api.example.com/list',
success: (res) => this.setData({ list: res.data }),
});
},
onTap() {
this.setData({ count: this.data.count + 1 });
},
});
8.3 生命周期与路由
App 生命周期
onLaunch:启动(只一次,可做登录)onShow:进入前台onHide:切后台onError:全局错误
Page 生命周期
onLoad(query):首次加载,收参数onShow/onHideonReady:首次渲染完成onPullDownRefresh/onReachBottom
路由 API
wx.navigateTo:入栈(可返回)wx.redirectTo:替换当前wx.switchTab:切 tabBar 页wx.reLaunch:清栈重启
组件化
Component({ properties, data, methods })- 支持
behaviors(类似 mixin) - 自定义组件可用
observers监听数据变化
8.4 UniApp — 一套代码跨 10+ 端
基于 Vue.js,编译产物可跑在:微信 / 支付宝 / 抖音 / QQ / 百度 / 快手 / 钉钉 小程序,H5, iOS App,Android App(App 端基于 WeeX / uni-app X)。
项目结构
my-uni-app/
├── pages/
│ └── index/index.vue # 页面(Vue 单文件)
├── components/ # 公共组件
├── static/ # 静态资源
├── App.vue # 入口
├── main.js # 挂载
├── manifest.json # 各端配置(appid 等)
├── pages.json # 路由 + TabBar
└── uni.scss # 全局样式变量
Vue 3 + Composition API 写法
<template>
<view class="page">
<text>{{ title }}</text>
<button @click="add">点击 {{ count }}</button>
<!-- #ifdef MP-WEIXIN -->
<button open-type="getUserInfo">微信授权</button>
<!-- #endif -->
</view>
</template>
<script setup>
import { ref } from 'vue';
const title = ref('UniApp Demo');
const count = ref(0);
const add = () => {
count.value++;
uni.showToast({ title: '+1', icon: 'success' });
};
</script>
<style lang="scss">
.page { padding: 32rpx; }
</style>
条件编译
UniApp 在同一源码里用注释区分不同平台逻辑,编译时只保留对应端代码:
// #ifdef H5
window.location.href = '/login';
// #endif
// #ifdef MP-WEIXIN
wx.login({ success: (r) => console.log(r.code) });
// #endif
// #ifdef APP-PLUS
plus.runtime.openURL('tel:10086');
// #endif
8.5 常用 API 对比(原生 wx / UniApp uni)
| 能力 | 原生小程序 | UniApp | 说明 |
|---|---|---|---|
| 网络请求 | wx.request | uni.request | Promise 化需自封装 |
| 本地存储 | wx.setStorageSync | uni.setStorageSync | 单 key 上限 1MB |
| 路由跳转 | wx.navigateTo | uni.navigateTo | 页面栈最多 10 层 |
| Toast | wx.showToast | uni.showToast | 支持 icon/image |
| 登录 | wx.login | uni.login({ provider }) | UniApp 统一多端鉴权 |
| 支付 | wx.requestPayment | uni.requestPayment | 需后端签名 |
| 文件上传 | wx.uploadFile | uni.uploadFile | 支持 multipart |
| 扫码 | wx.scanCode | uni.scanCode | 需相机权限 |
8.6 实战演示:网页版小程序模拟器
下方在浏览器里模拟了一个完整的小程序环境(手机外壳 + 顶部胶囊 + TabBar + 多页面 + 下拉刷新 +
常用 wx.* API),真实复刻「双线程通信 → setData 驱动视图」的行为。
✅ 已实现:页面切换(switchTab)、setData 响应式更新、showToast、
showModal、request(模拟)、setStorageSync、下拉刷新、生命周期日志。
8.7 踩坑 & 技术选型
性能优化
- 避免频繁
setData,合并批量更新 - 只传变化字段:
setData({ 'list[0].name': 'x' }) - 长列表用虚拟列表(recycle-view)
- 图片用 CDN + WebP
- 分包加载 / 独立分包 / 分包预下载
如何选型
- 只做微信:原生性能最佳、更新最快
- 多端(抖音/支付宝/H5/App):UniApp / Taro
- 团队熟悉 React:Taro 4(React + 编译)
- 团队熟悉 Vue:UniApp
- 极致性能 + 新能力:uni-app x(原生渲染)
调试工具
- 微信开发者工具(必装)
- vConsole:真机调试控制台
- 抓包:Charles / Whistle + 证书
- HBuilderX:UniApp 官方 IDE
发布流程
- 配置合法域名(request / socket / upload)
- 体验版 → 审核版 → 正式版
- 版本回滚、灰度发布
- 数据助手监控:崩溃率 / 启动耗时
⑨ Web 游戏开发 — 浏览器里的 3A 之梦
现代浏览器已经具备完整的游戏开发能力:GPU 渲染、接近原生的性能、低延迟网络、手柄/震动/音频全配套。 从《Among Us Web》到 Unity 导出的 3D 游戏,Web 正成为真正的游戏平台。
9.1 核心渲染技术
| 技术 | 定位 | 性能上限 | 适用场景 |
|---|---|---|---|
| Canvas 2D | 最基础 2D 绘图 | 数千精灵 | 休闲小游戏、棋牌、2D 动画 |
| WebGL | GPU 加速渲染(OpenGL ES) | 数万物体 | 所有 60fps 的 3D Web 游戏,Three.js/Babylon.js 底层 |
| WebGPU | 下一代 GPU 接口(Vulkan/Metal 风格) | 十万+物体 | 3A Web 游戏、光追、粒子爆炸、GPGPU 计算 |
| WebAssembly | 高性能引擎代码 | 接近原生 | Unity / Unreal 导出游戏的核心逻辑 |
9.2 主流游戏引擎与框架
| 引擎 | 类型 | 特点 | 适合游戏 |
|---|---|---|---|
| Phaser | 2D 全能引擎 | 开源 + 完整游戏循环 / WebGL+Canvas 自动切换 | 平台跳跃、RPG、俯视角射击、卡牌 |
| PixiJS | 2D 渲染库 | 极致性能 / 无游戏循环 | 弹幕、贪吃蛇大作战、UI 密集 |
| Three.js | 3D 渲染库 | 最流行的 WebGL 抽象 | 3D 展示、第一/三人称探索、低模游戏 |
| Babylon.js | 3D 引擎 | 微软主导 / 内置物理+GUI+粒子 / 早期支持 WebGPU | 高品质 3D、大型场景 |
| Unity(WebGL 导出) | 传统大型引擎 | 导出 HTML+JS+WASM,生态最大 | 中大型 2D/3D 商业游戏 |
| Unreal(Pixel Streaming) | 3A 引擎 | 画质顶级,导出体积大 | 高端演示、云游戏 |
| Cocos Creator | 国产 H5 引擎 | 微信小游戏首选 / 双端打包 | 微信小游戏、4399 H5、捕鱼棋牌 |
9.3 实时网络同步方案
🔌 传输协议
- WebSocket:回合制、休闲、棋牌。简单可靠
- WebRTC DataChannel:P2P 低延迟,1v1 格斗/双人协作
- WebTransport:基于 HTTP/3,不可靠数据报 + 可靠流,FPS/MOBA 最佳
- WebSocket over QUIC:兼顾兼容性与低延迟
🎯 同步策略
- 权威服务器:服务端持最终裁决,反作弊刚需
- 帧同步(Lockstep):只传指令,所有端独立计算;带宽省但需确定性引擎(MOBA/格斗)
- 状态同步:服务端算完发状态;通用但带宽大(FPS/MMO)
- 客户端预测 + 服务端校正:位置提前渲染,错了再回滚
- 插值 / 外推:平滑其他玩家画面
9.4 游戏辅助 API
输入
- Gamepad API:Xbox / PS / Switch Pro 手柄
- Pointer Lock API:隐藏鼠标,FPS 必备
- Keyboard Lock API:在全屏时锁定 Tab/Esc
- Touch / Pointer Events:移动端虚拟摇杆
输出
- Web Audio API:3D 空间音效、节奏精确
- Fullscreen API:沉浸式全屏
- Vibration API:移动端触觉反馈
- Screen Orientation:锁定横竖屏
- Wake Lock:防止屏幕休眠
性能
- OffscreenCanvas:渲染移至 Worker 不阻塞 UI
- SharedArrayBuffer:Worker 间零拷贝共享内存
- requestAnimationFrame:VSync 对齐的帧循环
- performance.now():高精度时间戳
分发
- PWA:可安装到桌面
- Cloud Gaming:Pixel Streaming / GeForce NOW
- WebXR:VR/AR 游戏
- 微信小游戏:基于 Web 技术的超级入口
9.5 实战 A:Canvas 2D 小游戏(打砖块)
经典 Breakout,演示游戏循环、碰撞检测、输入处理三件套。 控制:← → 或鼠标/触屏拖动挡板。
9.6 实战 B:Three.js 3D 场景(WebGL)
从 CDN 加载 Three.js,构建:带光照/阴影的地面 + 自转立方体 + 可旋转摄像机。 拖拽旋转、滚轮缩放。这是所有 3D Web 游戏的最小可运行模板。
import * as THREE from 'three';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, w / h, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
const geo = new THREE.BoxGeometry(1, 1, 1);
const mat = new THREE.MeshStandardMaterial({ color: 0x6c8cff });
const cube = new THREE.Mesh(geo, mat);
scene.add(cube);
scene.add(new THREE.DirectionalLight(0xffffff, 1));
camera.position.z = 4;
renderer.setAnimationLoop(() => {
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
});
9.7 实战 C:Web Audio 3D 空间音效
用 PannerNode 实现空间化音频:拖动画布上的小球(音源),
中心是听众,离得近声音大、在哪侧从哪侧出来,就像真实 3D 游戏里脚步声定位一样。请戴耳机体验。
9.8 实战 D:手柄 / 全屏 / 震动 / 屏幕方向
🎮 Gamepad API 实时面板
插入 Xbox/PS 手柄并按任意键即可自动识别;没有手柄则显示未连接。
🌐 其他游戏 API
点击即真实调用浏览器原生能力。
点击上方按钮试试 →
9.10 实战 E:FPS 射击游戏(5 把武器 + 手雷抛物 + 范围伤害)
一款完整的第一人称射击游戏:5 把武器(手枪/步枪/狙击/霰弹/火箭筒)+ 手雷抛物 + 范围爆炸 + Raycaster 命中判定 + 部位伤害 + 血量/护甲 + AI 敌人 + 复活机制。 每把武器伤害/射速/弹道差异化,所有玩法均由纯 JS 实现。
武器系统(5+1)
| 武器 | 伤害 | 射速 | 弹匣 | 散布 | 射程 | 特点 |
|---|---|---|---|---|---|---|
| 🔫 手枪 Glock | 22 | 200ms | 15 | 0.8° | 40m | 精准、备弹无限 |
| 🎯 步枪 AK-47 | 32 | 100ms | 30 | 1.5° | 80m | 全能、连发 |
| 🔭 狙击 AWP | 120 | 1500ms | 5 | 0° | 200m | 一枪致命,慢 |
| 💥 霰弹 SPAS-12 | 14×8 | 800ms | 8 | 10° | 20m | 近战之王,散射 8 颗 |
| 🚀 火箭筒 RPG | 120 + 范围 80 | 1500ms | 1 | 0° | — | 抛物线弹道 + 4m 爆炸 |
| 💣 手雷 | 范围 100 | — | 3 颗 | — | — | 抛物投掷 + 5m 爆炸 |
核心系统设计
🔫 武器切换
- 1-5 切换 5 把武器,G 投手雷
- 每把独立配置:damage / fireRate / magSize / spread / range / projectileType
- 霰弹一次射 8 条 raycast 模拟散射
- 狙击 spread=0 + 一发致命,体现"精准武器"差异
💣 投掷物 + 抛物线
- 手雷 = THREE.Mesh + 速度向量 v
- 每帧
v.y -= g·dt(重力 18m/s²) - 触地或 1.5s 后 爆炸:5m 范围扣血
- 火箭弹 = 直线匀速 + 命中即炸(4m)
- 距离衰减:
dmg × (1 - dist/radius)
🎯 命中判定
- 子弹:
Raycaster取最近障碍/敌人 - 霰弹:8 条 ray 各自结算,点状散布
- 头/胸/腿三段倍率(2× / 1× / 0.75×)
- 距离衰减:
1 - dist/range
🛡 掩体战术
- 开局玩家在南掩体后
- 3 个 AI 分别藏在北/东/西掩体
- AI 视野含遮挡判定,藏好真的安全
- 火箭/手雷可越过掩体清场
// 手雷抛物线
function throwGrenade() {
const dir = camera.getWorldDirection(new THREE.Vector3());
const grenade = {
mesh: greenSphere,
pos: camera.position.clone(),
vel: dir.multiplyScalar(18).setY(8), // 初速度 + 抬头
bornT: now,
};
grenades.push(grenade);
}
// 每帧物理更新
function updateGrenades(dt) {
for (const g of grenades) {
g.vel.y -= 18 * dt; // 重力
g.pos.addScaledVector(g.vel, dt);
if (g.pos.y <= 0.2 || age > 1500) {
explode(g.pos, 100, 5); // damage 100, radius 5m
remove(g);
}
}
}
// 范围伤害
function explode(center, dmg, radius) {
for (const e of enemies) {
const dist = e.position.distanceTo(center);
if (dist < radius) hitEnemy(e, dmg * (1 - dist/radius));
}
// 玩家也会被自己手雷炸到!
const myDist = camera.position.distanceTo(center);
if (myDist < radius) takeDamage(dmg * (1 - myDist/radius));
}
9.11 实战 F:真实 Node.js WebSocket 房间服务端
上一节用 MockWs 在浏览器里模拟服务端,下面给出可直接运行的 Node.js 版本。
支持多人房间、广播、心跳、断线清理。已附在项目根目录 server/room-server.js,一行命令启动:
npm install ws
node server/room-server.js
# 监听 ws://localhost:3001/room/{roomId}
server/room-server.js 完整实现
// Node.js 18+ | pnpm add ws
import { WebSocketServer } from 'ws';
import { randomUUID } from 'node:crypto';
const wss = new WebSocketServer({ port: 3001, path: '/room' });
/** @type {Map<string, Set<WebSocket>>} */
const rooms = new Map();
/** @type {Map<WebSocket, {uid: string, roomId: string, state: any}>} */
const clients = new Map();
const join = (ws, roomId) => {
if (!rooms.has(roomId)) rooms.set(roomId, new Set());
rooms.get(roomId).add(ws);
};
const leave = (ws) => {
const info = clients.get(ws);
if (!info) return;
rooms.get(info.roomId)?.delete(ws);
clients.delete(ws);
broadcast(info.roomId, { type: 'peer-leave', uid: info.uid });
};
const broadcast = (roomId, msg, exclude) => {
const data = JSON.stringify(msg);
rooms.get(roomId)?.forEach((c) => {
if (c !== exclude && c.readyState === 1) c.send(data);
});
};
wss.on('connection', (ws, req) => {
const roomId = new URL(req.url, 'http://x').pathname.split('/').pop() || 'lobby';
const uid = randomUUID().slice(0, 8);
clients.set(ws, { uid, roomId, state: {} });
join(ws, roomId);
ws.send(JSON.stringify({ type: 'welcome', uid, roomId }));
broadcast(roomId, { type: 'peer-join', uid }, ws);
// 心跳
ws.isAlive = true;
ws.on('pong', () => (ws.isAlive = true));
ws.on('message', (buf) => {
try {
const msg = JSON.parse(buf.toString());
const info = clients.get(ws);
if (msg.type === 'move') {
info.state = { x: msg.x, y: msg.y, z: msg.z, rot: msg.rot };
broadcast(roomId, { type: 'state', uid: info.uid, ...info.state }, ws);
} else if (msg.type === 'chat') {
broadcast(roomId, { type: 'chat', uid: info.uid, text: msg.text });
}
} catch (e) {
console.error(e);
}
});
ws.on('close', () => leave(ws));
});
// 30s 心跳
setInterval(() => {
wss.clients.forEach((ws) => {
if (!ws.isAlive) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30_000);
console.log('🚀 Room server running on ws://localhost:3001/room/{roomId}');
客户端接入(替换 MockWs)
const ws = new WebSocket('ws://localhost:3001/room/playground');
ws.addEventListener('open', () => console.log('connected'));
ws.addEventListener('message', (e) => {
const msg = JSON.parse(e.data);
switch (msg.type) {
case 'welcome': myUid = msg.uid; break;
case 'peer-join': chat(`${msg.uid} 加入`, 'join'); break;
case 'peer-leave': removeAvatar(msg.uid); break;
case 'state': setTarget(msg.uid, msg.x, msg.y, msg.z, msg.rot); break;
case 'chat': chat(`${msg.uid}: ${msg.text}`); break;
}
});
// 上报自己位置
setInterval(() => {
if (ws.readyState !== 1) return;
ws.send(JSON.stringify({
type: 'move',
x: camera.position.x, y: camera.position.y, z: camera.position.z, rot: yaw,
}));
}, 100);
💡 没装 Node.js 时点击会报错(正常),装好后 node server/room-server.js 再点。
房间 URL:ws://localhost:3001/room/playground
9.12 实战 G:WebSocket vs WebRTC vs WebTransport 对比
不同游戏类型对网络要求差异极大。下面是同一个"位置广播"协议在三种传输上的实现对比, 并模拟延迟、丢包、队头阻塞三种场景观察差异。
能力与差异
| 能力 | WebSocket | WebRTC DataChannel | WebTransport |
|---|---|---|---|
| 底层 | TCP | UDP + SCTP | UDP + QUIC (HTTP/3) |
| 可靠传输 | ✅ 强制 | ✅ 可选 | ✅ Stream 模式 |
| 不可靠传输 | ❌ | ✅ unordered+noRetrans | ✅ Datagram 模式 |
| 队头阻塞 | ❌ 有(TCP) | ✅ 无 | ✅ 无(多路复用) |
| 延迟 | 中(~50-200ms) | 低(P2P ~20ms) | 低(~20-50ms) |
| 连接建立 | HTTP Upgrade,1 RTT | 信令+ICE,数秒 | HTTP/3,0-1 RTT |
| 浏览器支持 | ✅ 全 | ✅ 全 | ⚠️ Chrome 97+ |
| 典型场景 | 聊天、回合制 | 1v1 P2P、WebRTC 会议 | FPS / MOBA / 大型 MMO |
三种协议的客户端代码对比
① WebSocket(最简单)
const ws = new WebSocket('wss://srv/room');
ws.onmessage = (e) => handle(JSON.parse(e.data));
ws.send(JSON.stringify(msg));
// 所有包按发送顺序到达
// 丢一个会卡住后面所有包
② WebRTC DataChannel(P2P)
const pc = new RTCPeerConnection({ iceServers });
// "游戏模式":不可靠、无序(类 UDP)
const dc = pc.createDataChannel('game', {
ordered: false,
maxRetransmits: 0,
});
dc.onmessage = (e) => handle(e.data);
dc.send(JSON.stringify(msg));
③ WebTransport Stream(可靠)
const wt = new WebTransport('https://srv/wt');
await wt.ready;
const stream = await wt.createBidirectionalStream();
const writer = stream.writable.getWriter();
writer.write(new TextEncoder().encode(JSON.stringify(msg)));
// 多条 stream 互不阻塞
③' WebTransport Datagram(不可靠)
const wt = new WebTransport('https://srv/wt');
await wt.ready;
const w = wt.datagrams.writable.getWriter();
w.write(new TextEncoder().encode(JSON.stringify(msg)));
// 类 UDP,最低延迟
// 不保证到达、不保证顺序
// 适合每帧位置更新
模拟三种传输的延迟与丢包
下方模拟每秒发 20 个"位置包",可调节延迟抖动与丢包率,观察三种协议下"感知延迟"差异:
📦 WebSocket (TCP 有序)
🎮 WebRTC DataChannel (UDP 不可靠)
🚀 WebTransport Datagram
🎯 结论:在 5% 丢包率下,WebSocket 会出现"一卡一卡"(前面包丢需重传,后续包排队); WebRTC / WebTransport 不可靠模式只丢那一帧,后续完全不受影响。 FPS/MOBA 之所以强烈倾向 UDP,就是因为宁愿丢一帧,也不要卡一秒。
9.9 技术选型速查
按品类选
- 棋牌 / 卡牌 / 休闲:Phaser + WebSocket
- 弹幕 / 贪吃蛇大作战:PixiJS + Web Audio + WebRTC
- 3D 探索 / 独立:Three.js 或 Babylon.js
- MOBA / FPS:Unity 或 Unreal 导出 + WebTransport + WebGPU
- 微信小游戏:Cocos Creator
性能优化
- 纹理打包 TextureAtlas 减少 Draw Call
- 对象池(Pool)避免 GC 抖动
- 脏矩形:只重绘变化区域
- OffscreenCanvas + Worker 分离逻辑与渲染
- 静态资源 HTTP/2 多路复用 / Brotli 压缩