-
Notifications
You must be signed in to change notification settings - Fork 0
Animation
The file src/lib/cljs/one/browser/animation.cljs
contains the one.browser.animation
namespace which implements basic effects and animations.
An animation is a composition of one or more effects or animations.
A simple effect can be described with five pieces of information: the effect name, the start value, the end value, time and acceleration. The values of start and end will depend on the kind of effect. Effects can be described with a simple Clojure map. For example: if we want to create an effect that moves the label in the text field of the sample application, we could create a slide
effect.
{:effect :slide :end [10 10] :time 500}
This describes an effect which will slide
an element to the position [10 10]
in 500 milliseconds. Notice that we do not need to provide all of the values. Some missing values have defaults and some will be calculated. In this example the start position is assumed to be the current position of the element that will be moved.
In some situations, we only know that we want to move something relative to its current location.
{:effect :slide :up 40 :time 200}
The slide
effect allows us to specify, :up
, :down
, :left
and :right
amounts and the positions will be calculated for us.
As we move through the rest of this page, you will want to follow along. Start the sample application and open a ClojureScript REPL. Evaluate the following form to enter the one.browser.animation
namespace.
(in-ns 'one.browser.animation)
Effects will visually change an HTML element and so must be bound to an element before they can be run. In the terminology of this library, once an effect is bound, it becomes an animation. The bind
function is used to associate effects with elements and produce animations.
In the example below, we use Domina to find the label element that we would like to animate. We then bind the effect to the element and call the start
function to start the animation.
(def label (get-element "//label[@id='name-input-label']/span"))
(start (bind label {:effect :slide :up 40 :time 200}))
Running this animation will move the label above the text field. We can move the label back into the field by moving it the same distance in the opposite direction.
(start (bind "//label[@id='name-input-label']/span"
{:effect :slide :down 40 :time 200}))
Notice that in this example, the xpath string is passed directly to bind
. bind
can be passed an element, a keyword which represents the CSS ID of an element or an xpath string.
When you click in the text field, you will notice that two things happen at the same time: the label moves up and the color changes. This is an example of two effects being applied in parallel. The two effects are :slide
and :color
.
{:effect :color :end "#53607b" :time 200}
{:effect :slide :up 40 :time 200}
The bind
function can take any number of effects and will run them in order. In the examples below, the label's color will change and then the label will be moved. Each change takes 1 second (the default) so that you can clearly see what is going on.
(start (bind label {:effect :color :end "#53607b"}
{:effect :slide :up 40}
{:effect :color :end "#BBC4D7"}
{:effect :slide :down 40}))
In the sample application, the color
and slide
effects happen at the same time. To run effects in parallel, we put them in a vector.
(start (bind label [{:effect :color :end "#53607b"}
{:effect :slide :up 40}]
[{:effect :color :end "#BBC4D7"}
{:effect :slide :down 40}]))
The bind
function accepts any number of vectors and maps and will run them sequentially. A map represents a single effect and a vector represents multiple effects to be run in parallel. In the first example of bind
above, we passed four maps which were run sequentially. In the second example, we passed two vectors, each containing two maps. The two vectors are effects which were run sequentially. The maps in each vector represent effects which were run in parallel.
Using bind to create complex animations is simple but it only allows us to create animations for a single element. Sometimes we may want to create animations which involve multiple elements.
Imagine that we would like to create an animation that moves the field label up and down while changing its color. In this example we get the label element and record is initial color. We then create an animation for the label which moves it up while changing its color to red, and then moves it down while changing its color back to its original color.
(def label (get-element "//label[@id='name-input-label']/span"))
(def label-color (color label))
(def red [255 0 0])
(def move-label (bind label
[{:effect :slide :up 200 :time 2000 :accel :ease-out}
{:effect :color :end red :time 2000}]
[{:effect :slide :down 200 :time 2000 :accel :ease-in}
{:effect :color :end label-color :time 2000}]))
(start move-label)
Next, imagine that we would like to change the background color of the text field. We first get the input element with the ID :name-input
. We then define some colors and record the original background color of the field. The animation will change the background color to red, green, blue and then back to the original color.
(def input (get-element :name-input))
(def green [0 255 0])
(def blue [0 0 255])
(def input-bg-color (bg-color input))
(def background (bind input
{:effect :bg-color :end red}
{:effect :bg-color :end green}
{:effect :bg-color :end blue}
{:effect :bg-color :end input-bg-color}))
(start background)
Finally, we would like to make the button change its size several times. We get the button and record its size. We then create an animation that makes several changes to the button's size. All of the other animations last about 4 seconds and we would like this one to take the same amount of time. We map the effects over a function that sets the time for each effect to 4000/6.
(def button (get-element :greet-button))
(def button-size (size button))
(def button-dance (apply bind button
(map #(assoc % :time (quot 4000 6))
[{:effect :resize :end [150 150]}
{:effect :resize-height :end 200}
{:effect :resize-width :end 200}
{:effect :resize-height :end 150}
{:effect :resize-width :end 150}
{:effect :resize :end button-size}])))
(start button-dance)
The functions serial
and parallel
may be used to take many animations and compose them into a single animation. serial
will compose them into a single animation which will run each animation in sequence. parallel
will compose them into a single animation which will run each animation in parallel.
(start (serial move-label background button-dance))
(start (parallel move-label background button-dance))
Events can be used with animations to trigger actions when an animation begins or finishes. In the example below we define a var with the complex parallel animation from above. We then listen once for a finish
event and show an alert box.
(def a (parallel move-label background button-dance))
(event/listen-once a
"finish"
#(js/alert "Animation Finished."))
(start a)
You may listen for begin
and finish
events.
Available effects include: :color
, :fade
, :fade-in
, :fade-out
, :fade-in-and-show
, :fade-out-and-hide
, :slide
, :swipe
, :bg-color
, :resize
, :resize-width
and :resize-height
.
Many of these effects are demonstrated in this page. For more detail and examples, please refer the source file src/lib/cljs/one/browser/animation.cljs
.
Each effect can have an associated acceleration function. An acceleration function is a function which takes a number in the range [0 1] and returns a number in the same range. There are three built-in acceleration functions: :ease-in
, :ease-out
and :ease-in-and-out
. These functions can be used in effects by associating the :accel
key with one of these keywords.
{:effect :slide :up 200 :time 2000 :accel :ease-out}
You may also implement your own acceleration functions.