bezier-easing-rs

A type-generic Rust port of gre's bezier-easing for sampling cubic Bezier curves as animation easing functions.

Library
Cargo
v0.3.0
2stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
16/100Needs Attention
Development Activity12
Maintenance0
Community4
Maturity48
Momentum0

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
77/100Good
Architecture78
Code Quality92
Innovation82
Learning Curve55

bezier_easing is a Rust port of Gaëtan Renaudeau’s popular JavaScript bezier-easing library, giving Rust and WebAssembly projects the same cubic-bezier easing math used by CSS cubic-bezier() transitions. Given four control-point coordinates, it produces a reusable easing function that maps a progress value in [0, 1] to its corresponding eased output, using the same closed-form root-finding approach (a Cardano-formula cubic solve with a trigonometric fallback) as the original implementation.

The crate is generic over any floating-point type that implements its BezierFloat trait, so callers can pick f32 for WASM/embedded contexts or f64 for higher precision, with no dynamic dispatch: BezierEasing is an unboxed struct dispatching on an internal enum (Linear, Degenerate, Cubic) chosen once at construction time. A companion examples/wasm-demo crate compiles the library to WebAssembly and exposes it as a JS-callable class, demonstrating the intended browser/animation use case directly.

What You Get

  • A bezier_easing(x1, y1, x2, y2) constructor and BezierEasing::new that validate control points and build a reusable easing function
  • Generic f32/f64 support via the BezierFloat trait, so you can trade precision for WASM/embedded footprint
  • An unboxed, allocation-free BezierEasing struct — no boxed closures — dispatching internally on Linear/Degenerate/Cubic curve shapes for consistent performance
  • A wasm-demo example crate showing how to wrap the library with wasm-bindgen and drive an HTML canvas animation from JS
  • Criterion benchmarks and bitwise-regression tests proving the unboxed rewrite matches the previous boxed implementation bit-for-bit

Common Use Cases

  • Porting CSS cubic-bezier() transition timing functions into native Rust game or GUI code so animations match a web design’s easing curves exactly
  • Compiling to WebAssembly to give a JS animation library the same numerically verified easing implementation used server-side, avoiding duplicate logic in two languages
  • Driving interpolation for procedural animation systems (particle motion, camera pans, UI transitions) in Rust-native engines without depending on a full animation crate
  • Benchmarking or fuzz-testing custom easing curves against known-good bitwise output via the crate’s golden and property tests as a reference implementation

Under The Hood

Architecture The repo is a two-member Cargo workspace: crates/bezier-easing (the published library) and examples/wasm-demo (a thin WASM wrapper). The library itself lives in a single src/lib.rs, built around a BezierFloat trait implemented for f32/f64 via a macro, a BezierEasing<T> struct wrapping an internal BezierEasingKind<T> enum (Linear, Degenerate, Cubic) selected once inside BezierEasing::new, and two free functions (y() and x2t()) that perform the actual curve evaluation and root-solving, ported directly from the reference JS implementation. There is no I/O and no mutable state — sample() is a pure function of x and the precomputed enum variant — so the design is essentially a compile-time strategy dispatch replacing the closures used in the original JS port with a plain match, chosen for zero-allocation, zero-dynamic-dispatch sampling; the wasm-demo crate is the only downstream consumer and touches only the public constructor and sample().

Tech Stack Rust 2024 edition (rust-version 1.85) with an empty [dependencies] table — the library ships with zero runtime dependencies — and dev-dependencies limited to criterion for benchmarking and proptest for property-based fuzz testing. The workspace uses Cargo’s native member/resolver support; wasm-bindgen appears only in the separate wasm-demo crate to expose the library to JavaScript, with the resulting canvas demo deployed via a dedicated GitHub Pages workflow. CI runs cargo fmt --check, cargo clippy --all-targets --all-features -D warnings, cargo test --all-features, and a cargo llvm-cov --fail-under-lines 90 coverage gate on every push and pull request, with cargo package/cargo publish --dry-run documented as pre-release checks.

Code Quality An extensive test suite spans five files (bezier_easing_test.rs, boundary_test.rs, golden_test.rs, bitwise_regression_test.rs, property_test.rs) that together dwarf the roughly 150-line implementation. Golden tests pin exact float outputs against the original JS library’s known-good values; the bitwise-regression tests pin byte-identical f32/f64 output between the current unboxed struct and the prior boxed-closure implementation to guard the 0.3.0 rewrite; property tests fuzz random control points via proptest; boundary tests cover edge cases like x=0/x=1 and degenerate (a==0) curves. Errors are explicit and typed via a BezierEasingError newtype implementing Display/Error, returned from a validating constructor rather than panicking. Internal variable naming mirrors the original JS port (m_x1, ay, by, cy) for traceability to upstream at some cost to idiomatic Rust naming. CI enforces formatting, clippy with warnings-as-errors, and a coverage floor on every push.

API Design The public surface is minimal and idiomatic: a single free function bezier_easing(x1, y1, x2, y2) (or BezierEasing::new) returns Result<BezierEasing<T>, BezierEasingError>, and .sample(x) evaluates the curve — mirroring the construct-then-invoke pattern developers already know from the original gre/bezier-easing JS package and from CSS’s own cubic-bezier() syntax, so there’s little learning curve for anyone coming from web animation work. Generics are inferred from literal suffixes (0.0_f32 vs. the f64 default) rather than requiring explicit turbofish in the common case, and the constructor’s rustdoc ships a runnable example that cargo test executes as part of CI. The internal enum and math helpers (x2t, y) are private, keeping the API intentionally narrow — simple to use, though it offers no way to introspect which curve variant was chosen without instrumenting sample() directly.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search