transitions
unlock client-side superpowers
client-side rendering with elm
When you click a link on this app, Elm doesn't refetch the entire page back from the server. Instead, it renders only the components that we want to change, and updates the URL for you.
This enables us to use page transitions to improve user experience.
how does it work?
Each elm-spa project looks for a file called src/Transitions.elm
to describe
two things:
How the entire app should enter the screen.
How pages should exit/enter the screen.
Here's the default when you create a project with elm-spa init:
module Transitions exposing (transitions)
import Generated.Docs.Pages as Docs
import Spa.Transition as Transition
import Utils.Spa as Spa
transitions : Spa.Transitions msg
transitions =
{ layout = Transition.fadeElmUi 300
, page = Transition.fadeElmUi 300
, pages = []
}
When you refresh the page, the whole app will use the layout
option
and fades it in over 300 milliseconds.
When you click an internal link, pages will fade out and fade in over 300 milliseconds.
disabling transitions
You can disable transitions with Transition.none
:
module Transitions exposing (transitions)
import Generated.Docs.Pages as Docs
import Spa.Transition as Transition
import Utils.Spa as Spa
transitions : Spa.Transitions msg
transitions =
{ layout = Transition.none
, page = Transition.none
, pages = []
}
custom transitions
Fading isn't your thing? Cool! You can use Transition.custom
to define your own
transitions.
Just provide three properties:
duration
- how long the animation lastsinvisible
- what the page looks like when invisiblevisible
- what the page looks like when visible
Here's an example with Elm UI:
import Element exposing (..)
import Ui
import Spa.Transition as Transition
import Utils.Spa as Spa
scaleTransition :
Int
-> Spa.Transition (Element msg)
scaleTransition duration =
Transition.custom
{ duration = duration
, invisible =
\page ->
el
[ scale 0, Ui.transition duration ]
page
, visible =
\page ->
el
[ scale 1, Ui.transition duration ]
page
}
And here's an example with HTML and CSS:
import Html exposing (..)
import Html.Attributes as Attr
import Spa.Transition as Transition
import Utils.Spa as Spa
scaleTransition :
Int
-> Spa.Transition (Html msg)
scaleTransition duration =
Transition.custom
{ duration = duration
, invisible =
\page ->
div [ Attr.style "transform" "scale(0)"
, Attr.style "transition" ("transform " ++ duration ++ "ms")
]
[ page ]
, visible =
\page ->
div [ Attr.style "transform" "scale(1)"
, Attr.style "transition" ("transform " ++ duration ++ "ms")
]
[ page ]
}
only transitioning views that change
You may have noticed a pages
property from the first example:
module Transitions exposing (transitions)
import Generated.Docs.Pages as Docs
import Spa.Transition as Transition
import Utils.Spa as Spa
transitions : Spa.Transitions msg
transitions =
{ layout = Transition.fadeElmUi 300
, page = Transition.fadeElmUi 300
, pages = []
}
By default, this value is just an empty list. The pages
property is a way to
prevent things like common sidebars from fading in and out when you click links
in the same view.
Here's how this site uses the pages
property to prevent the docs sidebar from
fading out when navigating between /docs/*
routes:
module Transitions exposing (transitions)
import Generated.Docs.Pages as Docs
import Spa.Transition as Transition
import Utils.Spa as Spa
transitions : Spa.Transitions msg
transitions =
{ layout = Transition.fadeElmUi 500
, page = Transition.fadeElmUi 300
, pages =
[ { path = Docs.path
, transition = Transition.fadeElmUi 300
}
]
}
Because the sidebar is rendered in src/Layouts/Docs.elm
, anytime we navigate
within the Docs.path
(/docs/*
) routes, it will stay in view.
Here we chose to use the same transition that the other page
property uses.