How do you organise animation state and other "internal" models

Hi!

When working on my latest project I decided to try and learn how to use animations, and specifically I’ve used @mdgriffith’s elm-style-animation package.

The aforementioned package requires saving a state for every animation in your model, and by looking at a few other packages I’ve noticed that this pattern is common. Now, it seems that this introduces two kinds of “models”: The user model and the “display” model (or internal? probably not the best name).

The user model is model that deals with the user perceivable state, for example, the displayed tab, which controls are selected, the colour scheme used etc.

The display model deals with book-keeping for tracking how to correctly render the view, for example, if we want to animate a button press, we need to know how many milliseconds had past since the clicking the button in-order to display the correct state.

How do you organise the two models? Do you keep them together, e.g.

type alias ButtonModel 
    = { isClicked : Bool
      , animation : Animation.State
      }
 
type alias Model
    = { button1 : ButtonModel
      , button2 : ButtonModel
      , textBox : String
      }

or maybe as two separate entities, e.g.

type alias Model
    = { button1Clicked : Bool
      , button2Clicked : Bool
      , textBox : String
      , button1Animation : Animation.State
      , button2Animation : Animation.State
      }

What worked well for you? What feels more clearly organised?

1 Like

Fundamentally the latter seems far worse to me. It will result in more potential for errors (you are now the one having to keep the link between button1Clicked and button1Animation, and the compiler can’t help you with that).

Where at all possible, you want to encode your knowledge about the data into the data structure, and as you show there is a way to do that here, so I would take that.

1 Like

This might help: https://youtu.be/DBVHxkMBfF4

Can you think of any other alternative solutions that might be even better?

The difficulty with putting it in the ButtonModel is that animation tends to drive proliferation of such sub-models in an application, then you start thinking about giving each its own update function and using Cmd.map and before you know it - a lot of nested TEA. I definitely found this when I started going overboard with gratuitous animation.

So the only alternative solutions that I can think of that are better are

  • Avoid gratuitous animation, only do what is really really necessary.
  • Do it in CSS, if its not ridiculously hard to do so.
2 Likes

I don’t have enough experience to suggest anything but the most tentative of suggestions, but I’ve been pondering over the design of my own app, which includes a graphical editor. The underlying problem, as I see it, is that there are two entirely separate states, yet only one model. The first state is the application data, the entities and relationships manipulated by the application’s business logic. The second state is that of the view. As there is only one application model, my thought was to divide it at its root, rather like the first example, into state related purely to the visual representation of objects (eg the shape, size, position, colour etc. of an entity in my graphical editor) and the state of the entity itself (eg the duration of an activity or who it is assigned to). I see, however, that elm-spa provides a page model and a global model, even for a one page app. I’m now thinking along the lines that application data (state) should go into the global model and per page view state into page model. Any thoughts?

I’ve been using flat model structure because it’s easier to work with. The grouping can be done with comments.

type alias Model
    = { -- BUTTON 1
        button1Clicked : Bool
      , button1Animation : Animation.State

      -- BUTTON 2
      , button2Clicked : Bool
      , button2Animation : Animation.State

      -- TEXT
      , textBox : String
      }

After I realized that a module was too big to maintain (3k LoC) and I split the animation state into separate module. In my case such module is a higher level abstraction on top of the animation package. Internally it’s a dict with animation states.

type Model msg
    = Model (Dict String (Animation msg))

type Animation msg
    = Animation Name Type State (Messenger.State (Msg msg))

type Name
    = Name String

type Type
    = Fade
    | SlideDown

type State
    = Idle
    | Animating Direction

type Direction
    = In
    | Out

So overall it depends on what your app architecture is. Start with flat model and refactor when you need to abstract it more.

Hi @Luftzig, I’ve recently created a new way of doing animations:

https://package.elm-lang.org/packages/z5h/timeline/latest/

The idea is to provide helpers that change your

main : Program () Model Msg

to

main : Program () (Timeline Model) (Timeline.Msg Msg)

with very little effort on your part.

The net effect, is that in your view function, you now have access to a “timeline” of your app, from which you can easily create custom animations.

An example ellie is here: https://ellie-app.com/82p3nvbZfMCa1

I’m working on some optimizations as well, that will allow you to use lazy and animate with CSS.

4 Likes

Timeline looks really cool, thanks for sharing it!

1 Like

Hi Mark!

This looks very cool! I’ll try to check it out once I have the time for that!

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.