English

アニメーション

キーフレームも Rust の値です。要素がキーフレームを使うと、そのキーフレームのルールも要素と一緒にページに書かれます。

キーフレーム

Keyframes::new(name) でアニメーションを作り、at(percent, style) で段階を 1 つ追加します。Style::keyframes は要素の animation-name を設定し、@keyframes のルールもスタイルに含めます。そのため、要素を含むページやパッチには、そのルールも含まれます。キーフレームにはセレクタもメディアクエリも書けないので、キーフレームに書かれるのはスタイルのうち条件の付かない宣言だけです。

use hypersig::html::span;
use hypersig::style::value::{AnimationIterationCount, AnimationTimingFunction, ms, raw};
use hypersig::style::{Keyframes, Media, Style};
use hypersig::{Page, Signals};
use serde::{Deserialize, Serialize};

#[derive(Signals, Serialize, Deserialize)]
struct Upload {
    busy: bool,
}

let spin = Keyframes::new("spin")
    .at(0, Style::new().transform(raw("rotate(0deg)")))
    .at(100, Style::new().transform(raw("rotate(360deg)")));

let spinner = span::<Upload>()
    .attr("aria-hidden", "true")
    .css(
        Style::new()
            .keyframes(&spin)
            .animation_duration(ms(800))
            .animation_timing_function(AnimationTimingFunction::Linear)
            .animation_iteration_count(AnimationIterationCount::Infinite)
            // Slower for a visitor who asks for reduced motion.
            .on(Media::ReducedMotion, Style::new().animation_duration(ms(4000))),
    )
    .show(Upload::busy())
    .text("◌");

let html = Page::new("Upload", Upload { busy: true }).child(spinner).render();
assert!(html.contains("@keyframes spin"));

実行結果

spinner.rs
use hypersig::html::{div, span};
use hypersig::style::pattern::hstack;
use hypersig::style::value::{
    AnimationIterationCount, AnimationTimingFunction, BorderStyle, ms, px, raw, rem,
};
use hypersig::style::{Keyframes, Media, Style};
use hypersig::{Page, Signals, if_else};
use hypersig_ui::{ButtonSize, ButtonVariant, button, token};
use serde::{Deserialize, Serialize};

#[derive(Signals, Serialize, Deserialize)]
pub struct Upload {
    busy: bool,
}

pub fn page() -> Page<Upload> {
    let busy = Upload::busy();
    let spin = Keyframes::new("spin")
        .at(0, Style::new().transform(raw("rotate(0deg)")))
        .at(100, Style::new().transform(raw("rotate(360deg)")));
    let ring = Style::new()
        .width(rem(1.5))
        .height(rem(1.5))
        .border_width(px(3))
        .border_style(BorderStyle::Solid)
        .border_color(token::muted().var())
        .border_top_color(token::primary().var())
        .border_radius(px(999))
        .keyframes(&spin)
        .animation_duration(ms(800))
        .animation_timing_function(AnimationTimingFunction::Linear)
        .animation_iteration_count(AnimationIterationCount::Infinite)
        .on(
            Media::ReducedMotion,
            Style::new().animation_duration(ms(4000)),
        );
    Page::new(
        "アップロード",
        Upload { busy: true },
    )
    .child(
        div()
            .css(hstack(rem(1)))
            .child(span().attr("aria-hidden", "true").css(ring).show(busy))
            .child(span().attr("role", "status").bind_text(if_else(
                busy,
                "アップロード中…",
                "完了",
            )))
            .child(
                button(ButtonVariant::Outline, ButtonSize::Sm)
                    .on_click(busy.toggle())
                    .bind_text(if_else(
                        busy,
                        "停止",
                        "開始",
                    )),
            ),
    )
}

動きを減らす設定

Media::ReducedMotion は、動きを減らすよう求める訪問者にだけスタイルを適用します。hypersig_ui のコンポーネントはその場合に色をトランジションなしで切り替えます。

同じ名前の 2 つの Keyframes は、ブラウザにとっては 1 つのルールとして扱われ、スタイルシートで後にある方が使われます。名前が重複していないかは検査されません。