fix: 进度条平滑过渡 — force reflow 技巧确保 CSS transition 生效

- 临时关 transition → 设旧值 → offsetWidth reflow → 开 transition → 设新值
- DOM 复用: 首次 innerHTML 创建, 后续只改属性
- 进度条从旧值滑到新值, 不再闪现

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
qinglong
2026-06-13 18:19:07 +08:00
parent 417213096f
commit 3ca001b938
4 changed files with 59 additions and 23 deletions
+35 -21
View File
@@ -164,27 +164,41 @@
var oldMem = parseFloat((body.getAttribute('data-mem')||memVal));
body.setAttribute('data-cpu', cpuVal);
body.setAttribute('data-mem', memVal);
body.innerHTML =
'<div class="metric-row">'+
'<div class="metric"><div class="metric-label">CPU</div><div class="metric-value cpu-val">'+oldCpu.toFixed(1)+'%</div><div class="metric-bar"><div class="metric-bar-fill '+cpuBarCls+'" style="width:'+oldCpu+'%"></div></div></div>'+
'<div class="metric"><div class="metric-label">MEM</div><div class="metric-value mem-val">'+oldMem.toFixed(1)+'%</div><div class="metric-bar"><div class="metric-bar-fill '+memBarCls+'" style="width:'+oldMem+'%"></div></div></div>'+
'</div>'+
'<div class="metric-row">'+
'<div class="metric"><div class="metric-label">NET ↓</div><div class="metric-value" style="font-size:.9rem">'+fmtSpeed(net.rx_bytes_sec||0)+'</div></div>'+
'<div class="metric"><div class="metric-label">NET ↑</div><div class="metric-value" style="font-size:.9rem">'+fmtSpeed(net.tx_bytes_sec||0)+'</div></div>'+
'</div>'+
'<div style="font-size:.7rem;color:var(--text-dim);margin-top:8px">内存 '+(mem.used_gb||0).toFixed(1)+'/'+(mem.total_gb||0).toFixed(1)+' GB</div>';
// 动画计数
var cpuEl = body.querySelector('.cpu-val');
var memEl = body.querySelector('.mem-val');
animateValue(cpuEl, oldCpu, cpuVal, 1, '%');
animateValue(memEl, oldMem, memVal, 1, '%');
// 进度条平滑过渡 (CSS transition 自动处理)
requestAnimationFrame(function(){
var fills = body.querySelectorAll('.metric-bar-fill');
if (fills[0]) { fills[0].style.width = cpuVal+'%'; fills[0].className = 'metric-bar-fill '+cpuBarCls; }
if (fills[1]) { fills[1].style.width = memVal+'%'; fills[1].className = 'metric-bar-fill '+memBarCls; }
});
var isNew = !body.querySelector('.cpu-val');
if (isNew) {
body.innerHTML =
'<div class="metric-row">'+
'<div class="metric"><div class="metric-label">CPU</div><div class="metric-value cpu-val">0%</div><div class="metric-bar"><div class="metric-bar-fill" style="width:0%"></div></div></div>'+
'<div class="metric"><div class="metric-label">MEM</div><div class="metric-value mem-val">0%</div><div class="metric-bar"><div class="metric-bar-fill" style="width:0%"></div></div></div>'+
'</div>'+
'<div class="metric-row">'+
'<div class="metric"><div class="metric-label">NET ↓</div><div class="metric-value net-down" style="font-size:.9rem"></div></div>'+
'<div class="metric"><div class="metric-label">NET ↑</div><div class="metric-value net-up" style="font-size:.9rem"></div></div>'+
'</div>'+
'<div class="mem-detail"></div>';
}
// 更新 NET
body.querySelector('.net-down').textContent = fmtSpeed(net.rx_bytes_sec||0);
body.querySelector('.net-up').textContent = fmtSpeed(net.tx_bytes_sec||0);
body.querySelector('.mem-detail').innerHTML = '<div style="font-size:.7rem;color:var(--text-dim);margin-top:8px">内存 '+(mem.used_gb||0).toFixed(1)+'/'+(mem.total_gb||0).toFixed(1)+' GB</div>';
// 进度条: 先设旧值 → force reflow → 设新值
var barCpu = body.querySelectorAll('.metric-bar-fill')[0];
var barMem = body.querySelectorAll('.metric-bar-fill')[1];
barCpu.style.transition = 'none';
barMem.style.transition = 'none';
barCpu.style.width = oldCpu+'%';
barMem.style.width = oldMem+'%';
barCpu.className = 'metric-bar-fill '+cpuBarCls;
barMem.className = 'metric-bar-fill '+memBarCls;
barCpu.offsetWidth; // force reflow
barMem.offsetWidth;
barCpu.style.transition = '';
barMem.style.transition = '';
barCpu.style.width = cpuVal+'%';
barMem.style.width = memVal+'%';
// 数字跳动
animateValue(body.querySelector('.cpu-val'), oldCpu, cpuVal, 1, '%');
animateValue(body.querySelector('.mem-val'), oldMem, memVal, 1, '%');
} else {
body.innerHTML = '<div style="color:var(--error);font-size:.85rem">'+(n.error||'离线')+'</div>';
}