JavaFX Binding, Events & Styling

Animations & Transitions

18 min Lesson 9 of 12

Animations & Transitions

JavaFX ships a fully integrated animation engine that runs on the same pulse timer as layout and rendering. You do not reach for an external library or a background thread — the framework handles scheduling, interpolation, and thread safety automatically. This lesson covers every major tool in that engine: value-animating Timeline, property-targeting Transition subclasses, chained SequentialTransition / ParallelTransition, and the low-level AnimationTimer for frame-by-frame logic.

The JavaFX Pulse. JavaFX repaints the scene at up to 60 frames per second on a dedicated JavaFX Application Thread pulse. Every animation class hooks into this pulse automatically. You never need a Thread.sleep loop or a javax.swing.Timer.

Timeline — The General-Purpose Animator

A Timeline animates any WritableValue (which includes every JavaFX property) along a series of KeyFrame checkpoints. You specify what value a property should hold at what time; the engine interpolates smoothly between them.

import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.scene.shape.Rectangle; import javafx.util.Duration; Rectangle rect = new Rectangle(50, 50, 80, 80); // Animate the rectangle's x position from its current value to 400 over 2 seconds KeyValue kv = new KeyValue(rect.translateXProperty(), 400); KeyFrame kf = new KeyFrame(Duration.seconds(2), kv); Timeline timeline = new Timeline(kf); timeline.setCycleCount(Timeline.INDEFINITE); // repeat forever timeline.setAutoReverse(true); // bounce back timeline.play();

KeyValue wraps a property reference and a target value. KeyFrame wraps one or more KeyValue objects and a point in time. A single Timeline can carry as many keyframes — with as many properties — as you need.

You can also attach a plain EventHandler<ActionEvent> to a keyframe to fire a callback at a specific moment, which is useful for triggering sound effects, updating state, or coordinating non-property changes:

KeyFrame tick = new KeyFrame( Duration.millis(500), e -> System.out.println("Half-second tick"), new KeyValue(rect.opacityProperty(), 0.2) );

Built-in Transition Classes

For the most common animation tasks JavaFX provides ready-made Transition subclasses. Each one targets a specific property or effect, so you avoid writing manual KeyValue boilerplate.

  • TranslateTransition — moves a node along X / Y / Z.
  • ScaleTransition — scales a node around its pivot point.
  • RotateTransition — rotates a node around its Z-axis (or a custom axis).
  • FadeTransition — animates the opacity property between two values.
  • FillTransition — interpolates the fill color of a Shape.
  • StrokeTransition — interpolates the stroke color of a Shape.
  • PathTransition — moves a node along an arbitrary Shape path.

Every subclass shares the same base API: setDuration(), setCycleCount(), setAutoReverse(), play(), pause(), stop(). Here is a complete example combining three transitions:

import javafx.animation.*; import javafx.scene.shape.Circle; import javafx.scene.paint.Color; import javafx.util.Duration; Circle circle = new Circle(40, Color.CORNFLOWERBLUE); // Slide the circle 200px to the right TranslateTransition slide = new TranslateTransition(Duration.seconds(1), circle); slide.setToX(200); // Grow it to 1.5× its original size ScaleTransition grow = new ScaleTransition(Duration.millis(600), circle); grow.setToX(1.5); grow.setToY(1.5); // Fade it out FadeTransition fade = new FadeTransition(Duration.millis(800), circle); fade.setToValue(0.0);

Composing Animations: Sequential and Parallel

Real UIs rarely run a single animation in isolation. SequentialTransition plays a list of animations one after the other; ParallelTransition plays them all at the same time. Both implement Animation, so you can nest them arbitrarily.

// Play slide, then grow, then fade — in order SequentialTransition seq = new SequentialTransition(slide, grow, fade); seq.setOnFinished(e -> System.out.println("All done")); seq.play(); // Or: slide and grow simultaneously, THEN fade ParallelTransition parallel = new ParallelTransition(slide, grow); SequentialTransition composed = new SequentialTransition(parallel, fade); composed.play();
Reuse transitions carefully. A Transition instance remembers its target node. If you call play() on a transition that is already running it restarts from the beginning. Clone or create fresh instances when you need to fire the same animation on multiple nodes.

Interpolators — Controlling the Feel

By default, properties interpolate linearly — constant speed from start to end, which looks mechanical. JavaFX's Interpolator class provides several built-in easing curves:

  • Interpolator.LINEAR — constant rate (default).
  • Interpolator.EASE_IN — starts slowly, accelerates.
  • Interpolator.EASE_OUT — starts fast, decelerates into the end value.
  • Interpolator.EASE_BOTH — slow start and slow end; the most natural-feeling curve for UI animations.
  • Interpolator.SPLINE(x1, y1, x2, y2) — a cubic Bézier for fully custom easing.
  • Interpolator.DISCRETE — jumps to the target value instantly at the keyframe boundary; useful for frame-by-frame sprite sheets.

Pass an Interpolator as the third argument to KeyValue, or call setInterpolator() on a Transition:

KeyValue kv = new KeyValue( rect.translateXProperty(), 400, Interpolator.EASE_BOTH ); // Or on a Transition subclass: TranslateTransition bounce = new TranslateTransition(Duration.seconds(0.4), btn); bounce.setToY(-20); bounce.setInterpolator(Interpolator.EASE_OUT); bounce.setAutoReverse(true); bounce.setCycleCount(2); bounce.play();

AnimationTimer — Frame-by-Frame Control

When you need to update a scene property on every single frame — for a game loop, a physics simulation, or a real-time data chart — AnimationTimer is the right tool. Override its handle(long now) method: now is the current timestamp in nanoseconds, which you use to compute delta-time between frames.

import javafx.animation.AnimationTimer; double[] x = {0}; // mutable container for lambda capture AnimationTimer timer = new AnimationTimer() { private long lastTime = 0; @Override public void handle(long now) { if (lastTime == 0) { lastTime = now; return; } double deltaSeconds = (now - lastTime) / 1_000_000_000.0; lastTime = now; x[0] += 150 * deltaSeconds; // 150 pixels per second if (x[0] > 600) x[0] = 0; rect.setTranslateX(x[0]); } }; timer.start(); // Later, to stop: timer.stop();
AnimationTimer runs on every frame. Anything you do inside handle() blocks the rendering thread. Keep it lightweight: update positions, consume a pre-computed queue, swap a pre-rendered image buffer. Never do I/O, database calls, or blocking computations there.

Lifecycle Methods

All Animation subclasses share the same lifecycle:

  • play() — start or resume from the current position.
  • playFromStart() — rewind to time 0 and play.
  • pause() — freeze at the current position; play() resumes from there.
  • stop() — halt and reset to time 0.
  • jumpTo(Duration) — seek to a specific position without changing play state.
  • setRate(double) — 2.0 plays at double speed; -1.0 plays in reverse.
  • setOnFinished(EventHandler) — callback fired when the final cycle completes.

Practical Pattern: Button Press Feedback

A short scale-and-return animation on button click is a classic example that ties together what you have learned. It uses a SequentialTransition of two ScaleTransition instances and reads cleanly in controller code:

private void animateClick(Node node) { ScaleTransition press = new ScaleTransition(Duration.millis(80), node); press.setToX(0.92); press.setToY(0.92); press.setInterpolator(Interpolator.EASE_IN); ScaleTransition release = new ScaleTransition(Duration.millis(120), node); release.setToX(1.0); release.setToY(1.0); release.setInterpolator(Interpolator.EASE_OUT); new SequentialTransition(press, release).play(); } // Wire it up: myButton.setOnAction(e -> { animateClick(myButton); handleBusinessLogic(); });

Summary

JavaFX gives you a layered animation toolkit: Timeline for full control over arbitrary properties, ready-made Transition subclasses for common effects, SequentialTransition and ParallelTransition for composition, and AnimationTimer for frame-level game loops. Interpolator curves decide how the motion feels. In the final lesson of this tutorial you will pull all of these techniques — binding, events, CSS, and animation — together into a complete reactive JavaFX application.