Animation
Keyframes are Rust values too. When an element uses keyframes, their rule is written to the page along with the element.
Keyframes
Keyframes::new(name) creates an animation, and at(percent, style) adds a step. Style::keyframes sets an element's animation-name and includes the @keyframes rule in the style, so the page or patch that contains the element also contains the rule. A keyframe writes only the unconditioned declarations of its style, because a keyframe takes no selector or media query.
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"));Result
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",
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,
"Uploading…",
"Done",
)))
.child(
button(ButtonVariant::Outline, ButtonSize::Sm)
.on_click(busy.toggle())
.bind_text(if_else(
busy,
"Stop",
"Start",
)),
),
)
}Reduced motion
Media::ReducedMotion applies a style for a visitor who asks for reduced motion. In that case the components of hypersig_ui change colors without a transition.
Two
Keyframes with the same name are treated as one rule by the browser, and the one later in the stylesheet is used. Duplicate names are not checked.