zzzUI

带残影的血条:显示每次受击的伤害量

血条下降时,把原先的长度留作一道浅色残影,停留约半秒再收缩到新值,玩家从残影的长度就能读出这一击的伤害量。本文介绍这种伤害残影的原理,以及 React 血条动画的两种实现:使用 zzzUI 的 Gauge,或用两个 div 加上过渡延迟。

效果演示

Hull
x

点击「Take a hit」:填充条立即缩短,原来的位置上留下一道浅色残影,停顿片刻后收缩到新值。点击「Patch up」则方向相反:淡色残影先延伸到新值,填充条随后增长到该位置。数值低于四分之一时,血条变红,并呈呼吸式明暗变化。

使用 Gauge 组件

zzzUI 的 Gauge 已实现上述全部效果。它基于 Base UI 的 Meter,名称取自 GaugeLabel,读屏软件会朗读当前值及其范围。low 是变红的比例阈值;segments 把它分成若干格,适合表示护盾或充能次数。

hull.tsx
<Gauge value={hull} max={120} low={0.25}>  <GaugeLabel>Hull</GaugeLabel>  <GaugeValue /></Gauge>

伤害残影的原理

同一个槽里叠着宽度相同的两层:残影在后,填充条在前。两层的宽度变化使用同一个过渡,关键在于其中一层延迟启动。数值下降时,残影延迟,因此在新的填充条后面仍会短暂显示旧值;数值上升时,填充条延迟,残影先到达新值,填充条随后增长到该位置。

因此组件只需知道数值上一次的变化方向。把前一个值保存在 state 中,在渲染过程中比较(React 保存前次渲染信息的标准写法),这样既不需要 effect,也不会多一次绘制。

health-bar.tsx
function HealthBar({ value, max }: { value: number; max: number }) {  const share = value / max;  // which way it last moved, kept with React's "previous render" pattern  const [last, setLast] = useState(share);  const [dropped, setDropped] = useState(true);  if (share !== last) {    setLast(share);    setDropped(share < last);  }  const width = `${share * 100}%`;  return (    <div className="bar" role="meter" aria-label="Health"      aria-valuenow={value} aria-valuemin={0} aria-valuemax={max}>      <div className={dropped ? "trail" : "trail gain"}        style={{ width, transitionDelay: dropped ? "480ms" : "0s" }} />      <div className="fill"        style={{ width, transitionDelay: dropped ? "0s" : "480ms" }} />    </div>  );}
health-bar.css
.bar { position: relative; height: 12px; overflow: hidden; background: #000; }.trail, .fill {  position: absolute;  inset: 0 auto 0 0;  transition: width 420ms cubic-bezier(0.2, 0.7, 0.2, 1);}.trail { background: #f5f5f0; }         /* the hit: pale, behind the fill */.trail.gain { background: #cde31c66; }  /* a heal: a faint lead */.fill { background: #cde31c; }@media (prefers-reduced-motion: reduce) {  .trail, .fill { transition-delay: 0s !important; }}

停顿多久合适

半秒左右最合适:既足以看清这一击的伤害量,又足够短,能在下一击到来之前让血条恢复准确。zzzUI 把这个时长定义为设计 token --zero-dur-hold(480 ms),两层的移动则使用慢速时长(420 ms)。开启「减少动态效果」时,停顿降为零,数值偏低时的呼吸式明暗变化也随之取消:数值依然准确,只是不再有残影停留。

延伸阅读

HUD 代码块把数值条与快捷栏、任务目标放在一起;Loadout 代码块用它显示耐久度和负重。若数值只朝一个方向变化,请参阅 Progress。