Skip to content

Instantly share code, notes, and snippets.

@raspasov
Created March 18, 2016 08:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raspasov/f9ca712571efd932169e to your computer and use it in GitHub Desktop.
Save raspasov/f9ca712571efd932169e to your computer and use it in GitHub Desktop.
ClojureScript Animation Library
(ns my-project.cljs-animate
(:require-macros [cljs.core.async.macros :refer [go]])
(:require [cljs.core.async :refer [offer! put! promise-chan chan <! >! timeout alts! chan timeout dropping-buffer sliding-buffer]]))
;THIS IS A COPY/PASTE of https://github.com/gstamp/tween-clj
;TODO make tween-clj a ClojureScript lib (easy)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Transition types
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn transition-linear
"Calculates a linear transition.
'pos' is a number between 0 and 1 representing the position of the transition between
starting (0) and finished (1)"
^double
[^double pos] pos)
(defn transition-pow
"Calculates a power transition.
'x' the the power to raise to.
'pos' is a number between 0 and 1 representing the position of the transition between
starting (0) and finished (1)"
(^double [^double pos] (transition-pow 6 pos))
(^double [^double x ^double pos] (js/Math.pow pos x)))
(defn transition-expo
"Calculates a exponential transition.
'pos' is a number between 0 and 1 representing the position of the transition between
starting (0) and finished (1)"
^double
[^double pos]
(js/Math.pow 2 (* 8 (- pos 1))))
(defn transition-sine
"Calculates a sineousidal transition.
'pos' is a number between 0 and 1 representing the position of the transition between
starting (0) and finished (1)"
^double
[^double pos]
(- (double 1)
(js/Math.sin (/ (* (- (double 1) pos)
js/Math.PI)
(double 2)))))
(defn transition-circ
"Calculates a circular transition.
'pos' is a number between 0 and 1 representing the position of the transition between
starting (0) and finished (1)"
^double
[^double pos]
(- 1 (js/Math.sin (js/Math.acos pos))))
(defn transition-back
"Calculates a transition that moves backwards before moving forwards.
'x' controls the bounceback size.
'pos' is a number between 0 and 1 representing the position of the transition between
starting (0) and finished (1)"
(^double [^double pos] (transition-back (double 1.618) pos))
(^double [^double x ^double pos] (* (js/Math.pow pos (double 2))
(- (* (inc x) pos) x))))
(defn transition-bounce
"Calculates a transition that looks somewhat like a bouncing ball.
'pos' is a number between 0 and 1 representing the position of the transition between
starting (0) and finished (1)"
^double
[^double pos]
(loop [a (double 0) b (double 1)]
(if (or (>= pos (/ (- 7 (* 4 a)) 11))
(not (<= 0 pos 1.0 )))
(- (* b b)
(js/Math.pow (/ (- 11 (* 6 a) (* 11 pos)) 4)
2))
(recur (+ a b) (/ b 2)))))
(defn transition-elastic
"Calculates a transition that looks somewhat like a bouncing ball.
'x' controls the elasticity
'pos' is a number between 0 and 1 representing the position of the transition between
starting (0) and finished (1)"
(^double [^double pos] (transition-elastic (double 1) pos))
(^double [^double x ^double pos]
(* (js/Math.pow (double 2) (* (double 10) (dec pos)))
(js/Math.cos (/ (* (double 20) (dec pos) js/Math.PI x)
(double 3))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Easing functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn ease-in
"Eases into the transition where
'transition' is a function defining the transition type and
'p' is a number between 0 and 1 representing the position of the transition between
starting (0) and finished (1)"
^double
[transition ^double p]
(transition p))
(defn ease-out
"Eases out of a transition where
'transition' is a function defining the transition type and
'p' is a number between 0 and 1 representing the position of the transition between
starting (0) and finished (1)"
^double
[transition ^double p]
(- (double 1) (transition (- (double 1) p) )))
(defn ease-in-out
"Eases in to then out of of a transition where
'transition' is a function defining the transition type and
'p' is a number between 0 and 1 representing the position of the transition between
starting (0) and finished (1)"
^double
[transition ^double p]
(if (<= p (double 0.5))
(/ (transition (* (double 2) p))
(double 2))
(/ (- 2 (transition (* (double 2) (- (double 1) p))))
(double 2))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Extra functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn constrain
"Constrains a value to not exceed a maximum and minimum value."
^double
[^double amt ^double low ^double high]
(if (< amt low) low
(if (> amt high)
high
amt)))
(defn range-to-p
"Converts the current position in a range into a p value for use in the transition functions.
'start' is the starting value of the range
'end' is the ending value of the range
'current' is the current value between 'start' and 'end'"
^double
[^double start ^double end ^double current]
(constrain
(/ (- current start) (- end start))
(double 0)
(double 1)))
(defn p-to-range
"Converts a p value (between 0 & 1) back into a range between start and end.
'start' is the starting value of the range
'end' is the ending value of the range
'p' is a value between 0 & 1."
^double
[^double start ^double end ^double p]
(constrain
(+ start
(* p (- end start)))
start
end))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;END OF COPY/PASTE
;Start cljs-animate library
(defn animation-stateful-transducer-ch
[^cljs.core/Atom from-to anim-f]
(chan
(sliding-buffer 1)
(map (fn [[x [easing-fn transition-fn]]]
;dynamically resolve from and to from an atom
(let [[start end] @from-to
[start end] (if (< end start) [end start] [start end])
v (cond
(= x start)
start
(= x end)
end
:else
(p-to-range start end (easing-fn transition-fn (range-to-p start end x))))]
(anim-f v)
v)))))
(def default [ease-in transition-expo])
(def linear [ease-in transition-linear])
(def ease-out [ease-out transition-expo])
(def ease-in-out [ease-in-out transition-pow])
(defn animated-value-fn!
"This is the core of the lib. Give : ) it's ok. ITS ON GIT lol. yea what is that. I've heard of Dropbox.
That's easy. Only thing you need to change is....
a-fn takes one argument, the old value, and returns the new value"
([animated-value a-fn duration-ms]
(animated-value-fn! animated-value a-fn duration-ms default))
([{:keys [from-to anim-xf-ch ^cljs.core/IFn get-last-value]} a-fn duration-ms anim-config]
(let [;get last value
from (get-last-value)
to (a-fn from)
;set the new animation state
new-from-to (reset! from-to [from to])
num-of-updates (int (* (/ duration-ms 1000) 60))
diff (- to from)
step (float (/ diff num-of-updates))
values-range (range from to step)]
(go (loop [values values-range]
;at every frame check if this loop is still valid to proceed
(if (= new-from-to @from-to)
(if-let [value (first values)]
(do
(>! anim-xf-ch [value anim-config])
(<! (timeout 17))
(recur (rest values)))
(do
;final value
(>! anim-xf-ch [to anim-config])
to))))))))
(defn animated-value-to!
"Animated directly to a value"
([animated-value to duration-ms]
(animated-value-to! animated-value to duration-ms default))
([animated-value to duration-ms anim-config]
(animated-value-fn! animated-value (fn [_] to) duration-ms anim-config)))
(defn init-animated-value-reagent
"local-atom - local atom that holds the state to be animated,
i.e. an opacity, position (left, right, top, etc) or any other HTML element property
k - path to the key inside local-atom that's going to be animated, i.e. [:a :b :c], always a vector"
[local-atom k]
(let [from-to (atom [(get-in local-atom k) (get-in local-atom k)])
anim-f (fn [v] (swap! local-atom assoc-in k v))
c (animation-stateful-transducer-ch from-to anim-f)]
{:from-to from-to :anim-xf-ch c :get-last-value (fn [] (get-in local-atom k))}))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment