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
+22
View File
@@ -262,5 +262,27 @@
"framework.command.execute" "framework.command.execute"
], ],
"timestamp": 27544.2709947 "timestamp": 27544.2709947
},
"29ffcf99": {
"plugin_name": "example_plugin",
"permissions": [
"plugin.example.read",
"plugin.example.write",
"plugin.example.execute",
"framework.event.subscribe",
"framework.command.execute"
],
"timestamp": 27821.237625479
},
"bd46f35e": {
"plugin_name": "sentinel",
"permissions": [
"plugin.sentinel.read",
"plugin.sentinel.write",
"plugin.network.access",
"framework.event.subscribe",
"framework.command.execute"
],
"timestamp": 27821.239961313
} }
} }
+1 -1
View File
@@ -101,7 +101,7 @@ commands:
permissions: permissions:
- framework.command.test - framework.command.test
source: internal source: internal
last_updated: 27544.226537616 last_updated: 27821.221132094
plugin_commands: plugin_commands:
example_plugin: example_plugin:
echo: *id001 echo: *id001
+1 -1
View File
@@ -1,5 +1,5 @@
http_port: 4200 http_port: 4200
last_updated: 27544.261331002 last_updated: 27821.238246209
plugin_routes: plugin_routes:
example_plugin: example_plugin:
- methods: - methods:
+35 -21
View File
@@ -164,27 +164,41 @@
var oldMem = parseFloat((body.getAttribute('data-mem')||memVal)); var oldMem = parseFloat((body.getAttribute('data-mem')||memVal));
body.setAttribute('data-cpu', cpuVal); body.setAttribute('data-cpu', cpuVal);
body.setAttribute('data-mem', memVal); body.setAttribute('data-mem', memVal);
body.innerHTML = var isNew = !body.querySelector('.cpu-val');
'<div class="metric-row">'+ if (isNew) {
'<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>'+ body.innerHTML =
'<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 class="metric-row">'+
'</div>'+ '<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-row">'+ '<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 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>'+
'<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 class="metric-row">'+
'</div>'+ '<div class="metric"><div class="metric-label">NET ↓</div><div class="metric-value net-down" style="font-size:.9rem"></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>'; '<div class="metric"><div class="metric-label">NET ↑</div><div class="metric-value net-up" style="font-size:.9rem"></div></div>'+
// 动画计数 '</div>'+
var cpuEl = body.querySelector('.cpu-val'); '<div class="mem-detail"></div>';
var memEl = body.querySelector('.mem-val'); }
animateValue(cpuEl, oldCpu, cpuVal, 1, '%'); // 更新 NET
animateValue(memEl, oldMem, memVal, 1, '%'); body.querySelector('.net-down').textContent = fmtSpeed(net.rx_bytes_sec||0);
// 进度条平滑过渡 (CSS transition 自动处理) body.querySelector('.net-up').textContent = fmtSpeed(net.tx_bytes_sec||0);
requestAnimationFrame(function(){ 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>';
var fills = body.querySelectorAll('.metric-bar-fill'); // 进度条: 先设旧值 → force reflow → 设新值
if (fills[0]) { fills[0].style.width = cpuVal+'%'; fills[0].className = 'metric-bar-fill '+cpuBarCls; } var barCpu = body.querySelectorAll('.metric-bar-fill')[0];
if (fills[1]) { fills[1].style.width = memVal+'%'; fills[1].className = 'metric-bar-fill '+memBarCls; } 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 { } else {
body.innerHTML = '<div style="color:var(--error);font-size:.85rem">'+(n.error||'离线')+'</div>'; body.innerHTML = '<div style="color:var(--error);font-size:.85rem">'+(n.error||'离线')+'</div>';
} }