Modernizing animation example
This commit is contained in:
parent
bbc3e3a68e
commit
ca4e9651ad
|
@ -14,15 +14,19 @@ lto = true
|
|||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1.5"
|
||||
dominator = "0.5.0"
|
||||
wasm-bindgen = "0.2.45"
|
||||
futures-signals = "0.3.0"
|
||||
futures = "0.3.0"
|
||||
console_error_panic_hook = "0.1.6"
|
||||
dominator = "0.5.18"
|
||||
wasm-bindgen = "0.2.74"
|
||||
futures = "0.3.15"
|
||||
futures-signals = "0.3.20"
|
||||
gloo-timers = { version = "0.2.1", features = ["futures"] }
|
||||
once_cell = "1.7.2"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.22"
|
||||
version = "0.3.51"
|
||||
features = [
|
||||
"console",
|
||||
]
|
||||
|
|
|
@ -8,10 +8,11 @@
|
|||
"start": "rimraf dist/js && rollup --config --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@wasm-tool/rollup-plugin-rust": "^1.0.0",
|
||||
"@wasm-tool/rollup-plugin-rust": "^1.0.6",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^1.31.0",
|
||||
"rollup-plugin-livereload": "^1.2.0",
|
||||
"rollup-plugin-serve": "^1.0.1"
|
||||
"rollup": "^2.50.2",
|
||||
"rollup-plugin-livereload": "^2.0.0",
|
||||
"rollup-plugin-serve": "^1.1.0",
|
||||
"rollup-plugin-terser": "^7.0.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import rust from "@wasm-tool/rollup-plugin-rust";
|
||||
import serve from "rollup-plugin-serve";
|
||||
import livereload from "rollup-plugin-livereload";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
|
||||
const is_watch = !!process.env.ROLLUP_WATCH;
|
||||
|
||||
|
@ -16,7 +17,6 @@ export default {
|
|||
plugins: [
|
||||
rust({
|
||||
serverPath: "js/",
|
||||
debug: false,
|
||||
}),
|
||||
|
||||
is_watch && serve({
|
||||
|
@ -25,5 +25,7 @@ export default {
|
|||
}),
|
||||
|
||||
is_watch && livereload("dist"),
|
||||
|
||||
!is_watch && terser(),
|
||||
],
|
||||
};
|
||||
|
|
|
@ -1,112 +1,180 @@
|
|||
use wasm_bindgen::prelude::*;
|
||||
use std::rc::Rc;
|
||||
use futures::future::ready;
|
||||
use std::sync::Arc;
|
||||
use once_cell::sync::Lazy;
|
||||
use futures::stream::StreamExt;
|
||||
use futures_signals::map_ref;
|
||||
use futures_signals::signal::SignalExt;
|
||||
use futures_signals::signal::{Mutable, SignalExt};
|
||||
use futures_signals::signal_vec::MutableVec;
|
||||
use dominator::traits::*;
|
||||
use dominator::{Dom, html, clone, events};
|
||||
use gloo_timers::future::IntervalStream;
|
||||
use dominator::{Dom, html, clone, events, class};
|
||||
use dominator::traits::AnimatedSignalVec;
|
||||
use dominator::animation::{easing, Percentage, MutableAnimation, AnimatedMapBroadcaster};
|
||||
|
||||
|
||||
fn make_animated_box(value: u32, broadcaster: AnimatedMapBroadcaster) -> Dom {
|
||||
let animation = Rc::new(MutableAnimation::new(3000.0));
|
||||
struct Bar {
|
||||
color: u32,
|
||||
wave_animation: MutableAnimation,
|
||||
hover_animation: MutableAnimation,
|
||||
}
|
||||
|
||||
let hover_animation = Rc::new(MutableAnimation::new(300.0));
|
||||
|
||||
let low: f64 = value as f64;
|
||||
let high: f64 = (value + 60) as f64;
|
||||
|
||||
html!("div", {
|
||||
.future(animation.signal().for_each(clone!(animation => move |x| {
|
||||
let x: f64 = x.into_f64();
|
||||
|
||||
if x == 0.0 {
|
||||
animation.animate_to(Percentage::new(1.0));
|
||||
|
||||
} else if x == 1.0 {
|
||||
animation.animate_to(Percentage::new(0.0));
|
||||
}
|
||||
|
||||
ready(())
|
||||
})))
|
||||
|
||||
.event(clone!(hover_animation => move |_: events::MouseEnter| {
|
||||
hover_animation.animate_to(Percentage::new(1.0));
|
||||
}))
|
||||
|
||||
.event(clone!(hover_animation => move |_: events::MouseLeave| {
|
||||
hover_animation.animate_to(Percentage::new(0.0));
|
||||
}))
|
||||
|
||||
.style("border-radius", "10px")
|
||||
|
||||
.style_signal("width", animation.signal()
|
||||
.map(|t| easing::in_out(t, easing::cubic))
|
||||
.map(|t| Some(format!("{}px", t.range_inclusive(167.0, 500.0)))))
|
||||
|
||||
.style("position", "relative")
|
||||
|
||||
.style_signal("margin-left", animation.signal()
|
||||
.map(|t| t.invert())
|
||||
.map(|t| easing::in_out(t, easing::cubic))
|
||||
.map(|t| Some(format!("{}px", t.range_inclusive(20.0, 0.0)))))
|
||||
|
||||
.style_signal("left", broadcaster.signal()
|
||||
.map(|t| easing::in_out(t, easing::cubic))
|
||||
.map(|t| Some(format!("{}px", t.range_inclusive(100.0, 0.0)))))
|
||||
|
||||
.style_signal("height", map_ref! {
|
||||
let animation = broadcaster.signal().map(|t| easing::in_out(t, easing::cubic)),
|
||||
let hover = hover_animation.signal().map(|t| easing::out(t, easing::cubic)) =>
|
||||
Some(format!("{}px", animation.range_inclusive(0.0, hover.range_inclusive(5.0, 15.0))))
|
||||
impl Bar {
|
||||
fn new(color: u32) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
color,
|
||||
wave_animation: MutableAnimation::new(3000.0),
|
||||
hover_animation: MutableAnimation::new(300.0),
|
||||
})
|
||||
}
|
||||
|
||||
.style_signal("background-color", animation.signal()
|
||||
.map(|t| easing::in_out(t, easing::cubic))
|
||||
.map(move |t| Some(format!("hsl({}, {}%, {}%)",
|
||||
t.range_inclusive(low, high),
|
||||
t.range_inclusive(50.0, 100.0),
|
||||
t.range_inclusive(50.0, 100.0)))))
|
||||
fn render(bar: Arc<Bar>, insert_animation: AnimatedMapBroadcaster) -> Dom {
|
||||
static CLASS: Lazy<String> = Lazy::new(|| class! {
|
||||
.style("border-radius", "10px")
|
||||
.style("position", "relative")
|
||||
.style("border-style", "solid")
|
||||
.style("border-width", "5px")
|
||||
});
|
||||
|
||||
.style("border-style", "solid")
|
||||
let low: f64 = bar.color as f64;
|
||||
let high: f64 = (bar.color + 60) as f64;
|
||||
|
||||
.style_signal("border-width", broadcaster.signal()
|
||||
.map(|t| easing::in_out(t, easing::cubic))
|
||||
.map(|t| Some(format!("{}px", t.range_inclusive(0.0, 5.0)))))
|
||||
|
||||
.style_signal("border-color", animation.signal()
|
||||
.map(|t| easing::in_out(t, easing::cubic))
|
||||
.map(move |t| Some(format!("hsl({}, {}%, {}%)",
|
||||
t.range_inclusive(high, low),
|
||||
t.range_inclusive(100.0, 50.0),
|
||||
t.range_inclusive(100.0, 50.0)))))
|
||||
})
|
||||
}
|
||||
html!("div", {
|
||||
.class(&*CLASS)
|
||||
|
||||
|
||||
struct State {
|
||||
boxes: MutableVec<u32>,
|
||||
}
|
||||
.future(bar.wave_animation.signal().for_each(clone!(bar => move |t| {
|
||||
let t: f64 = t.into_f64();
|
||||
|
||||
impl Drop for State {
|
||||
fn drop(&mut self) {
|
||||
web_sys::console::log_1(&JsValue::from("Dropping"));
|
||||
// Automatically cycles back and forth between 0 and 1
|
||||
// This creates a wave effect
|
||||
if t == 0.0 {
|
||||
bar.wave_animation.animate_to(Percentage::new(1.0));
|
||||
|
||||
} else if t == 1.0 {
|
||||
bar.wave_animation.animate_to(Percentage::new(0.0));
|
||||
}
|
||||
|
||||
async {}
|
||||
})))
|
||||
|
||||
|
||||
// Animation when hovering over the Bar
|
||||
.event(clone!(bar => move |_: events::MouseEnter| {
|
||||
bar.hover_animation.animate_to(Percentage::new(1.0));
|
||||
}))
|
||||
|
||||
.event(clone!(bar => move |_: events::MouseLeave| {
|
||||
bar.hover_animation.animate_to(Percentage::new(0.0));
|
||||
}))
|
||||
|
||||
|
||||
// These will animate when the Bar is inserted/removed
|
||||
.style_signal("left", insert_animation.signal()
|
||||
.map(|t| t.none_if(1.0).map(|t| easing::in_out(t, easing::cubic)))
|
||||
.map(|t| t.map(|t| format!("{}px", t.range_inclusive(100.0, 0.0)))))
|
||||
|
||||
.style_signal("height", map_ref! {
|
||||
let insert = insert_animation.signal().map(|t| easing::in_out(t, easing::cubic)),
|
||||
|
||||
let hover = bar.hover_animation.signal().map(|t| easing::out(t, easing::cubic)) =>
|
||||
|
||||
// Animate the height between 5px and 15px when hovering
|
||||
// But if the Bar is being inserted/removed then it will interpolate to 0px
|
||||
Some(format!("{}px", insert.range_inclusive(0.0, hover.range_inclusive(5.0, 15.0))))
|
||||
})
|
||||
|
||||
.style_signal("border-width", insert_animation.signal()
|
||||
.map(|t| t.none_if(1.0).map(|t| easing::in_out(t, easing::cubic)))
|
||||
.map(|t| t.map(|t| format!("{}px", t.range_inclusive(0.0, 5.0)))))
|
||||
|
||||
|
||||
// These will animate in a continuous wave-like pattern
|
||||
.style_signal("width", bar.wave_animation.signal()
|
||||
.map(|t| easing::in_out(t, easing::cubic))
|
||||
.map(|t| Some(format!("{}px", t.range_inclusive(167.0, 500.0)))))
|
||||
|
||||
.style_signal("margin-left", bar.wave_animation.signal()
|
||||
.map(|t| easing::in_out(t, easing::cubic))
|
||||
.map(|t| Some(format!("{}px", t.range_inclusive(0.0, 20.0)))))
|
||||
|
||||
.style_signal("background-color", bar.wave_animation.signal()
|
||||
.map(|t| easing::in_out(t, easing::cubic))
|
||||
.map(move |t|
|
||||
Some(format!("hsl({}, {}%, {}%)",
|
||||
t.range_inclusive(low, high),
|
||||
t.range_inclusive(50.0, 100.0),
|
||||
t.range_inclusive(50.0, 100.0)))))
|
||||
|
||||
.style_signal("border-color", bar.wave_animation.signal()
|
||||
.map(|t| easing::in_out(t, easing::cubic))
|
||||
.map(move |t|
|
||||
Some(format!("hsl({}, {}%, {}%)",
|
||||
t.range_inclusive(high, low),
|
||||
t.range_inclusive(100.0, 50.0),
|
||||
t.range_inclusive(100.0, 50.0)))))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO move this into gloo
|
||||
fn set_interval<F>(ms: i32, f: F) where F: FnMut() + 'static {
|
||||
let f = wasm_bindgen::closure::Closure::wrap(Box::new(f) as Box<dyn FnMut()>);
|
||||
struct App {
|
||||
current_color: Mutable<u32>,
|
||||
bars: MutableVec<Arc<Bar>>,
|
||||
}
|
||||
|
||||
web_sys::window()
|
||||
.unwrap_throw()
|
||||
.set_interval_with_callback_and_timeout_and_arguments_0(wasm_bindgen::JsCast::unchecked_ref(f.as_ref()), ms)
|
||||
.unwrap_throw();
|
||||
impl App {
|
||||
fn new() -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
current_color: Mutable::new(0),
|
||||
bars: MutableVec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
// TODO cleanup
|
||||
f.forget()
|
||||
fn new_color(&self) -> u32 {
|
||||
// Cycles through the color hue spectrum
|
||||
self.current_color.replace_with(|x| (*x + 10) % 360)
|
||||
}
|
||||
|
||||
fn new_bar(&self) {
|
||||
let mut lock = self.bars.lock_mut();
|
||||
|
||||
// Limits the number of bars to 40
|
||||
if lock.len() >= 40 {
|
||||
lock.remove(0);
|
||||
}
|
||||
|
||||
lock.push_cloned(Bar::new(self.new_color()));
|
||||
}
|
||||
|
||||
fn render_bars(app: Arc<Self>) -> Dom {
|
||||
html!("div", {
|
||||
.children_signal_vec(app.bars.signal_vec_cloned()
|
||||
// Animates the Bar for 2000ms when inserting/removing
|
||||
.animated_map(2000.0, |bar, animation| Bar::render(bar, animation)))
|
||||
})
|
||||
}
|
||||
|
||||
fn render(app: Arc<Self>) -> Dom {
|
||||
static CLASS: Lazy<String> = Lazy::new(|| class! {
|
||||
.style("display", "flex")
|
||||
.style("flex-direction", "row")
|
||||
});
|
||||
|
||||
html!("div", {
|
||||
.class(&*CLASS)
|
||||
|
||||
// Inserts a new colored bar every 500ms
|
||||
.future(IntervalStream::new(500).for_each(clone!(app => move |_| {
|
||||
app.new_bar();
|
||||
async {}
|
||||
})))
|
||||
|
||||
.children(&mut [
|
||||
// Renders the bars twice, both columns will always be perfectly in sync
|
||||
Self::render_bars(app.clone()),
|
||||
Self::render_bars(app),
|
||||
])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -115,61 +183,8 @@ pub fn main_js() -> Result<(), JsValue> {
|
|||
#[cfg(debug_assertions)]
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
|
||||
let state = Rc::new(State {
|
||||
boxes: MutableVec::new_with_values(vec![0]),
|
||||
});
|
||||
|
||||
let mut color = 10;
|
||||
|
||||
let _timer_id = set_interval(500, clone!(state => move || {
|
||||
let mut lock = state.boxes.lock_mut();
|
||||
|
||||
if lock.len() >= 40 {
|
||||
lock.remove(0);
|
||||
}
|
||||
|
||||
lock.push(color % 360);
|
||||
color += 10;
|
||||
}));
|
||||
|
||||
/*dominator::append_dom(&body,
|
||||
html!("button", {
|
||||
.event(clone!(state => move |_: ClickEvent| {
|
||||
js! { @(no_return)
|
||||
clearInterval(@{&timer_id});
|
||||
}
|
||||
|
||||
state.boxes.clear();
|
||||
}))
|
||||
|
||||
.text("Clear all animations")
|
||||
})
|
||||
);*/
|
||||
|
||||
for _ in 0..1 {
|
||||
dominator::append_dom(&dominator::body(),
|
||||
html!("div", {
|
||||
.style("display", "flex")
|
||||
|
||||
.children(&mut [
|
||||
html!("div", {
|
||||
.children_signal_vec(state.boxes.signal_vec()
|
||||
.animated_map(2000.0, |value, t| {
|
||||
make_animated_box(value, t)
|
||||
}))
|
||||
}),
|
||||
|
||||
html!("div", {
|
||||
.children_signal_vec(state.boxes.signal_vec()
|
||||
.animated_map(2000.0, |value, t| {
|
||||
make_animated_box(value, t)
|
||||
}))
|
||||
}),
|
||||
])
|
||||
})
|
||||
);
|
||||
}
|
||||
let app = App::new();
|
||||
dominator::append_dom(&dominator::body(), App::render(app));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue