Here's how to keep a timeline in your model and update it:
import Animator
import Animator.Timeline as Timeline
import Browser.Events
import Time
type alias Model =
{ visible : Timeline.Timeline Bool }
init : Model
init =
{ visible = Timeline.init False }
type Msg
= Tick Time.Posix
| Show
subscriptions : Model -> Sub Msg
subscriptions model =
if Timeline.isRunning model.visible then
Browser.Events.onAnimationFrame Tick
else
Sub.none
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Tick now ->
( { model | visible = Timeline.update now model.visible }
, Cmd.none
)
Show ->
( { model | visible = Timeline.to (Animator.ms 300) True model.visible }
, Cmd.none
)Create a timeline with an initial state.
So, if you previously had a Bool in your model:
type alias Model = { checked : Bool }
-- created via
{ checked = False }You could replace that with a Timeline.Timeline Bool:
type alias Model = { checked : Timeline.Timeline Bool }
-- created via
{ checked = Timeline.init False }Now that we have a Timeline set up, we likely want to set a new value.
In order to do that we need to specify both —
Duration for how long this transition should take.Interrupt the existing schedule on the next update.
Call with the timestamp from Browser.Events.onAnimationFrame while
isRunning, or from an existing game loop. Check arrivedAt before updating if
you need to detect arrivals during this tick.
Use this to control your animation-frame subscription. CSS resting loops can
continue in the browser even when this returns False.
True when scheduling requests are waiting for the next update. Becomes
False once they are scheduled, even if the animation is still running.
Use isRunning for animation-frame subscriptions.
In some more advanced cases you might want to define a series of states to animate through instead of just going to one directly.
Timeline.interrupt
[ Timeline.wait (Animator.ms 300)
-- after waiting 300 milliseconds,
-- start transitioning to a new state, Griffyndor
-- Take 1 whole second to make the transition
, Timeline.transitionTo (Animator.ms 1000) Griffyndor
-- Once we've arrived at Griffyndor,
-- immediately start transitioning to Slytherin
-- and take half a second to make the transition
, Timeline.transitionTo (Animator.ms 500) Slytherin
]
timelineIf several interruptions are requested before the next update, the latest
nonempty schedule wins.
An initial wait lets existing motion continue until the replacement begins.
Wait until the current timeline is finished and then continue with these new steps.
Pending steps are scheduled on the next update.
A list containing only waits has no destination and does not create a schedule.
Scale durations when pending steps are scheduled by the next update.
0.5 -> Animations take half as much time
1.0 -> normal
2.0 -> Animations take twice as longNote - 0.1 is the lowest number allowed, and 5 is the highest.
Set this on the model's timeline before calling update. It does not retime
events that have already been scheduled, so applying it only in a view does not
change their playback speed.
Delay the events of a timeline.
This is generally used in your view function to add a bit of variety when animating multiple elements.
Animator.Value.float (Animator.Timeline.delay (Animator.ms 200) timeline) <|
\state ->
if state then
Animator.Value.to 0
else
Animator.Value.to 1Delays add together, negative additions are ignored, and the total is capped at 5 seconds.
If you need a longer delay, it's likely you want to create a separate timeline.
A duration shared with Animator.Duration. Construct one with Animator.ms.
You might be wondering, 'How do we get our value "out" of a Timeline?'
Well, we can ask the Timeline all sorts of questions.
Get the current state of the timeline.
This value will switch to a new value when a transition begins.
If you had a timeline that went from A to B to C, here's what current would be at various points on the timeline.
A---------B---------C
^ ^ ^ ^
current: B B C CNote — If you want to detect the moment when you arrive at a new state, try using arrivedAt
Get the previous state on this timeline.
As you'll see in the Loading example, it means we can use previous to refer to data that we've already "deleted" or set to Nothing.
How cool!
A---------B---------C
^ ^ ^
previous: A A BCheck to see if a state is upcoming on a timeline.
Note — This can be used to ensure a set of states can only be queued if they aren't already running.
Note 2 — This only checks if an event is in the future, but does not check the value you're currently at. You might need to use arrived as well if you also care about the current state.
Subtly different than current, this will provide the new state as soon as the transition has finished.
A---------B---------C
^ ^ ^ ^
arrived: A B B CSometimes we want to know when we've arrived at a state so we can trigger some other work.
Use arrivedAt before updating the timeline to detect arrivals after its current
time and at or before the new tick. An arrival is not reported again on the next
tick. Destinations canceled by an interruption are not reported.
Tick time ->
if Animator.Timeline.arrivedAt MyState time model.timeline then
--...do something specialThe proportion (number between 0 and 1) of progress between the last state and the new one.
Once we arrive at a new state, this value will be 1 until we start another transition.
A timeline of
statevalues.Behind the scenes this is roughly a list of states and the times that they should occur!