Progress bars usually jump. A batch finishes, the bar lurches forward 30%, done. That works, but if you want the bar to actually feel like something is happening, easing gives you smooth, animated transitions between values instead.
It's opt-in, lightweight, and takes about ten lines to set up.
EasingConfiguration.DEFAULT gives you a ready-to-use animation with sensible values — EASE_OUT_QUAD, 500ms duration, 20 frames, and a threshold of 5. Good for most cases without any configuration:
ProgressBarConfiguration config = ProgressBarConfiguration.builder()
.easing(EasingConfiguration.DEFAULT)
.build();
ProgressBar bar = Clique.progressBar(100, config);EasingConfiguration.DISABLED is an explicit no-op. Pass it anywhere an EasingConfiguration is accepted to opt out of animation entirely:
ProgressBarConfiguration config = ProgressBarConfiguration.builder()
.easing(EasingConfiguration.DISABLED)
.build();Easing is configured separately from the progress bar itself, then attached via ProgressBarConfiguration. Once wired up, you use tickAnimated() instead of tick() for the updates you want animated.
EasingConfiguration easing = EasingConfiguration.builder()
.function(EasingFunction.EASE_OUT_CUBIC)
.duration(500)
.frames(30)
.threshold(10)
.build();
ProgressBarConfiguration config = ProgressBarConfiguration.builder()
.easing(easing)
.build();
ProgressBar bar = Clique.progressBar(100, config);
bar.tickAnimated(40); // smooth 500ms animation
bar.tickAnimated(3); // below threshold — jumps instantlytickAnimated() is safe to call on any progress bar. If easing isn't configured, or the tick amount falls below the threshold, it just falls through to a regular tick().
The curve that controls how the animation accelerates and decelerates. See the full list below.
.function(EasingFunction.EASE_OUT_CUBIC)Total animation time in milliseconds. Must be positive.
.duration(500) // half a secondHow many steps the animation is broken into. Frame delay is calculated automatically as duration / frames — integer division, so round numbers play nicer together.
.frames(30) // ~16ms per frame at 500ms durationMore frames means smoother motion but more CPU cycles. For CLI use, 20–30 frames is usually plenty.
The minimum tick amount needed to trigger animation. Ticks below this jump instantly. This is what makes it practical to mix tickAnimated() with tight loops — small increments skip the animation overhead entirely.
.threshold(10) // animate ticks >= 10, instant otherwiseIf you need to explicitly disable easing, use EasingConfiguration.DISABLED instead of building a configuration manually.
All functions are based on easings.net. The naming follows a simple pattern: EASE_IN accelerates into the animation, EASE_OUT decelerates out of it, EASE_IN_OUT does both.
| Function | Feel |
|---|---|
LINEAR |
Constant speed, no curve |
EASE_IN_SINE |
Gentle acceleration |
EASE_OUT_SINE |
Gentle deceleration |
EASE_IN_OUT_SINE |
Soft on both ends |
EASE_IN_QUAD |
Moderate acceleration |
EASE_OUT_QUAD |
Moderate deceleration |
EASE_IN_OUT_QUAD |
Balanced curve |
EASE_IN_CUBIC |
Strong acceleration |
EASE_OUT_CUBIC |
Strong deceleration |
EASE_IN_OUT_CUBIC |
Dramatic on both ends |
For progress bars, EASE_OUT_QUAD and EASE_OUT_CUBIC tend to feel the most natural — they move fast at first and settle into place, which matches how progress intuitively feels.
This is where easing actually shines in practice. You don't have to pick one or the other — animate the big jumps, tick instantly through the small ones:
ProgressBar bar = Clique.progressBar(1000, config);
// Large batch arrives, animate it
bar.tickAnimated(150);
// Process items individually — instant, no overhead
for (int i = 0; i < 50; i++) {
processItem();
bar.tick();
}
bar.render();
// Another large batch
bar.tickAnimated(200);tickAnimated() blocks. The animation runs on the calling thread and sleeps between frames. For CLI tools where the bar is the main event, this is fine. If that's a problem, you'll need to run it on a separate thread yourself.
Frames snap to target. After the animation loop finishes, the bar is set exactly to the target value. The easing math is floating point, so without the snap you'd accumulate small rounding errors over many ticks.
Auto-renders during animation. Unlike tick(), tickAnimated() calls render() for every frame automatically. You don't need to call it afterward.
// Smooth, higher CPU
.duration(1000).frames(60)
// Good general balance
.duration(500).frames(30)
// Snappy, minimal overhead
.duration(300).frames(15)If updates are happening dozens of times per second, skip easing entirely and use tick(). Easing is for the moments that deserve a bit of ceremony — large batch completions, stage transitions, the final stretch to 100%.
- Progress Bars — the full progress bar API
- Markup Reference — styling your progress bar output