@@ -0,0 +1,285 @@
4+use dodrio::bumpalo::{self, Bump};
5+use dodrio::{Node, Render, Vdom};
6+use std::cell::RefCell;
9+use wasm_bindgen::prelude::*;
10+use wasm_bindgen::JsCast;
12+/// This is the main function that is automatically invoked when the wasm module
14+#[wasm_bindgen(start)]
16+ // Set up the panic hook for debugging when things go wrong.
19+ // Grab the document's `<body>`.
20+ let window = web_sys::window().unwrap_throw();
21+ let document = window.document().unwrap_throw();
22+ let scene = document.get_element_by_id("scene").unwrap_throw();
24+ // Create a new `Moire` render component.
25+ let component = Moire::new();
27+ // Create a virtual DOM and mount it and the `Hello` render component to the
29+ let vdom = Vdom::new(&scene, component);
31+ // Kick off a loop that keeps re-rendering on every animation frame.
32+ let rc: Rc<RefCell<Option<Closure<FnMut()>>>> = Rc::new(RefCell::new(None));
33+ let rc2 = rc.clone();
34+ let window2 = window.clone();
35+ let weak = vdom.weak();
36+ let f = Closure::wrap(Box::new(move || {
37+ weak.schedule_render();
39+ .request_animation_frame(
44+ .unchecked_ref::<js_sys::Function>(),
49+ .request_animation_frame(f.as_ref().unchecked_ref::<js_sys::Function>())
51+ *rc2.borrow_mut() = Some(f);
53+ // Run the virtual DOM forever and don't unmount it.
58+ if #[cfg(feature = "logging")] {
60+ console_error_panic_hook::set_once();
61+ console_log::init_with_level(log::Level::Trace)
62+ .expect_throw("should initialize logging OK");
71+/// A rendering component that renders two overlapping sets of concentric
72+/// circles that move around and form Moiré patterns.
74+/// https://en.wikipedia.org/wiki/Moir%C3%A9_pattern
76+ // How many circles to render in each of our sets. This is controlled by the
82+ /// Construct a new `Moire` rendering component.
84+ /// Defaults to 200 circles.
85+ pub fn new() -> Moire {
89+ /// Callback for "change" events emitted on the `<input>` slider that
90+ /// controls how many circles we render on each frame.
91+ fn on_change(&mut self, event: web_sys::Event) {
92+ // Get the `<input>` element.
96+ .unchecked_into::<web_sys::HtmlInputElement>();
98+ // Parse its new value.
99+ let value = input.value();
100+ let value: u32 = value
102+ .expect_throw("<input type=\"range\"> value should always be an integer");
104+ // And update our circle count!
109+impl Render for Moire {
110+ fn render<'a, 'bump>(&'a self, bump: &'bump Bump) -> Node<'bump>
114+ use dodrio::builder::*;
116+ let elapsed = web_sys::window()
121+ let elapsed = elapsed / 1600.0;
124+ .attr("style", self.moire_style(bump, elapsed))
126+ // The `<input>` that lets users control how many circles to
129+ .attr("id", "circle-count")
130+ .attr("type", "range")
134+ let count = bumpalo::format!(in bump, "{}", self.count);
135+ count.into_bump_str()
137+ .on("change", |root, _vdom, event| {
138+ root.unwrap_mut::<Moire>().on_change(event);
140+ // Note: there is no need to manually schedule a
141+ // re-render here, since we already started a
142+ // `requestAnimationFrame` loop that automatically
143+ // re-renders on every frame in the `run` function.
146+ // And the actual concentric circles that form the Moire patterns.
147+ self.orbiting_objects(bump, elapsed),
148+ self.lemniscate_objects(bump, elapsed),
154+/// Rendering helper methods.
156+ /// Generate the main Moire element's inline CSS styles.
157+ fn moire_style<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64) -> &'bump str
161+ let elapsed = elapsed / 3.0;
162+ let color = colors::get_interpolated_color(|(_, bg)| bg, elapsed % 1.0);
163+ let style = bumpalo::format!(
165+ "background-color: rgb({}, {}, {})",
170+ style.into_bump_str()
173+ /// Generate the orbiting circle objects.
174+ fn orbiting_objects<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64) -> Node<'bump>
178+ const D: f64 = 200.0;
179+ let x = elapsed.sin() * D;
180+ let y = elapsed.cos() * D;
181+ self.moving_object(bump, elapsed, x, y)
184+ /// Generate the lemniscate circle objects that interact with the orbiting
185+ /// circle objects to form Moire patterns.
186+ fn lemniscate_objects<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64) -> Node<'bump>
190+ const A: f64 = 150.0;
191+ let x = elapsed.sin() * A;
192+ let y = elapsed.sin() * elapsed.cos() * A;
193+ self.moving_object(bump, elapsed, x, y)
196+ fn moving_object<'a, 'bump>(
206+ use dodrio::builder::*;
208+ .attr("class", "object")
209+ .attr("style", self.moving_object_style(bump, x, y))
210+ .children([self.circle(bump, elapsed, self.count)])
214+ /// Generate inline CSS styles for a moving object.
215+ fn moving_object_style<'a, 'bump>(&'a self, bump: &'bump Bump, x: f64, y: f64) -> &'bump str
219+ bumpalo::format!(in bump, "left: {}px; top: {}px;", x, y).into_bump_str()
222+ /// Recursively generate `self.count` concentric circles.
223+ fn circle<'a, 'bump>(&'a self, bump: &'bump Bump, elapsed: f64, n: u32) -> Node<'bump>
227+ use dodrio::builder::*;
231+ let mut circle = div(bump)
232+ .attr("class", "circle")
233+ .attr("data-radius", {
234+ let r = bumpalo::format!(in bump, "{}", r);
237+ .attr("style", self.circle_style(bump, elapsed, n, r));
240+ circle = circle.child(self.circle(bump, elapsed, n - 1));
246+ /// Generate inline CSS styles for a circle.
247+ fn circle_style<'a, 'bump>(
257+ let (border, alpha) = self.circle_color(elapsed, n);
258+ let margin = r / 2 + 3;
259+ let style = bumpalo::format!(
261+ "border-color: rgba({}, {}, {}, {});\
262+ margin-left: -{}px;\
275+ style.into_bump_str()
278+ /// Compute the color for a circle.
279+ fn circle_color(&self, elapsed: f64, n: u32) -> (colors::Rgb, f64) {
280+ let elapsed = elapsed / 3.0;
281+ let base = colors::get_interpolated_color(|(fg, _)| fg, elapsed % 1.0);
282+ let lightness = 1.0 - (n as f64) / (self.count as f64);