mdgriffith / elm-animator

A timeline-based animation engine for Elm
https://package.elm-lang.org/packages/mdgriffith/elm-animator/latest/
BSD 3-Clause "New" or "Revised" License
132 stars 15 forks source link

Animating elements in a dynamic model #18

Closed Evelios closed 3 years ago

Evelios commented 4 years ago

I'm trying to create elements that have load in animations when an elements is added to a model. For example when a user or the server sends me a new element, I would like to have it fade into the screen. In all the examples I have seen, the number of elements has been known up front so it can be set into the animator as such.

For example I followed the tutorial Korban - Using Elm Animator with elm-ui which is using a Timeline (Dict Id State) as a parallel data structure to the actual model elements. Firstly, I'm having doubts if this was the proper way to store this data as you must make sure that you maintain two parallel data structures. However, if I follow this to try to implement a dynamic data structure (add new elements to my page) I need to be able to update both my elements and the Timeline states. However, this module does not provide a update : (state -> state) -> Timeline state -> Timeline state type function for me to be able to add new states to my timeline.

Proposed Solution

Accompanying ellie of a testable example for this solution

Now, I'll prefice this with the fact that I haven't looked into all the implecations of this solution. I don't know if it will mess up any of the events or their scheduling with this minimalist solution.

-- Internal/Timeline.elm
update : (event -> event) -> Timeline event -> Timeline event
update map (Timeline details) =
    { details | initial : map event }

Workaround

Here is my accompanying ellie for a working example of my workaround

The way that I have been able to get around this is to store each of the timelines with my objects. This seems like a better approach for data modeling but provides scaling problems for the animator and subscriptions.

I also feel like I'm violating this piece of advice because I am no longer using a single animator. I would need one for each of these types of components.

Note — You likely only need one animator for a given project.

Here is the snippets of the important parts of the code in my working example.

type alias Model = { items : List Item }

type alias Item =  { text : String, timeline : Timeline State }

update msg model =
    case msg of
        Tick newTime ->
            ({ model | items = List.map 
                   (\item ->
                       Animator.update newTime animator item
                   )
                   model.items
             }
            , Cmd.none
            )

animator : Animator Item
animator =
    Animator.animator
        |> Animator.watching .timeline
            (\newTimeline item ->
                { item | timeline = newTimeline }

subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.batch
        (List.map
            (\item ->
                Animator.toSubscription Tick item animator
            )
            model.items
        )
dbj commented 3 years ago

@Evelios I've been able to get this to work without multiple animators and without using a Dict. Like you, I have gone with embedding the timeline within each item. See my working example Ellie.