Skip to content

Commit a8bc52d

Browse files
Added first example of animation
1 parent ec34694 commit a8bc52d

File tree

2 files changed

+69
-11
lines changed

2 files changed

+69
-11
lines changed

src/main/scala/demo/animate.scala

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package demo
2+
3+
import scala.scalajs.js.Date
4+
import org.scalajs.dom.window
5+
import japgolly.scalajs.react.BackendScope
6+
7+
8+
object animate {
9+
trait Interpolable[A] {
10+
def mix(a: A, b: A, t: Double): A
11+
}
12+
13+
case class AnimateOptions(
14+
duration: Double = 500,
15+
easing: Double => Double = identity
16+
)
17+
18+
implicit val interpolateNumbers = new Interpolable[Double] {
19+
def mix(a: Double, b: Double, t: Double) = a + (b - a) * t
20+
}
21+
22+
implicit class AnimateBackendScope[P, S](val $: BackendScope[P, S]) extends AnyVal {
23+
def animateState(finalState: S, options: AnimateOptions = AnimateOptions())(implicit i: Interpolable[S]) = {
24+
val start = Date.now()
25+
val initialState = $._state
26+
27+
def update(d: Double = 0): Unit = {
28+
val now = Date.now()
29+
val t = ((now - start) / options.duration) min 1
30+
$.setState(i.mix(initialState, finalState, options.easing(t)))
31+
32+
if (t < 1) window.requestAnimationFrame(update _)
33+
}
34+
35+
update()
36+
}
37+
38+
def animateModState(mod: S => S, options: AnimateOptions = AnimateOptions())(implicit i: Interpolable[S]) =
39+
animateState(mod($._state), options)
40+
}
41+
}

src/main/scala/demo/components/logo.scala

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,44 @@
11
package demo.components
22

3+
import scala.util.Random
34
import scala.scalajs.js
45
import japgolly.scalajs.react._
5-
import japgolly.scalajs.react.vdom.all.{ a, h1, h3, href, div, className }
6+
import japgolly.scalajs.react.vdom.all.{ a, h1, h3, href, div, className, onClick }
67
import japgolly.scalajs.react.vdom.svg.all._
78
import paths.mid.Bezier
89

910
import demo.colors._
11+
import demo.animate._
1012

1113

1214
object logo {
15+
type Points = List[(Double, Double)]
16+
implicit val interpolate = new Interpolable[Points] {
17+
def mix(ps: Points, qs: Points, t: Double) =
18+
ps zip qs map { case ((a, b), (c, d)) => (a + (c - a) * t, b + (d - b) * t) }
19+
}
20+
val points: Points = List(
21+
(0, 50),
22+
(50, 70),
23+
(100, 40),
24+
(150, 30),
25+
(200, 60),
26+
(250, 80),
27+
(300, 50)
28+
)
29+
30+
class Backend($: BackendScope[Unit, Points]) {
31+
def randomize(ps: Points) = ps map { case (x, y) => (x, y - 25 + 50 * Random.nextDouble) }
32+
def onClick(e: ReactEventI) = $.animateModState(randomize)
33+
}
34+
1335
val Logo = ReactComponentB[Unit]("Paths.js")
14-
.render(_ => {
15-
val line = Bezier(List(
16-
(0, 50),
17-
(50, 70),
18-
(100, 40),
19-
(150, 30),
20-
(200, 60),
21-
(250, 80),
22-
(300, 50)
23-
))
36+
.initialState(points)
37+
.backend(new Backend(_))
38+
.render((_, ps, b) => {
39+
val line = Bezier(ps)
2440
val circles = line.path.points.map(p => circle(
41+
onClick ==> b.onClick,
2542
r := 5,
2643
cx := p(0),
2744
cy := p(1),

0 commit comments

Comments
 (0)