fandhe-animation / fandhe-frontend-animation API ガイド
本ガイドは、fandhe-animation(プラットフォーム非依存のアニメーション演算コア)と fandhe-frontend-animation(Web アダプタ)を Rust コードから直接呼び出す利用者向けの解説です。Spring/Keyframes/RafDriver/ DomTarget/animate() 等の型・関数を自分のコードから呼ぶ使い方を扱います。
pre-styled-ui の presence/keyframes/stagger や wasm-full の in-view・stagger 配線・View Transitions・FLIP・SVG path・hover/press・scroll といった宣言的な data-* 属性を書くだけで動く機能の使い方は docs/guides/animation.md(アニメーション機能ガイド)を参照してください(本ガイドでは扱いません)。
設計判断の根拠(trait 境界の選定理由・no_std 評価・切り出し手順)を知りたい場合は docs/design/animation-core-architecture.md(開発者向け設計記録)を参照してください。本ガイドはその利用者向け API リファレンスです。
1. 3 層構成のおさらい
fandhe-animation ← fandhe-frontend-animation ← wasm-full(optional 依存)
(演算のみ) (Web アダプタ) (配線)fandhe-animation: イージング・補間・spring・keyframes・timeline の計算のみを担う。外部依存ゼロ・#![forbid(unsafe_code)]。将来別リポジトリへ切り出される前提のため、DOM・requestAnimationFrame・Web Animations API には一切触れません。fandhe-frontend-animation:fandhe-animationが定義するDriver/Target等の trait を、wasm-bindgen / web-sys / js-sys を使って requestAnimationFrame・DOM・Web Animations API 上に実装する層です。fandhe-frontend-wasm-full/-wasm-client/-wasm-thinのいずれにも依存しません。wasm-full:fandhe-frontend-animationを optional 依存として取り込み、型を再公開する配線層です(§4 参照)。
依存方向は一方向(fandhe-animation ← fandhe-frontend-animation ← wasm-full)で、逆方向の依存はありません。
2. fandhe-animation(プラットフォーム非依存)
2.1 easing
CSS 相当の決定論的イージング関数を提供します。
CubicBezier: CSScubic-bezier(x1, y1, x2, y2)相当。EASE/EASE_IN/EASE_OUT/EASE_IN_OUTの定数を持ちます。CubicBezier::new(x1, y1, x2, y2)はx1/x2が[0, 1]の範囲外、またはいずれかの引数が非有限値のときNoneを返します。Steps: CSSsteps(count, position)相当。StepPositionはJumpStart/JumpEnd/JumpNone/JumpBothの 4 種類です。Easing: 上記 2 種類とLinearをまとめた enum です。
use fandhe_animation::easing::{CubicBezier, Easing};
let ease_in_out = CubicBezier::new(0.42, 0.0, 0.58, 1.0).expect("有効な制御点");
let y = ease_in_out.evaluate(0.5); // t = 0.5 時点の進捗値
let easing = Easing::CubicBezier(ease_in_out);
assert_eq!(easing.evaluate(0.5), y);2.2 Interpolate と値型
Interpolate trait は「2 値と進捗 t から補間値を返す」契約です。t は [0, 1] に clamp されず、範囲外の t は外挿として扱われます(spring・オーバーシュート系イージングが範囲外の t を渡すため)。
対応する値型:
f64/f32: そのまま線形補間。Vec2/Vec3/Vec4: 成分ごとに線形補間。Rgba: 成分ごとに線形補間(premultiplied alpha・oklab 等の色空間指定はスコープ外)。Quat(単位四元数): slerp(球面線形補間)。回転角が大きいほど角速度が一定になる点が nlerp(成分ごと線形)と異なります。入力がほぼ同一の場合は数値的に不安定な slerp を避け nlerp + 正規化へ自動フォールバックします。Mat4(4x4 行列): 成分ごと線形補間です。回転を含む行列補間は正確ではありません(180° 回転同士の中点がゼロ行列へ潰れる等)。回転を伴う用途はVec3(translate/scale)+Quatを個別に補間してから合成することを推奨します。
use fandhe_animation::interpolate::{Interpolate, Vec3};
let from = Vec3 { x: 0.0, y: 0.0, z: 0.0 };
let to = Vec3 { x: 10.0, y: 20.0, z: 30.0 };
let mid = from.interpolate(&to, 0.5);
assert_eq!(mid, Vec3 { x: 5.0, y: 10.0, z: 15.0 });2.3 spring
物理ベースのばねソルバです(motion.dev spring() 相当)。時間単位は秒で統一されています。
SpringConfig:stiffness/damping/massの物理パラメータ。Defaultは motion.dev の既定値(stiffness = 100.0/damping = 10.0/mass = 1.0)です。SpringConfig::from_duration_bounce(duration, bounce, velocity, mass): duration(秒)/ bounce(0..=1、1 に近いほど大きくオーバーシュート)から物理パラメータへ変換します。数値的に発散した場合はDefaultへ fail-safe にフォールバックします。Spring::new(config, from, to, initial_velocity) -> Option<Spring>: パラメータが有効範囲外・非有限値のときはNoneを返します(ライブラリコードで panic しません)。Spring::at(t) -> SpringState { value, velocity, done }: 時刻t(秒)における値・速度・収束済みかどうかを返します。Spring::settle_duration() -> f64:doneになる最初の時刻(秒)を探索して返します。
use fandhe_animation::spring::{Spring, SpringConfig};
let spring = Spring::new(SpringConfig::default(), 0.0, 100.0, 0.0)
.expect("既定値は常に有効なパラメータ");
let state = spring.at(0.5);
assert!(!state.done);
let settle = spring.settle_duration();
assert!(spring.at(settle).done);2.4 keyframes
オフセット付きキーフレーム列の補間です(Motion の animate() 配列キーフレーム [0, 1, 0] + ease: [...] 相当)。
Keyframe<T> { offset, value }:offsetは0.0..=1.0に正規化された時刻です。Keyframes::new(frames, easings)/Keyframes::evenly(values, easings):Result<Keyframes<T>, KeyframesError>を返します。easingsが空なら全区間Easing::Linearを補います。それ以外は長さが区間数(frames.len() - 1)と一致していなければなりません。Keyframes::at(t) -> T(T: Clone):tは[0, 1]へ clamp され、区間外(先頭 offset 未満・末尾 offset 超過)は端点の値を保持します(Web Animations API の fill 挙動と同じ)。
use fandhe_animation::easing::Easing;
use fandhe_animation::keyframes::Keyframes;
let kf = Keyframes::evenly(vec![0.0, 10.0, 0.0], vec![Easing::Linear, Easing::Linear])
.expect("3 値・2 easing は有効な構成");
assert_eq!(kf.at(0.0), 0.0);
assert_eq!(kf.at(0.5), 10.0);
assert_eq!(kf.at(1.0), 0.0);2.5 timeline / stagger
Stagger は複数要素へ一律の遅延パターン(Motion の stagger() 相当)を与える純関数、Timeline は複数セグメント(開始時刻・duration・対象値)をラベル付きで束ね、任意時刻 t における各セグメントの進行度を返します。いずれも時間単位は秒です。
use fandhe_animation::timeline::{At, Stagger, Timeline};
let stagger = Stagger::new(0.5);
let mut tl: Timeline<usize> = Timeline::new();
for i in 0..3 {
tl.add_at(i, 1.0, At::Absolute(stagger.delay(i, 3))).unwrap();
}
// 各要素は 0.5 秒ずつずれて開始する。
assert_eq!(tl.segments()[0].start, 0.0);
assert_eq!(tl.segments()[1].start, 0.5);
assert_eq!(tl.segments()[2].start, 1.0);
// t = 1.0 時点では要素 0 は完了・要素 2 は開始直後。
let progress: Vec<f64> = tl.progress_at(1.0).map(|(p, _)| p).collect();
assert_eq!(progress, vec![1.0, 0.5, 0.0]);At は Absolute(絶対時刻)・AfterPrevious(直前セグメント終了時刻からの相対)・WithPrevious(直前セグメント開始時刻からの相対)・Label(ラベル参照)の 4 種類です。Timeline::label/label_time で名前付きの時刻マーカーを扱えます。
2.6 Driver / Target trait
Driver は「フレームループから tick を受け取る」trait、Target<T> は「計算結果 T の書き込み先」を抽象化する trait です。いずれも本クレート自体はフレームループ・DOM への書き込みを持たず、実装を fandhe-frontend-animation(§3)へ委ねます。
Driver::tick(&mut self) -> Option<f64>: 直前の呼び出しからの経過秒(pull 型。呼び出し側が毎フレーム 1 回呼ぶ)。供給が尽きた・まだ計測を開始していない等の理由で経過時間を返せない場合はNoneを返します。Target<T>::write(&mut self, value: T): 計算済みの値を書き込みます。毎フレーム呼ばれる想定のため panic しない・エラーを返さないことが前提です。
テストでの駆動には ManualDriver(あらかじめ積んだ delta 列を FIFO で払い出す参照実装)と RecordingTarget(書き込まれた値を到着順に蓄積する参照実装)が使えます。crate 外(fandhe-frontend-animation 等)のテストコードから利用するには fandhe-animation の test-utils feature を有効にします。
[dev-dependencies]
fandhe-animation = { version = "0.1", features = ["test-utils"] }3. fandhe-frontend-animation(Web アダプタ)
3.1 RafDriver + AnimationLoop
RafDriver は window.performance.now() の差分(秒)を返す Driver 実装です。RafDriver::new() -> Option<Self> はブラウザ環境(wasm32 ターゲットかつ window/window.performance が取得できる環境)でのみ Some を返し、それ以外(native/SSR)では panic せず None を返します。
AnimationLoop::start(step: impl FnMut() -> bool + 'static) -> Self は requestAnimationFrame で step を毎フレーム呼び続け、step が false を返したら自動停止するループ補助です。戻り値の AnimationLoop を保持し続ける必要があります(Drop で自動的に stop() されるため、変数を drop するとループが止まります)。
use fandhe_frontend_animation::raf_driver::{AnimationLoop, RafDriver};
use fandhe_animation::driver::Driver;
let mut driver = RafDriver::new().expect("ブラウザ環境");
// `loop_` を保持し続ける(drop すると Drop::drop 経由で自動停止する)。
let loop_ = AnimationLoop::start(move || {
let Some(_dt) = driver.tick() else {
return true; // 初回 tick は基準時刻の記録のみ
};
// ここで spring.at(t) 等を計算し Target へ書き込む
true // 継続するなら true、止めるなら false
});3.2 DomTarget
DomTarget は HtmlElement の style へ計算値を書き込む Target<f64> 実装です。
DomTarget::style_property(element, name, unit): 標準 CSS プロパティ(例:opacity)へ書き込みます。unitは値へ付与する単位("px"/"deg"等)で、単位不要なプロパティは空文字を渡します。DomTarget::custom_property(element, name): CSS カスタムプロパティ(例:--fandhe-motion-progress)へ書き込みます。
いずれも CSSStyleDeclaration.setProperty(name, value)(2 引数 API)のみを使うため、; 等で追加の CSS 宣言を注入する経路を構造的に持ちません(§5 参照)。
use fandhe_frontend_animation::dom_target::DomTarget;
use fandhe_animation::target::Target;
let mut target = DomTarget::style_property(element, "opacity", "");
target.write(0.5); // element.style.opacity = "0.5"
let mut translate_x = DomTarget::style_property(element2, "translate", "px");
translate_x.write(120.0); // element2.style.translate = "120px"3.3 統合例: spring を実 DOM で駆動する
RafDriver(§3.1)・DomTarget(§3.2)・Spring(§2.3)を組み合わせると、実 DOM 上で spring アニメーションを駆動できます。
use fandhe_animation::driver::Driver;
use fandhe_animation::spring::{Spring, SpringConfig};
use fandhe_animation::target::Target;
use fandhe_frontend_animation::dom_target::DomTarget;
use fandhe_frontend_animation::raf_driver::{AnimationLoop, RafDriver};
use web_sys::HtmlElement;
/// `element` の `opacity` を 0 → 1 へ spring で駆動するループを開始する。
/// 戻り値の `AnimationLoop` を呼び出し側が保持し続けること。
fn start_fade_in(element: HtmlElement) -> AnimationLoop {
let spring = Spring::new(SpringConfig::default(), 0.0, 1.0, 0.0)
.expect("既定値は常に有効なパラメータ");
let mut driver = RafDriver::new().expect("ブラウザ環境");
let mut target = DomTarget::style_property(element, "opacity", "");
let mut elapsed = 0.0_f64;
AnimationLoop::start(move || {
let Some(dt) = driver.tick() else {
return true; // 初回 tick は基準時刻の記録のみ
};
elapsed += dt;
let state = spring.at(elapsed);
target.write(state.value);
!state.done // 収束したら停止する
})
}より詳細な結合例は crates/frontend-animation/tests/spring_via_raf_dom_browser.rs(実ブラウザでの動作確認テスト)を参照してください。
3.4 animate()(WAAPI 薄いラッパ)
element.animate()(Web Animations API、以下 WAAPI)の薄いラッパです(wasm32 ターゲット限定)。
WaapiKeyframe { offset, easing, properties }: WAAPI へ渡す 1 keyframe です。AnimateOptions { duration_ms, easing, fill, iterations }:element.animate()の第 2 引数に相当します。duration_msはミリ秒です(fandhe-animation全体が秒単位で統一されている点と異なるので注意してください)。easing_to_css(easing: Easing) -> String:fandhe-animation::easingは CSS 文字列を一切生成しない不変条件を持つため、CSS easing 文字列("cubic-bezier(...)"/"steps(...)"等)への変換はこの関数が担います。native(非 wasm32)からも呼び出せます。keyframes_to_waapi(keyframes, property, to_css_value) -> Vec<WaapiKeyframe>:Keyframes<T>を単一 CSS プロパティの WAAPI keyframes 列へ変換します。Keyframes::atが区間外を端点値で保持する契約と、WAAPI が offset 0/1 の keyframe 欠落を要素の「underlying value」で補完する仕様との差を埋めるため、先頭が offset 0 でない・末尾が offset 1 でない入力は端点値を保持したまま offset 0/1 の keyframe を自動的に補います。animate(element, keyframes, options) -> Result<AnimationHandle, JsValue>(wasm32 限定):element.animate(keyframes, options)を呼び出します。AnimationHandle::finished(&self) -> Result<(), JsValue>(wasm32 限定、async): WAAPIAnimation.finishedPromise を待ちます。
use fandhe_animation::easing::{CubicBezier, Easing};
use fandhe_animation::keyframes::{Keyframe, Keyframes};
use fandhe_frontend_animation::animate::{easing_to_css, keyframes_to_waapi};
let keyframes = Keyframes::new(
vec![
Keyframe { offset: 0.0, value: 0.0_f64 },
Keyframe { offset: 1.0, value: 1.0_f64 },
],
vec![Easing::CubicBezier(CubicBezier::EASE_IN)],
)
.unwrap();
let waapi = keyframes_to_waapi(&keyframes, "opacity", |v| v.to_string());
assert_eq!(waapi.len(), 2);
assert_eq!(
waapi[0].easing.as_deref(),
Some(easing_to_css(Easing::CubicBezier(CubicBezier::EASE_IN)).as_str())
);use fandhe_frontend_animation::animate::{animate, AnimateOptions};
let handle = animate(&element, &waapi, &AnimateOptions {
duration_ms: 300.0,
easing: None, // 各 keyframe の easing を使うため全体既定は指定しない
fill: Some("forwards".to_string()),
iterations: Some(1.0),
})?;
handle.finished().await?;4. wasm-full から利用する(fandhe-frontend 固有部分)
fandhe-animation・fandhe-frontend-animation は fandhe-frontend- wasm-full に依存しない独立クレートですが、wasm-full は optional 依存としてこれらを取り込み、直接依存を追加しなくても型へ到達できる配線点を提供します(依存方向は不変: fandhe-animation ← fandhe-frontend-animation ← wasm-full(optional))。
4.1 feature
animation-driver(既定 on):dep:fandhe-frontend-animationを有効化し、animation_driverモジュール(RafDriver/DomTarget/AnimationLoopの薄い再公開)を公開します。Runtime::mount/hydrateからの新規呼び出しは伴いません(型の再公開のみ)。animate(既定 on):fandhe-frontend-animationcrate 自体(fandhe_animationへの到達路を含む)を再公開します。
いずれも crates/dist-server/src/wasm_dist_features.rs の WASM_DIST_FEATURES(fandhe-frontend-dist-server が配布する最小インタラクティブ構成 7 feature)には含まれていません。つまり、fandhe-frontend-dist-server 経由の配布 WASM にはこれらの機能は出荷されません(docs/guides/wasm-full-features.md を参照)。
default-features = false を使う場合は、これらの機能を維持するために features 配列へ明示的に "animation-driver"/"animate" を含める必要があります(詳細・完全な feature 列挙は docs/guides/wasm-full-features.md を参照)。
4.2 到達パス
wasm-full への依存だけで、次の 2 経路から到達できます(自分のアプリで fandhe-frontend-animation へ直接依存を追加する必要はありません)。
// animation-driver feature 経由(RafDriver/DomTarget/AnimationLoop)
use fandhe_frontend_wasm_full::animation_driver::{AnimationLoop, DomTarget, RafDriver};
// animate feature 経由(animate() WAAPI ラッパ、fandhe-animation の型も
// ここから到達できる)
use fandhe_frontend_wasm_full::fandhe_frontend_animation::animate::{animate, AnimateOptions};
use fandhe_frontend_wasm_full::fandhe_frontend_animation::fandhe_animation::spring::{Spring, SpringConfig};4.3 data-* 配線との対応関係
現状(2026-09 時点): rAF Driver / DOM Target / WAAPI を data-* 属性から自動的に駆動する宣言的配線は存在しません。 animation_driver モジュールは型の再エクスポートのみを行い、wire_* 相当の自動呼び出しを持ちません。
既存の in_view/gesture/stagger_index 等の data-* 配線(宣言的機能側)は IntersectionObserver・Pointer Events 由来の状態を data-* へ書き戻すのみで、fandhe-animation/fandhe-frontend-animation には一切依存しない別系統です。本ガイドの API(§2/§3/§4.1/§4.2)と混同しないよう注意してください。
今後 data-* 属性からの自動配線(stagger 適用・animate() 自動トリガー等)が追加される場合の実装先は親トラッキング #2508 配下です。宣言的な使い方は docs/guides/animation.md(アニメーション機能ガイド)を参照してください。
5. セキュリティ上の注意
DomTargetはCSSStyleDeclaration.setPropertyの 2 引数 API のみを使い、プロパティ名・単位は呼び出し側 Rust コードの固定値です。信頼できない DOM 文字列(data-*属性の値等)をそのままプロパティ名に使う経路は持ちません。animate()のWaapiKeyframe.properties/AnimateOptions.easingは、本ガイドの型付き API(easing_to_css・呼び出し側のto_css_value)を経由した信頼できる値を渡してください。未検証の外部入力文字列をそのまま渡さないでください。
6. 関連ドキュメント
docs/design/animation-core-architecture.md— 3 層構成の設計根拠・trait 境界・no_std 評価・切り出し手順(開発者向け)docs/design/motion-reference-adoption-policy.md§6 — Motion 参照方針・3 層構成/ゼロコスト方針の決定記録docs/guides/animation.md(アニメーション機能ガイド) — 宣言的data-*配線の使い方(pre-styled-ui の presence/keyframes/stagger・wasm-full の in-view/View Transitions/FLIP 等)- pre-styled-ui motion feature ガイド
- wasm-full feature 選択ガイド