动画与过渡
HMI 动画全部基于 CSS transition / animation / keyframes, 不需要 JS 动画库。配合 darra.bind 回调切换 class / style, 即可实现工业设备的开/关/旋转/流动/闪烁效果。
三种动画技术
| 技术 | 适合 | 性能 |
|---|---|---|
transition | 属性渐变 (width/color/transform) | 极高 |
animation + @keyframes | 循环/复杂序列 (旋转/闪烁/流动) | 高 |
| JS 动画 (requestAnimationFrame) | 极端自定义 (轨迹曲线) | 中, 占 CPU |
90% 场景优先用 CSS, CSS 动画由 GPU 处理, 流畅且省电。
1. transition (状态切换)
.valve {
transition: background 0.3s ease, transform 0.3s ease;
}
.valve.open { background: green; transform: rotate(0deg); }
.valve.closed { background: red; transform: rotate(90deg); }
<div class="valve" :class="{ open: isOpen, closed: !isOpen }"
x-data="{ isOpen: false }"
x-init="darra.bind('M11.0', v => isOpen = v)">
</div>
常用过渡属性:
| 属性 | 说明 |
|---|---|
transition: all 0.3s ease | 一次性声明 |
transition: color .2s, background .5s ease-out | 多属性不同时长 |
transition-delay: .1s | 延迟 |
缓动函数: linear / ease / ease-in / ease-out / ease-in-out / cubic-bezier(.2,.8,.2,1)。
2. keyframes (循环动画)
阀门旋转开闭
@keyframes valve-open {
0% { transform: rotate(90deg); background: red; }
50% { transform: rotate(45deg); background: orange; }
100% { transform: rotate(0deg); background: green; }
}
.valve.opening {
animation: valve-open 1s ease-out forwards;
}
电机旋转
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.motor.running {
animation: spin 1s linear infinite;
}
.motor.running.slow { animation-duration: 3s; }
.motor.running.fast { animation-duration: 0.3s; }
<div class="motor" :class="{
'running': speed > 0,
'slow': speed > 0 && speed < 500,
'fast': speed >= 2000
}"
x-data="{ speed: 0 }"
x-init="darra.bind('DB3.MotorSpeed', v => speed = v)">
<img src="/static/img/motor.svg" />
</div>
液位上升 (tank)
.tank {
position: relative;
width: 120px; height: 200px;
border: 3px solid #333;
overflow: hidden;
}
.tank .liquid {
position: absolute;
left: 0; right: 0; bottom: 0;
background: linear-gradient(180deg, #4FC3F7, #1E88E5);
height: 0;
transition: height 0.5s ease;
}
.tank .liquid::before {
content: '';
position: absolute;
left: 0; right: 0; top: -10px;
height: 10px;
background: inherit;
border-radius: 50%;
animation: wave 2s ease-in-out infinite;
}
@keyframes wave {
0%,100% { transform: translateX(0) scaleY(1); }
50% { transform: translateX(-5px) scaleY(0.8); }
}
<div class="tank"
x-data="{ level: 0 }"
x-init="darra.bind('DB1.Level', v => level = v)">
<div class="liquid" :style="`height: ${level}%`"></div>
</div>
管道流体动画
.pipe {
height: 20px;
background: linear-gradient(90deg, #666, #aaa, #666);
}
.pipe.flowing {
background-image: repeating-linear-gradient(
-45deg,
#4FC3F7 0px, #4FC3F7 10px,
#81D4FA 10px, #81D4FA 20px
);
background-size: 28.28px 28.28px;
animation: flow 1s linear infinite;
}
@keyframes flow {
from { background-position: 0 0; }
to { background-position: 28.28px 0; }
}
.pipe.flowing.reverse { animation-direction: reverse; }
.pipe.flowing.slow { animation-duration: 3s; }
<div class="pipe" :class="{
'flowing': flow > 0,
'reverse': flow < 0,
'slow': Math.abs(flow) < 5
}"
x-data="{ flow: 0 }"
x-init="darra.bind('DB1.Flow', v => flow = v)">
</div>
报警闪烁
@keyframes alarm-blink {
0%, 49% { background: var(--darra-danger); }
50%,100% { background: #7f1d1d; }
}
.alarm-active {
animation: alarm-blink 0.5s step-end infinite;
color: #fff;
}
/* 伴随抖动 */
@keyframes alarm-shake {
0%,100% { transform: translateX(0); }
25% { transform: translateX(-3px); }
75% { transform: translateX(3px); }
}
.alarm-active.critical {
animation: alarm-blink 0.3s step-end infinite, alarm-shake .2s infinite;
}
LED 呼吸灯
@keyframes pulse {
0%,100% { opacity: 1; box-shadow: 0 0 8px currentColor; }
50% { opacity: .5; box-shadow: 0 0 20px currentColor; }
}
.led.on { animation: pulse 2s ease-in-out infinite; }
3. SVG 动画 (机器人/管线图)
<svg viewBox="0 0 200 100">
<!-- 机械臂示意 -->
<g :transform="`rotate(${j1Angle} 50 50)`">
<line x1="50" y1="50" x2="100" y2="50" stroke="#666" stroke-width="6" />
<circle cx="50" cy="50" r="8" fill="#333" />
</g>
</svg>
<script>
const el = document.querySelector('svg g')
let j1Angle = 0
darra.bind('DB50.Axis0.Pos', (v) => {
j1Angle = v
el.setAttribute('transform', `rotate(${v} 50 50)`)
})
</script>
4. Chart.js 动画
const chart = new Chart(ctx, {
type: 'line',
data: { /* ... */ },
options: {
animation: { duration: 400, easing: 'easeOutQuart' },
// 或关闭 (高速刷新)
// animation: false
}
})
// 强制重置动画
chart.reset()
趋势图高频更新时关闭动画:
darra.bind('DB1.T1', (v) => {
chart.data.datasets[0].data.push({ x: Date.now(), y: v })
chart.update('none') // 'none' = 跳过动画
})
5. 页面切换动画
body { animation: page-in .4s ease; }
@keyframes page-in {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
6. Alpine x-transition (显隐动画)
<div x-data="{ show: false }">
<button @click="show = !show">切换</button>
<div x-show="show"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 transform scale-90"
x-transition:enter-end="opacity-100 transform scale-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0">
内容
</div>
</div>
简化写法:
<div x-show="show" x-transition>内容</div>
7. 性能 & 最佳实践
只动 transform / opacity
这两个属性走 GPU 合成层, 不触发 layout/paint:
/* 好 */
.el { transform: translateX(100px); opacity: 0.5; }
/* 差 (触发 layout) */
.el { left: 100px; width: 200px; }
用 will-change 提示浏览器
.chart-canvas {
will-change: transform;
}
不要滥用, 只对即将动画的元素设。
降频处理
// 60 FPS 太高, 节流到 20 FPS 显示动画
let lastUpdate = 0
darra.bind('DB1.Temp', (v) => {
const now = performance.now()
if (now - lastUpdate < 50) return // 20 FPS
lastUpdate = now
tempDisplay.textContent = v.toFixed(2)
})
动画降级 (老设备)
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.001s !important;
transition-duration: 0.001s !important;
}
}
8. 陷阱
| 陷阱 | 解法 |
|---|---|
| 动画卡顿 | 只动 transform/opacity, 避免 top/left/width/height |
animation: spin 1s infinite CPU 高 | 隐藏元素动画继续? 加 visibility: hidden 或移除 class |
| 阀门在打开和关闭间切换时跳动 | 用 forwards 保留最终帧 |
| 液位变化过于"丝滑"看不清 | transition: height .5s cubic-bezier(...) 或数值显示 |
| Chart.js 动画叠加卡顿 | chart.update('none') 跳过 |
9. 工业动画参考清单
| 设备 | 动画思路 |
|---|---|
| 阀门 | 旋转 + 色变 |
| 电机 | 持续旋转, 速度由动画时长驱动 |
| 液位 | 高度渐变 + 上表面波浪 |
| 管道 | 背景条纹平移 |
| 加热丝 | 橙→红色渐变 + 轻微闪烁 |
| 传送带 | 纹理平移 |
| 报警灯 | 颜色闪烁 + 抖动 |
| 搅拌机 | 叶片旋转 |
| 指示灯 | 呼吸灯 pulse |
| 机械臂 | SVG group rotate |