mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 06:10:42 +00:00
Animate HUD troop/population bars with transform instead of width (#4319)
## Problem The troop and population ratio bars in `ControlPanel` and `PlayerInfoOverlay` update their inline `width` on every game tick, with a `transition-[width] duration-200` to smooth the change. But `width` is a layout property — animating it forces the browser to **recalculate layout for the surrounding HUD components every animation frame**. Since the width changes every tick, this kept the whole HUD in a near-constant relayout loop and showed up as jank. ## Fix Keep the smooth animation, but drive it with `transform` (GPU-composited, no layout) instead of `width`: - Replace the two flex `width: %` segments with absolutely-positioned, full-width bars. - Segment 1: `transform: scaleX(green/100)` anchored to the left edge (`origin-left`). - Segment 2: `transform: translateX(green%) scaleX(orange/100)` so it stays flush against the first segment. - Animate with `transition-transform duration-200 ease-out`. Because `transform` is composited rather than laid out, the bars animate smoothly **without** triggering the per-frame HUD relayout. The segments are now always mounted (`scaleX(0)` when empty) instead of conditionally rendered, which also prevents the transition from resetting as values cross zero. Files: - `src/client/hud/layers/ControlPanel.ts` (mobile + desktop troop bars: malibu-blue / aquarius) - `src/client/hud/layers/PlayerInfoOverlay.ts` (sky-700 / malibu-blue) A grep confirmed these were the only `transition-[width]` usages in the client. ## Testing - `eslint --fix` / prettier ran clean via the pre-commit hook. - CSS-only change; no sim/behavioral logic touched. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -347,19 +347,16 @@ export class ControlPanel extends LitElement implements Controller {
|
||||
<div
|
||||
class="w-full h-6 border border-gray-600 rounded-md bg-gray-900/60 overflow-hidden relative"
|
||||
>
|
||||
<div class="h-full flex">
|
||||
${greenPercent > 0
|
||||
? html`<div
|
||||
class="h-full bg-malibu-blue transition-[width] duration-200"
|
||||
style="width: ${greenPercent}%;"
|
||||
></div>`
|
||||
: ""}
|
||||
${orangePercent > 0
|
||||
? html`<div
|
||||
class="h-full bg-aquarius transition-[width] duration-200"
|
||||
style="width: ${orangePercent}%;"
|
||||
></div>`
|
||||
: ""}
|
||||
<div class="relative h-full">
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 w-full origin-left bg-malibu-blue transition-transform duration-200 ease-out"
|
||||
style="transform: scaleX(${greenPercent / 100});"
|
||||
></div>
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 w-full origin-left bg-aquarius transition-transform duration-200 ease-out"
|
||||
style="transform: translateX(${greenPercent}%) scaleX(${orangePercent /
|
||||
100});"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-0 flex items-center justify-between px-1.5 text-xs font-bold leading-none pointer-events-none"
|
||||
@@ -402,19 +399,16 @@ export class ControlPanel extends LitElement implements Controller {
|
||||
<div
|
||||
class="w-full h-6 border border-gray-600 rounded-md bg-gray-900/60 overflow-hidden relative"
|
||||
>
|
||||
<div class="h-full flex">
|
||||
${greenPercent > 0
|
||||
? html`<div
|
||||
class="h-full bg-malibu-blue transition-[width] duration-200"
|
||||
style="width: ${greenPercent}%;"
|
||||
></div>`
|
||||
: ""}
|
||||
${orangePercent > 0
|
||||
? html`<div
|
||||
class="h-full bg-aquarius transition-[width] duration-200"
|
||||
style="width: ${orangePercent}%;"
|
||||
></div>`
|
||||
: ""}
|
||||
<div class="relative h-full">
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 w-full origin-left bg-malibu-blue transition-transform duration-200 ease-out"
|
||||
style="transform: scaleX(${greenPercent / 100});"
|
||||
></div>
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 w-full origin-left bg-aquarius transition-transform duration-200 ease-out"
|
||||
style="transform: translateX(${greenPercent}%) scaleX(${orangePercent /
|
||||
100});"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-0 flex items-center text-lg font-bold leading-none pointer-events-none"
|
||||
|
||||
@@ -414,19 +414,16 @@ export class PlayerInfoOverlay extends LitElement implements Controller {
|
||||
<div
|
||||
class="w-full h-5 lg:h-6 border border-gray-600 rounded-md bg-gray-900/60 overflow-hidden relative"
|
||||
>
|
||||
<div class="h-full flex">
|
||||
${greenPercent > 0
|
||||
? html`<div
|
||||
class="h-full bg-sky-700 transition-[width] duration-200"
|
||||
style="width: ${greenPercent}%;"
|
||||
></div>`
|
||||
: ""}
|
||||
${orangePercent > 0
|
||||
? html`<div
|
||||
class="h-full bg-malibu-blue transition-[width] duration-200"
|
||||
style="width: ${orangePercent}%;"
|
||||
></div>`
|
||||
: ""}
|
||||
<div class="relative h-full">
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 w-full origin-left bg-sky-700 transition-transform duration-200 ease-out"
|
||||
style="transform: scaleX(${greenPercent / 100});"
|
||||
></div>
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 w-full origin-left bg-malibu-blue transition-transform duration-200 ease-out"
|
||||
style="transform: translateX(${greenPercent}%) scaleX(${orangePercent /
|
||||
100});"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
class="absolute inset-0 flex items-center justify-between px-1.5 text-sm font-bold leading-none pointer-events-none"
|
||||
|
||||
Reference in New Issue
Block a user