手账便签撕纸声音与物理触感采集流:Web Audio 声音合成

在追求极致手作质感的前端交互中,当用户左滑删除一条待办事项时,伴随的撕纸声如果直接去加载一个 200KB 的 .mp3 音频文件,会带来两个无法忽视的工程硬伤:
在现代 Web 平台中,利用浏览器原生 Web Audio API 进行“纯代码声学物理合成(Procedural Audio Synthesis)” 是游戏与专业级界面的终极解法。
通过白噪声音频节点(White Noise Node)配合带通滤波器(Bandpass Filter)与指数衰减增益(Exponential Decay Gain),我们只需 0 KB 外部音频文件,就能在 0 毫秒延迟 下实时合成极其逼真、带有纤维撕裂感的“沙沙撕纸声”。
+——————————————————————–+
| Web Audio API 纯代码物理撕纸声学合成图 |
+——————————————————————–+
| [白噪声发生器 (AudioBuffer / Math.random() 模拟纸张纤维摩擦)] |
| | |
| v (串联 BiquadFilter 带通滤波: 频率 1800Hz ~ 3200Hz) |
| [带通滤波器 (过滤低频轰鸣,只保留高频清脆沙沙声)] |
| | |
| v (串联 GainNode 增益包络控制器) |
| [指数增益衰减器: 0ms (音量 0.3) -> 120ms (指数衰减至 0.001)] |
| | |
| [物理扬声器输出: 瞬间爆发清脆、逼真、0 延迟的手作撕纸沙沙声] |
+——————————————————————–+
1. 纯 TypeScript 撕纸声学合成器实现
创建 src/lib/paperAudio.ts:
class ProceduralAudioEngine {
private ctx: AudioContext | null = null;
private getContext(): AudioContext {
if (!this.ctx) {
const AudioCtx = window.AudioContext || (window as any).webkitAudioContext;
this.ctx = new AudioCtx();
}
if (this.ctx.state === "suspended") {
this.ctx.resume();
}
return this.ctx;
}
// 合成纯代码撕纸声 (Paper Tear Sound)
public playPaperTear() {
const ctx = this.getContext();
const duration = 0.14; // 140 毫秒清脆撕纸
const bufferSize = ctx.sampleRate * duration;
const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
const data = buffer.getChannelData(0);
// 1. 生成白噪声 (模拟纸张纤维断裂随机声)
for (let i = 0; i < bufferSize; i++) {
data[i] = Math.random() * 2 – 1;
}
const noise = ctx.createBufferSource();
noise.buffer = buffer;
// 2. 带通滤波器:只保留 2200Hz 附近的纸张摩擦特征频段
const filter = ctx.createBiquadFilter();
filter.type = "bandpass";
filter.frequency.setValueAtTime(2400, ctx.currentTime);
filter.Q.setValueAtTime(3.0, ctx.currentTime);
// 3. 指数衰减增益包络
const gain = ctx.createGain();
gain.gain.setValueAtTime(0.25, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration);
// 连接音频处理链路
noise.connect(filter);
filter.connect(gain);
gain.connect(ctx.destination);
noise.start();
noise.stop(ctx.currentTime + duration);
}
}
export const soundEngine = new ProceduralAudioEngine();
2. 在 React 滑动删除中的 0 延迟触发
当用户左滑待办超过阈值触发删除时:
const handleDelete = () => {
// 1. 纯代码 0 延迟播放撕纸声
soundEngine.playPaperTear();
// 2. 触发手机 12ms 触感微震
triggerHaptic("tear");
// 3. 执行业务删除
todoDb.todos.delete(id);
};
极简工程的美感
不需要下载一个字节的音频文件,用纯粹的数学波形和物理滤波在指尖重现真实世界的声学奇迹。
这就是极客浪漫与手作美学的完美交融。



