Last active
May 31, 2018 12:35
-
-
Save busypeoples/7cc7acd575c1d8cbb143221e8bf27cfe to your computer and use it in GitHub Desktop.
Reason-Animation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open Sliders; | |
ReactDOMRe.renderToElementWithId(<Sliders />, "root"); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Animation library heavily influenced by https://github.com/mgold/elm-animation | |
Enables to decouple the actual calculations for an animation from | |
actually running the animation. | |
Enables to define animation between 2 values. Define any From/To values, this | |
library will keep track of the calculation depending on the time. | |
This enables us to build very low level blocks, which can further be refined | |
using easing functionalities, leaving the actual rendering to user land. | |
*/ | |
type durationOrSpeed = | |
| Duration(int) | |
| Speed(float); | |
type animation = { | |
start: int, | |
delay: int, | |
dos: durationOrSpeed, | |
ramp: option(float), | |
ease: float => float, | |
from: float, | |
to_: float, | |
}; | |
let defaultDuration = Duration(750); | |
let defaultEase = ease => (1.0 -. Js.Math.cos(Js.Math._PI *. ease)) /. 2.0; | |
let animation = | |
( | |
~start=0, | |
~delay=0, | |
~dos=defaultDuration, | |
~ramp=None, | |
~ease=defaultEase, | |
~from=0.0, | |
~to_=1.0, | |
(), | |
) => { | |
start, | |
delay, | |
dos, | |
ramp, | |
ease, | |
from, | |
to_, | |
}; | |
let dur = (durationOrSpeed, from, to_) => | |
switch (durationOrSpeed) { | |
| Duration(t) => t | |
| Speed(s) => int_of_float(Js.Math.abs_float(to_ -. from) /. s) | |
}; | |
let spd = (durationOrSpeed, from, to_) => | |
switch (durationOrSpeed) { | |
| Duration(t) => Js.Math.abs_float(to_ -. from) /. float_of_int(t) | |
| Speed(s) => s | |
}; | |
let createAnimation = t => animation(~start=t, ()); | |
let static = x => animation(~from=x, ~to_=x, ()); | |
let clamp = (a, b, x) => | |
if (x < a) { | |
a; | |
} else if (b <= x) { | |
b; | |
} else { | |
x; | |
}; | |
let animate = (t, a) => { | |
let {start, delay, dos, ramp, from, to_, ease} = a; | |
let duration = dur(dos, from, to_); | |
let fr = | |
clamp( | |
0.0, | |
1.0, | |
float_of_int(t - start - delay) /. float_of_int(duration), | |
); | |
let eased = ease(fr); | |
let correction = | |
switch (ramp) { | |
| Some(vel) => | |
let eased_ = defaultEase(fr); | |
let from_ = vel *. float_of_int(t - start); | |
from_ -. from_ *. eased_; | |
| None => 0.0 | |
}; | |
from +. (to_ -. from) *. eased +. correction; | |
}; | |
let timeElapsed = (t, a) => Js.Math.max_int(t - (a.start + a.delay), 0); | |
let timeRemaining = (t, a) => { | |
let {dos, from, to_} = a; | |
let duration = dur(dos, from, to_); | |
Js.Math.max_int(a.start + a.delay + duration - t, 0); | |
}; | |
let velocity = (t, a) => { | |
let backDiff = animate(t - 10, a); | |
let forwDiff = animate(t + 10, a); | |
(forwDiff -. backDiff) /. 20.0; | |
}; | |
let getDuration = a => { | |
let {dos, from, to_} = a; | |
dur(dos, from, to_); | |
}; | |
let getSpeed = a => { | |
let {dos, from, to_} = a; | |
spd(dos, from, to_); | |
}; | |
let isStatic = ({from, to_}) => from === to_; | |
let isScheduled = (t, a) => t <= a.start + a.delay && ! isStatic(a); | |
let isRunning = (t, a) => { | |
let {start, delay, dos, from, to_} = a; | |
let duration = dur(dos, from, to_); | |
t > start + delay && t < start + delay + duration && ! isStatic(a); | |
}; | |
let isDone = (t, a) => { | |
let {start, delay, dos, from, to_} = a; | |
let duration = dur(dos, from, to_); | |
isStatic(a) || t >= start + delay + duration; | |
}; | |
let equals = (a, b) => | |
a.start | |
+ a.delay == b.start | |
+ b.delay | |
&& a.from == b.from | |
&& a.to_ == b.to_ | |
&& a.ramp == b.ramp | |
&& ( | |
a.dos === b.dos | |
|| | |
0.001 >= float_of_int( | |
dur(a.dos, a.from, a.to_) - dur(b.dos, b.from, b.to_), | |
) | |
) | |
&& List.filter(t => a.ease(t) !== b.ease(t), [0.1, 0.3, 0.7, 0.9]) | |
|> (l => List.length(l) == 0); | |
/* undo : (Time, Animation) => Animation */ | |
let undo = (t, a) => { | |
...a, | |
from: a.to_, | |
to_: a.from, | |
start: t, | |
delay: - timeRemaining(t, a), | |
ramp: None, | |
ease: t => 1.0 -. a.ease(1.0 -. t), | |
}; | |
let retarget = (t, newTo, a) => | |
if (newTo === a.to_) { | |
a; | |
} else if (isStatic(a)) { | |
{...a, start: t, to_: newTo, ramp: None}; | |
} else if (isScheduled(t, a)) { | |
{...a, to_: newTo, ramp: None}; | |
} else if (isDone(t, a)) { | |
{...a, start: t, delay: 0, from: a.to_, to_: newTo, ramp: None}; | |
} else { | |
let vel = velocity(t, a); | |
let pos = animate(t, a); | |
let newDos = | |
switch (a.dos) { | |
| Speed(_s) => a.dos | |
| Duration(_d) => Speed(spd(a.dos, a.from, a.to_)) | |
}; | |
{ | |
start: t, | |
delay: 0, | |
dos: newDos, | |
ramp: Some(vel), | |
ease: a.ease, | |
from: pos, | |
to_: newTo, | |
}; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open ReasonAnimation; | |
type time = int; | |
type state = { | |
clock: time, | |
a1: animation, | |
a2: animation, | |
a3: animation, | |
initial: bool, | |
}; | |
let stay = static(0.0); | |
type action = | |
| Tick(time) | |
| Click; | |
let slideLen = 500.0; | |
[@bs.val] | |
external requestAnimationFrame : (int => unit) => unit = | |
"requestAnimationFrame"; | |
let component = ReasonReact.reducerComponent("Sliders"); | |
let make = _children => { | |
...component, | |
initialState: () => {clock: 0, a1: stay, a2: stay, a3: stay, initial: true}, | |
didMount: self => self.send(Tick(0)), | |
reducer: (action, state) => | |
switch (action) { | |
| Tick(s) => | |
ReasonReact.UpdateWithSideEffects( | |
{...state, clock: s}, | |
(self => requestAnimationFrame(t => self.send(Tick(t)))), | |
) | |
| Click => | |
let {initial, clock, a1, a2, a3} = state; | |
if (initial) { | |
let a = | |
animation( | |
~start=clock, | |
~from=0.0, | |
~to_=slideLen, | |
~dos=Duration(1200), | |
(), | |
); | |
ReasonReact.Update({...state, a1: a, a2: a, a3: a, initial: false}); | |
} else { | |
let t = clock; | |
let dur = Duration(750); | |
ReasonReact.Update({ | |
...state, | |
a1: animation(~start=t, ~from=a1.to_, ~to_=a1.from, ~dos=dur, ()), | |
a2: | |
animation( | |
~start=t, | |
~from=animate(t, a2), | |
~to_=a1.from, | |
~dos=dur, | |
(), | |
), | |
a3: retarget(t, a1.from, a3) |> (a => {...a, dos: dur}), | |
}); | |
}; | |
}, | |
render: ({state, send}) => { | |
let w1 = animate(state.clock, state.a1); | |
let w2 = animate(state.clock, state.a2); | |
let w3 = animate(state.clock, state.a3); | |
let slider = w => | |
<div | |
style=( | |
ReactDOMRe.Style.make( | |
~padding="0px", | |
~margin="0px", | |
~pointerEvents="none", | |
~width="550px", | |
~height="50px", | |
(), | |
) | |
)> | |
<div | |
style=( | |
ReactDOMRe.Style.make( | |
~padding="0px", | |
~margin="0px", | |
~width="550px", | |
~height="50px", | |
~backgroundColor="rgb(238, 238, 236)", | |
~position="absolute", | |
(), | |
) | |
) | |
/> | |
<div | |
style=( | |
ReactDOMRe.Style.make( | |
~padding="0px", | |
~margin="0px", | |
~width=string_of_float(Js.Math.round(w) +. 50.0) ++ "px", | |
~height="50px", | |
~position="absolute", | |
(), | |
) | |
)> | |
<div | |
style=( | |
ReactDOMRe.Style.make( | |
~padding="0px", | |
~margin="0px", | |
~width=string_of_float(Js.Math.round(w)) ++ "px", | |
~height="50px", | |
~float="left", | |
(), | |
) | |
) | |
/> | |
<div | |
style=( | |
ReactDOMRe.Style.make( | |
~padding="0px", | |
~margin="0px", | |
~width="50px", | |
~height="50px", | |
~float="left", | |
~backgroundColor="rgb(52, 101, 164)", | |
(), | |
) | |
) | |
/> | |
</div> | |
</div>; | |
<div> | |
<h3> (ReasonReact.string("elm-animation examples in ReasonML")) </h3> | |
<div> | |
<a | |
href="https://github.com/mgold/elm-animation/blob/master/examples/sliders.elm"> | |
(ReasonReact.string("Find the original example here")) | |
</a> | |
</div> | |
<div> | |
( | |
ReasonReact.string( | |
"This is a demo of three different approaches to interrupted animation.\n Click the mouse rapidly.", | |
) | |
) | |
</div> | |
<div onClick=(_e => send(Click))> | |
( | |
ReasonReact.string( | |
"The first slider is very naive. When interrupted, it pretends the previous animation has already completed, and jumps to the other side only to return. Astoundingly, this is how CSS transitions still work.", | |
) | |
) | |
(slider(w1)) | |
</div> | |
<div onClick=(_e => send(Click))> | |
( | |
ReasonReact.string( | |
"This slider will undo the current animation, instantly reversing its direction.", | |
) | |
) | |
(slider(w2)) | |
</div> | |
<div onClick=(_e => send(Click))> | |
( | |
ReasonReact.string( | |
"This slider will smoothly decelerate and reverse.", | |
) | |
) | |
(slider(w3)) | |
</div> | |
<div> | |
( | |
ReasonReact.string( | |
"Notice that all sliders reach their destination at the same time. The first slider is discontinuous is position; the second slider is discontinuous in velocity; the third slider is smooth.", | |
) | |
) | |
</div> | |
</div>; | |
}, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment