Harmonica for Ruby
A simple, physics-based animation library for Ruby.
Ruby implementation of charmbracelet/harmonica.
A simple, efficient spring animation library for smooth, natural motion.
Installation
Add to your Gemfile:
gem "harmonica"Or install directly:
gem install harmonicaComponents
| 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 |
Usage
Spring
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
Projectile simulates physics motion with position, velocity, and acceleration. Great for particles, falling objects, and game physics.
Basic usage
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
endGravity constants
Harmonica::GRAVITY # Vector(0, -9.81, 0) - origin at bottom-left
Harmonica::TERMINAL_GRAVITY # Vector(0, 9.81, 0) - origin at top-leftCustom acceleration
No 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
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
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)Frame Rate Helper
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)Complete Example
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)
endDevelopment
Requirements:
- Ruby 3.2+
Install dependencies:
bundle installRun tests:
bundle exec rake testRun demos:
./demo/spring
./demo/dampingContributing
Bug reports and pull requests are welcome on GitHub at https://github.com/marcoroth/harmonica-ruby.
License
The gem is available as open source under the terms of the MIT License.
Acknowledgments
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.