A domino chain reaction can be animated by giving each tile a slightly later start time. In this SwingLoops example, 92 dominoes are placed on a spiral. Each one appears to fall toward the next, changing from blue to orange as it moves. You can create the effect using Java Swing and Java 2D without a physics engine or external libraries.

Domino Spiral
Source code
Save this example as DominoSpiral.java.
package org.kodejava.swing.animation;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Path2D;
public class DominoSpiral extends JPanel {
private static final int WIDTH = 450;
private static final int HEIGHT = 800;
private static final int COUNT = 92;
private static final double START = 1.0;
private static final double DELAY = 0.065;
private static final double FALL = 0.24;
private static final double CYCLE = 10.0;
private static final Color BACKGROUND = new Color(10, 17, 31);
private final double[] x = new double[COUNT];
private final double[] y = new double[COUNT];
private final double[] direction = new double[COUNT];
private final Timer timer = new Timer(16, _ -> repaint());
private long startedAt;
public DominoSpiral() {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setBackground(BACKGROUND);
// Place the tiles about 13 pixels apart along the spiral.
double angle = 0;
double pitch = 8.5;
for (int i = 0; i < COUNT; i++) {
double radius = 195 - pitch * angle;
x[i] = WIDTH / 2.0 + radius * Math.cos(angle);
y[i] = HEIGHT / 2.0 + radius * Math.sin(angle);
angle += 13 / Math.hypot(radius, pitch);
}
for (int i = 0; i < COUNT - 1; i++) {
direction[i] = Math.atan2(y[i + 1] - y[i], x[i + 1] - x[i]);
}
direction[COUNT - 1] = direction[COUNT - 2];
}
@Override
public void addNotify() {
super.addNotify();
startedAt = System.nanoTime();
timer.start();
}
@Override
public void removeNotify() {
timer.stop();
super.removeNotify();
}
private static double clamp(double value) {
return Math.max(0, Math.min(1, value));
}
private static Color tileColor(double progress) {
return new Color(
57 + (int) (193 * progress),
171 - (int) (54 * progress),
248 - (int) (179 * progress));
}
private static void drawTile(Graphics2D g, double progress) {
double lean = 10 * progress;
double height = 20 - 14 * progress;
g.setColor(new Color(2, 6, 13, 150));
g.fillOval(-5, -6, 19, 12);
Path2D face = new Path2D.Double();
face.moveTo(0, -4);
face.lineTo(0, 4);
face.lineTo(lean + 5, 4 - height);
face.lineTo(lean + 5, -4 - height);
face.closePath();
g.setColor(tileColor(progress));
g.fill(face);
g.setColor(new Color(255, 255, 255, 155));
g.draw(face);
}
@Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
double elapsed = (System.nanoTime() - startedAt) / 1e9;
double t = elapsed % CYCLE;
double fade = t < 9 ? 1 : 1 - clamp(t - 9);
Graphics2D g = (Graphics2D) graphics.create();
try {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
double scale = Math.min(getWidth() / (double) WIDTH,
getHeight() / (double) HEIGHT);
g.translate((getWidth() - WIDTH * scale) / 2,
(getHeight() - HEIGHT * scale) / 2);
g.scale(scale, scale);
g.setComposite(java.awt.AlphaComposite.SrcOver.derive((float) fade));
g.setColor(new Color(31, 49, 72));
Path2D track = new Path2D.Double();
track.moveTo(x[0], y[0]);
for (int i = 1; i < COUNT; i++) {
track.lineTo(x[i], y[i]);
}
g.draw(track);
for (int i = 0; i < COUNT; i++) {
double u = clamp((t - START - i * DELAY) / FALL);
double progress = u * u * (3 - 2 * u);
Graphics2D tile = (Graphics2D) g.create();
try {
tile.translate(x[i], y[i]);
tile.rotate(direction[i]);
drawTile(tile, progress);
} finally {
tile.dispose();
}
}
} finally {
g.dispose();
}
}
static void main() {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("SwingLoops - Domino Spiral");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new DominoSpiral());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
Run the example
Open a terminal in the directory containing the file. With a JDK installed and a desktop environment available, run:
javac DominoSpiral.java
java DominoSpiral
The window opens with a preferred drawing area of 450 × 800 pixels. Resize it to fit your screen; the drawing keeps its proportions. Close the window to exit.
Place the tiles along a spiral
The code starts with a radius of 195 pixels and reduces it by 8.5 pixels for each radian of angular travel. Sine and cosine convert the radius and angle to a position on the screen.
Using the same angle increment everywhere would spread out the outer tiles and crowd the inner ones. Instead, the code uses this approximation:
angle += 13 / Math.hypot(radius, pitch);
For a small angle change, the distance traveled along this spiral is approximately the angle change multiplied by sqrt(radius² + pitch²). Dividing the desired spacing of 13 pixels by that factor gives an angle step that grows as the spiral tightens. The result is approximately even spacing. After placing the tiles, Math.atan2() determines which direction each tile should face by looking at the next tile.
Turn a delay into a chain reaction
The first tile starts one second into the cycle. Each later tile waits an additional 65 milliseconds:
double u = clamp((t - START - i * DELAY) / FALL);
A value of zero means the tile is upright, and one means it has finished falling. The fall takes 240 milliseconds, so several neighboring tiles are in motion at once. Smoothstep, u * u * (3 - 2 * u), eases the movement at both ends.
To draw the fall, the program shifts the tile’s upper edge forward and reduces its visible height. Its color shifts toward orange at the same time. This is a stylized 2D animation: the timing creates the apparent chain reaction, and the program does not calculate contact between tiles.
Repeat the animation
A Swing timer requests repaints about every 16 milliseconds. The drawing uses elapsed time from System.nanoTime(), so a delayed repaint does not change when the next tile is supposed to fall. During the final second the spiral fades away. At the ten-second boundary, the upright tiles reappear.
Try raising DELAY from 0.065 to 0.075 to slow down the chain. If you increase it further, check that START + (COUNT - 1) * DELAY + FALL still fits before the fade begins at nine seconds.
The complete source code is free to copy and run. Find more examples in the SwingLoops repository.