perf(ui): stop the nav-rail brand pulse to fix web scroll jank

The DeltaMark ran an endless 9s breathing animation (60fps repaint)
inside the persistent nav rail, so it repainted on every scroll
frame and stuttered scrolling on Flutter web. Make the rail mark
static (animated: false) and isolate the animated variant (landing
page) behind a RepaintBoundary.

Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
flemming-it 2026-06-19 02:25:38 +02:00
parent 51beb332c4
commit 136d3b48c6
2 changed files with 29 additions and 12 deletions

View file

@ -50,7 +50,11 @@ class _ShellPageState extends State<ShellPage> {
),
child: Column(
children: [
const DeltaMark(size: 36),
// Static in the persistent nav rail: a 60fps pulse
// here repaints every scroll frame and stutters web
// scrolling. The breathing mark stays on the
// landing page (which isn't scrolled).
const DeltaMark(size: 36, animated: false),
const SizedBox(height: ReclaimSpace.sm),
const BrandWordmark(size: 18),
Text(

View file

@ -46,17 +46,30 @@ class _DeltaMarkState extends State<DeltaMark>
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
// Static mark: no controller listening, paint once.
if (!widget.animated) {
return CustomPaint(
size: Size.square(widget.size),
painter: _DeltaPainter(pulse: 1.0),
);
}
// Animated: isolate the per-frame repaint in its own layer so
// the breathing pulse never invalidates surrounding content
// (web scroll stays smooth).
return RepaintBoundary(
child: AnimatedBuilder(
animation: _controller,
builder: (context, _) {
final t = _controller.value;
// Breathe: scale 1 1.28 1 over the cycle.
final pulse = 1.0 + 0.28 * (1 - (math.cos(t * 2 * math.pi).abs()));
final pulse =
1.0 + 0.28 * (1 - (math.cos(t * 2 * math.pi).abs()));
return CustomPaint(
size: Size.square(widget.size),
painter: _DeltaPainter(pulse: widget.animated ? pulse : 1.0),
painter: _DeltaPainter(pulse: pulse),
);
},
),
);
}
}