From here: https://groups.google.com/forum/#!topic/elm-discuss/bg7p-6W1F6c
type alias Item =
{ content : String
, id : Int
}
type AnimState = Static | IsHidden Int | IsEntering Int
type Msg = AddItem Item | DisplayedHidden Int Time | TransitionEnded Int
type Model =
{ items : List Item
, animState : AnimState
}
update msg model =
case msg of
AddItem item ->
{ model
| items = model.items ++ [ item ]
, animState = IsHidden item.id
} ! []
DisplayedHidden itemId _ ->
{ model
| animState = IsEntering itemId
} ! []
TransitionEnded itemId ->
{ model
| animState = Static
} ! []
view model =
ul []
List.map (itemView model) model.items
itemView model item =
let
itemAttributes =
case model.animState of
Static ->
[ class "static" ]
IsHidden itemId ->
if item.id == itemId then
[ class "hidden" ]
else
[ class "static" ]
IsEntering itemId ->
if item.id == itemId then
[ class "entering", onTransitionEnd (TransitionEnded itemId) ]
else
[ class "static" ]
in
li
itemAttributes
[ text item.content ]
onTransitionEnd msg =
Html.Events.on "transitionend" (Json.succeed msg)
subscriptions model =
case model.animState of
IsHidden itemId ->
AnimationFrame.times (DisplayedHidden itemId)
_ ->
Sub.none
In your css, you would have the following classes for the item:
- ".hidden" that does NOT use "display: none", but some other attributes (e.g. opacity, height, or transform: translate) to make the item invisible.
- ".entering", which includes a "transition: xx ..ms" to do the animation from .hidden to .entering
- ".static", which has the styling for after the animation.
Some remarks here:
- you need a subscription for AnimationFrame, to ensure that elm has at least rendered once on screen before the next class is applied.
- the transitionend (at least in Chrome) fires each time one element of the transition has ended, so if your css-transition has two animating elements (e.g. opacity and position), the transitionend will fire as soon as the first transition has ended