录制进度与状态:让用户看得见的反馈
做录制功能,最怕什么?
用户点下录制按钮,然后一脸懵——到底在录没录?录了多久?什么时候能停?
我早期做的一个项目就踩过这个坑。用户反馈说「点了录制没反应」,其实后台已经在录了,但界面上啥提示都没有。用户等了一分钟,又点了一次,结果录了两份文件……
所以今天咱们重点聊聊:怎么让录制状态变得「看得见」。
录制状态的核心三要素
我个人习惯把录制状态拆成三个维度:
- 状态指示:当前在干嘛?录制中、暂停、还是空闲?
- 时间反馈:已经录了多久?
- 进度可视化:还剩多少空间?或者录到哪了?
你想想看,这三个东西配合好了,用户心里就有底了。
状态指示灯:红点闪烁的艺术
录制指示灯,说白了就是一个红点。但怎么让它看起来「专业」?
我建议用 CSS 动画实现呼吸灯效果,而不是简单的闪烁。
/* 录制指示灯 - 呼吸灯效果 */
.recording-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #ff4444;
display: inline-block;
margin-right: 8px;
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(0.8); }
100% { opacity: 1; transform: scale(1); }
}
/* 空闲状态 - 灰色 */
.indicator-idle {
background-color: #999;
animation: none;
}
/* 暂停状态 - 黄色闪烁 */
.indicator-paused {
background-color: #ffaa00;
animation: blink 1s step-end infinite;
}
@keyframes blink {
50% { opacity: 0.3; }
}
嗯,这里要注意:不要用纯红色常亮。用户会以为那是报错。
我的小技巧: 指示灯旁边一定要配文字。比如「录制中」「已暂停」「空闲」。光靠颜色,色盲用户就惨了。
录制计时器:从零开始的精确计时
计时器怎么实现?很多人直接用 setInterval 每秒加一。但这样有个问题——不准。
JavaScript 的定时器会因为事件循环阻塞而延迟。我遇到过录制 10 分钟,计时器显示 9 分 52 秒的情况。
正确的做法是:用开始时间戳来计算差值。
class RecordingTimer {
constructor() {
this.startTime = null;
this.elapsed = 0;
this.timerId = null;
this.isRunning = false;
}
start() {
this.startTime = Date.now() - this.elapsed;
this.isRunning = true;
this._tick();
}
pause() {
if (this.isRunning) {
clearTimeout(this.timerId);
this.elapsed = Date.now() - this.startTime;
this.isRunning = false;
}
}
resume() {
if (!this.isRunning) {
this.startTime = Date.now() - this.elapsed;
this.isRunning = true;
this._tick();
}
}
stop() {
clearTimeout(this.timerId);
this.startTime = null;
this.elapsed = 0;
this.isRunning = false;
}
_tick() {
const now = Date.now();
this.elapsed = now - this.startTime;
this._updateDisplay(this.elapsed);
this.timerId = setTimeout(() => this._tick(), 100); // 100ms 刷新一次
}
_updateDisplay(ms) {
const totalSeconds = Math.floor(ms / 1000);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
const centiseconds = Math.floor((ms % 1000) / 10);
// 显示格式:00:00:00.00
const display = `${this._pad(hours)}:${this._pad(minutes)}:${this._pad(seconds)}.${this._pad(centiseconds)}`;
// 更新 DOM
document.getElementById('timer-display').textContent = display;
}
_pad(num) {
return num.toString().padStart(2, '0');
}
}
关键点: 用 Date.now() 计算差值,而不是累加。这样即使页面卡顿,恢复后时间也是准确的。
录制进度条:不只是好看
进度条有两种场景:
- 基于时长限制:比如最多录 30 分钟,显示已用比例
- 基于文件大小:比如最大 500MB,显示已用空间
我建议两种都做,让用户自己选。但默认用时长限制,因为更直观。
function updateProgressBar(elapsedMs, maxDurationMs) {
const progress = Math.min(elapsedMs / maxDurationMs, 1);
const percent = (progress * 100).toFixed(1);
const bar = document.getElementById('progress-bar');
bar.style.width = `${percent}%`;
// 颜色渐变:绿 -> 黄 -> 红
if (progress < 0.6) {
bar.style.backgroundColor = '#4caf50';
} else if (progress < 0.85) {
bar.style.backgroundColor = '#ff9800';
} else {
bar.style.backgroundColor = '#f44336';
// 接近上限时闪烁警告
bar.classList.add('warning-blink');
}
// 显示百分比文字
document.getElementById('progress-text').textContent = `${percent}%`;
}
我曾经踩过的坑: 进度条到 100% 时自动停止录制,但没给用户缓冲时间。结果用户最后几秒的话被截断了。建议在 95% 时就弹提示,让用户决定是否提前结束。
完整的状态管理
把上面三个组件整合起来,需要一个统一的状态机。
const RecordingState = {
IDLE: 'idle',
RECORDING: 'recording',
PAUSED: 'paused',
STOPPING: 'stopping', // 正在保存文件
ERROR: 'error'
};
class RecordingController {
constructor() {
this.state = RecordingState.IDLE;
this.timer = new RecordingTimer();
this.mediaRecorder = null;
this.maxDuration = 30 * 60 * 1000; // 30分钟
}
startRecording() {
// ... 初始化 MediaRecorder
this.state = RecordingState.RECORDING;
this.timer.start();
this._updateUI();
}
pauseRecording() {
if (this.state === RecordingState.RECORDING) {
this.mediaRecorder.pause();
this.state = RecordingState.PAUSED;
this.timer.pause();
this._updateUI();
}
}
resumeRecording() {
if (this.state === RecordingState.PAUSED) {
this.mediaRecorder.resume();
this.state = RecordingState.RECORDING;
this.timer.resume();
this._updateUI();
}
}
_updateUI() {
// 更新指示灯
const indicator = document.getElementById('recording-indicator');
indicator.className = `recording-indicator indicator-${this.state}`;
// 更新状态文字
const statusText = {
[RecordingState.IDLE]: '就绪',
[RecordingState.RECORDING]: '录制中',
[RecordingState.PAUSED]: '已暂停',
[RecordingState.STOPPING]: '正在保存...',
[RecordingState.ERROR]: '发生错误'
};
document.getElementById('status-text').textContent = statusText[this.state];
}
}
知识体系总览
下面这张图,把本章的核心逻辑串起来了:
实际项目中的避坑指南
最后分享几个我实际项目中遇到的坑:
- 计时器在页面隐藏时不准——用
document.visibilitychange事件校正时间 - 进度条不要用 requestAnimationFrame——录制时 CPU 已经吃紧,用 setInterval 100ms 就够了
- 状态切换要有防抖——用户快速点击暂停/恢复,可能导致状态错乱
- 录制结束时先停计时器,再保存文件——顺序反了会导致时间显示多跳几秒
总结一句话: 录制反馈不是锦上添花,而是必需品。用户看不到状态,就会怀疑你的应用「坏了」。
好,这一章的内容就到这。代码可以直接拿去用,但建议你根据自己的 UI 框架调整一下样式。下一章咱们聊聊更进阶的话题——怎么在录制中实时添加水印和标注。
公众号:蓝海资料掘金营,微信deep3321