Pendulum Wave

A row of pendulums with tuned periods drifts from a straight line into travelling waves and braids, then realigns, as a seamless loop

Installation
$ pnpm dlx shadcn@latest add @remocn/pendulum-wave

Usage

PendulumWave is the classic pendulum wave demo seen from the side. A row of bobs swings up and down along vertical tracks. Pendulum i makes cycles + i full swings per loop, so each one is slightly faster than its left neighbor. At frame 0 every bob sits at the top of its track. The row then slides into a travelling wave, breaks into braids and two- and three-row patterns, and lines up again at the top on the last frame of the loop.

A red line threads through the bobs as a smooth curve, so the wave reads as one shape. Each bob leaves a faint smear of its last four frames, and bobs low on the track are drawn up to 22% larger than bobs at the top, a small depth cue. The faint guides are a center line, a dotted track per pendulum and a tick at each end of every track.

import { PendulumWave } from "@/components/remocn/pendulum-wave";
 
export const RhythmBed = () => <PendulumWave />;

The component fills its parent and paints its own background. The row spans the middle 80% of the width and the bobs travel 30% of the height above and below the center line. Strokes and bob sizes scale with the composition, so 16:9, 1:1 and 9:16 look alike. Every frame is computed directly from the Remotion frame, so seeking and rendering are deterministic.

Timing

At 30 fps, speed={1} and the defaults (cycles={10}, loopFrames={450}):

FramesMotion
0All 15 bobs line up at the top of their tracks
0–90The line peels into a travelling wave, fastest bobs on the right
90–220The wave tightens into braids and short-lived row patterns
112Bobs repeat in groups of four: top, center, bottom, center
225Bobs alternate top and bottom in a two-row zigzag
225–450The sequence plays back in mirror order
450All bobs line up at the top again, identical to frame 0

The loop period is loopFrames / speed frames: 450 frames, 15 seconds, at the defaults. At loopFrames / k the bobs repeat in groups of k along the row, so the patterns land on predictable frames. The loop closes exactly only when cycles is a whole number. For a seamless background, make the Sequence a multiple of the period, or wrap the component in a Remotion <Loop> of that length.

import { Loop } from "remotion";
import { PendulumWave } from "@/components/remocn/pendulum-wave";
 
export const Background = () => (
  <Loop durationInFrames={300}>
    <PendulumWave count={21} cycles={8} loopFrames={300} showGuides={false} />
  </Loop>
);

Variations

Raise count for a denser, faster-braiding row. Lower cycles for slower swings; the difference between neighbors stays one swing per loop, so the patterns keep the same timing. showGuides={false} leaves only the bobs and the wave line. Set accent to background to hide the line.

Props
PropTypeDefaultDescription
count
number15Number of pendulums, rounded, minimum 2
cycles
number10Full swings of the slowest pendulum per loop; pendulum i makes cycles + i. Use a whole number for a seamless loop
loopFrames
number450Frames until every pendulum realigns at the top
showGuides
booleantrueDraws the center line, dotted tracks and end ticks
background
string"#111111"Background fill
color
string"#FFFFFF"Bobs, smears and guides
accent
string"#E5322D"The wave line threaded through the bobs
speed
number1Playback multiplier; the loop period is loopFrames / speed