Ruby implementation of charmbracelet/harmonica.
A simple, efficient spring animation library for smooth, natural motion.
Add to your Gemfile:
gem "harmonica"Or install directly:
gem install harmonica| Component | Description |
|---|---|
| Spring | Damped harmonic oscillator for smooth UI animations |
| Projectile | Physics projectile motion for particles |
| Point | 3D point coordinates |
| Vector | 3D vector with magnitude and direction |
Spring provides smooth, realistic motion using a damped harmonic oscillator. Perfect for UI animations like scroll positions, element transitions, and interactive feedback.
Basic usage:
require "harmonica"
spring = Harmonica::Spring.new(
delta_time: Harmonica.fps(60), # 60 FPS
angular_frequency: 6.0, # Speed of motion
damping_ratio: 0.5 # Smoothness
)
position = 0.0
velocity = 0.0
target = 100.0
loop do
position, velocity = spring.update(position, velocity, target)
break if (position - target).abs < 0.01
endDamping ratios:
| Ratio | Behavior |
|---|---|
< 1.0 |
Under-damped: oscillates before settling (bouncy) |
= 1.0 |
Critically-damped: fastest without oscillation |
> 1.0 |
Over-damped: slow approach, no oscillation |
Example with Bubbletea:
class ScrollModel
def initialize
@scroll_position = 0.0
@scroll_velocity = 0.0
@target_scroll = 0.0
@spring = Harmonica::Spring.new(
delta_time: Harmonica.fps(60),
angular_frequency: 5.0,
damping_ratio: 0.8
)
end
def update(message)
case message
when Bubbletea::KeyMessage
@target_scroll += 10 if message.to_s == "down"
@target_scroll -= 10 if message.to_s == "up"
end
@scroll_position, @scroll_velocity = @spring.update(
@scroll_position,
@scroll_velocity,
@target_scroll
)
[self, nil]
end
endProjectile simulates physics motion with position, velocity, and acceleration. Great for particles, falling objects, and game physics.
require "harmonica"
projectile = Harmonica::Projectile.new(
delta_time: Harmonica.fps(60),
position: Harmonica::Point.new(0, 100, 0),
velocity: Harmonica::Vector.new(10, 20, 0),
acceleration: Harmonica::GRAVITY
)
loop do
position = projectile.update
puts "Position: #{position.x}, #{position.y}"
break if position.y <= 0
endHarmonica::GRAVITY # Vector(0, -9.81, 0) - origin at bottom-left
Harmonica::TERMINAL_GRAVITY # Vector(0, 9.81, 0) - origin at top-leftNo gravity (space)
projectile = Harmonica::Projectile.new(
delta_time: Harmonica.fps(60),
position: Harmonica::Point.new(0, 0, 0),
velocity: Harmonica::Vector.new(5, 5, 0),
acceleration: Harmonica::Vector.new(0, 0, 0)
)Strong gravity
projectile = Harmonica::Projectile.new(
delta_time: Harmonica.fps(60),
position: Harmonica::Point.new(0, 100, 0),
velocity: Harmonica::Vector.new(0, 0, 0),
acceleration: Harmonica::Vector.new(0, -20, 0)
)Point represents a position in 3D space.
point = Harmonica::Point.new(10.0, 20.0, 30.0)
point.x # => 10.0
point.y # => 20.0
point.z # => 30.0
point.to_a # => [10.0, 20.0, 30.0]Vector represents direction and magnitude in 3D space.
vector = Harmonica::Vector.new(3.0, 4.0, 0.0)
vector.magnitude # => 5.0
vector.normalize # => Vector(0.6, 0.8, 0.0)
v1 = Harmonica::Vector.new(1, 2, 3)
v2 = Harmonica::Vector.new(4, 5, 6)
v1 + v2 # => Vector(5, 7, 9)
v1 - v2 # => Vector(-3, -3, -3)
v1 * 2 # => Vector(2, 4, 6)Use Harmonica.fps to calculate the time delta for a given frame rate:
Harmonica.fps(60) # => 0.01666... (1/60 second)
Harmonica.fps(30) # => 0.03333... (1/30 second)
Harmonica.fps(120) # => 0.00833... (1/120 second)Animate a value from 0 to 100 with spring physics:
require "harmonica"
spring = Harmonica::Spring.new(
delta_time: Harmonica.fps(60),
angular_frequency: 6.0,
damping_ratio: 0.3 # bouncy
)
position = 0.0
velocity = 0.0
target = 100.0
60.times do |frame|
position, velocity = spring.update(position, velocity, target)
bar_length = (position / 2).to_i
bar = "#" * bar_length
puts "\r#{bar.ljust(50)} #{position.round(1)}"
sleep(1.0 / 60)
endRequirements:
- Ruby 3.2+
Install dependencies:
bundle installRun tests:
bundle exec rake testRun demos:
./demo/spring
./demo/dampingBug reports and pull requests are welcome on GitHub at https://github.com/marcoroth/harmonica-ruby.
The gem is available as open source under the terms of the MIT License.
This gem is a Ruby implementation of charmbracelet/harmonica, part of the excellent Charm ecosystem. The spring algorithm is based on Ryan Juckett's damped springs. Charm Ruby is not affiliated with or endorsed by Charmbracelet, Inc.
Part of Charm Ruby.
Lipgloss • Bubble Tea • Bubbles • Glamour • Huh? • Harmonica • Bubblezone • Gum • ntcharts
The terminal doesn't have to be boring.
