aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoris2017-02-13 15:51:53 +0100
committerJoris2017-02-13 15:51:53 +0100
commitcd7cc5fe19485d60d21c7a58023ba1c2eb9005b6 (patch)
tree4b429f2f52fcf29a476a5d294e787bde7422056d
parent82d88358b2ab76ae272cf35deb09737b94ccb0cc (diff)
downloadreading-cd7cc5fe19485d60d21c7a58023ba1c2eb9005b6.tar.gz
reading-cd7cc5fe19485d60d21c7a58023ba1c2eb9005b6.tar.bz2
reading-cd7cc5fe19485d60d21c7a58023ba1c2eb9005b6.zip
Animate book detail show and hide
-rw-r--r--src/main/scala/reading/Route.scala13
-rw-r--r--src/main/scala/reading/component/widget/Animate.scala55
-rw-r--r--src/main/scala/reading/component/widget/Modal.scala36
-rw-r--r--src/main/scala/reading/component/widget/Transition.scala12
-rw-r--r--src/main/scala/reading/component/widget/style/Modal.scala24
5 files changed, 115 insertions, 25 deletions
diff --git a/src/main/scala/reading/Route.scala b/src/main/scala/reading/Route.scala
index c1f993e..9295d49 100644
--- a/src/main/scala/reading/Route.scala
+++ b/src/main/scala/reading/Route.scala
@@ -1,6 +1,7 @@
package reading
-import org.scalajs.dom
+import org.scalajs.dom.window
+import org.scalajs.dom.raw.PopStateEvent
import scala.scalajs.js.URIUtils
import rx.Var
@@ -12,10 +13,10 @@ sealed trait Route
object Route {
case class Books(filters: Seq[Filter]) extends Route
- val current: Var[Route] = Var(parse(dom.window.location.hash))
+ val current: Var[Route] = Var(parse(window.location.hash))
- dom.window.onpopstate = (e: dom.raw.PopStateEvent) => {
- current() = parse(dom.window.location.hash)
+ window.onpopstate = (e: PopStateEvent) => {
+ current() = parse(window.location.hash)
}
def parse(hash: String): Route =
@@ -50,7 +51,7 @@ object Route {
case Books(filters) => "/books" ++ (if (filters.nonEmpty) filters.map(filter => s"${filter.kind}=${filter.nonFormattedName}").mkString("?", "&", "") else "")
case _ => "/books"
}
- dom.window.location.origin + dom.window.location.pathname + "#" + URIUtils.encodeURI(hash)
+ window.location.origin + window.location.pathname + "#" + URIUtils.encodeURI(hash)
}
def goTo(route: Route): Unit = {
@@ -59,6 +60,6 @@ object Route {
}
def push(route: Route): Unit = {
- dom.window.history.pushState(null, "", url(route));
+ window.history.pushState(null, "", url(route));
}
}
diff --git a/src/main/scala/reading/component/widget/Animate.scala b/src/main/scala/reading/component/widget/Animate.scala
new file mode 100644
index 0000000..0e848aa
--- /dev/null
+++ b/src/main/scala/reading/component/widget/Animate.scala
@@ -0,0 +1,55 @@
+package reading.component.widget
+
+import scala.collection.mutable.Map
+
+import org.scalajs.dom.{ window, document }
+import org.scalajs.dom.raw.HTMLElement
+
+object Animate {
+ val animationFrames: Map[String, Int] = Map.empty
+
+ def apply(
+ id: String,
+ duration: Double,
+ transition: (Double, Double) => Double,
+ animate: (Double, HTMLElement) => Unit,
+ onEnd: => Unit = ()
+ ): Unit = {
+ animationFrames.get(id) match {
+ case Some(animationFrame) => window.cancelAnimationFrame(animationFrame)
+ case None => ()
+ }
+ val animationFrame = window.requestAnimationFrame(ts =>
+ frame(id, ts, duration, transition, animate, onEnd)(ts))
+ animationFrames.put(id, animationFrame)
+ ()
+ }
+
+ private def frame(
+ id: String,
+ start: Double,
+ duration: Double,
+ transition: (Double, Double) => Double,
+ animate: (Double, HTMLElement) => Unit,
+ onEnd: => Unit
+ )(
+ timestamp: Double
+ ): Unit = {
+ document.getElementById(id).asInstanceOf[HTMLElement] match {
+ case element: HTMLElement =>
+ val elapsed = timestamp - start
+ if (elapsed < duration) {
+ animate(Transition.easeOut(elapsed, duration), element)
+ val animationFrame = window.requestAnimationFrame(frame(id, start, duration, transition, animate, onEnd))
+ animationFrames.put(id, animationFrame)
+ } else {
+ animate(1, element)
+ onEnd
+ }
+ case _ =>
+ val animationFrame = window.requestAnimationFrame(ts => frame(id, ts, duration, transition, animate, onEnd)(ts))
+ animationFrames.put(id, animationFrame)
+ }
+ ()
+ }
+}
diff --git a/src/main/scala/reading/component/widget/Modal.scala b/src/main/scala/reading/component/widget/Modal.scala
index fe10d1f..02c42be 100644
--- a/src/main/scala/reading/component/widget/Modal.scala
+++ b/src/main/scala/reading/component/widget/Modal.scala
@@ -1,24 +1,41 @@
package reading.component.widget
-import rx._
-import Ctx.Owner.Unsafe._
+import scala.util.Random
-import scalatags.JsDom.all._
+import org.scalajs.dom.raw.HTMLElement
+import rx._
+import rx.Ctx.Owner.Unsafe._
import scalacss.Defaults._
import scalacss.ScalatagsCss._
+import scalatags.JsDom.all._
import reading.component.widget.style.{ Modal => ModalStyle }
import reading.utils.{ RxAttr }
object Modal {
def apply(onClose: => Unit)(content: HtmlTag): HtmlTag = {
+ val modalId = s"modal${Random.nextInt}"
+
+ Animate(
+ id = modalId,
+ duration = 300,
+ transition = Transition.easeOut,
+ animate = (progress, element) => {
+ element.style.opacity = s"$progress"
+ element.childNodes(2) match {
+ case e: HTMLElement => e.style.transform = s"translateY(${40 * (progress - 1)}px)"
+ }
+ }
+ )
+
div(
ModalStyle.render,
ModalStyle.modal,
+ id := modalId,
div(
ModalStyle.curtain,
- RxAttr(onclick, Rx(() => onClose))
+ RxAttr(onclick, Rx(() => close(modalId, onClose)))
),
div(
@@ -26,10 +43,19 @@ object Modal {
content,
button(
ModalStyle.close,
- RxAttr(onclick, Rx(() => onClose)),
+ RxAttr(onclick, Rx(() => close(modalId, onClose))),
"Fermer"
)
)
)
}
+
+ private def close(modalId: String, onClose: => Unit): Unit =
+ Animate(
+ id = modalId,
+ duration = 300,
+ transition = Transition.linear,
+ onEnd = onClose,
+ animate = (progress, element) => element.style.opacity = s"${1 - progress}"
+ )
}
diff --git a/src/main/scala/reading/component/widget/Transition.scala b/src/main/scala/reading/component/widget/Transition.scala
new file mode 100644
index 0000000..aa8ff3d
--- /dev/null
+++ b/src/main/scala/reading/component/widget/Transition.scala
@@ -0,0 +1,12 @@
+package reading.component.widget
+
+object Transition {
+ def linear(progress: Double, total: Double): Double =
+ progress / total
+
+ def easeIn(progress: Double, total: Double): Double =
+ math.pow(progress, 2) / math.pow(total, 2)
+
+ def easeOut(progress: Double, total: Double): Double =
+ (-1) * (progress / total) * (progress / total - 2)
+}
diff --git a/src/main/scala/reading/component/widget/style/Modal.scala b/src/main/scala/reading/component/widget/style/Modal.scala
index bfcc276..ae37c4b 100644
--- a/src/main/scala/reading/component/widget/style/Modal.scala
+++ b/src/main/scala/reading/component/widget/style/Modal.scala
@@ -19,23 +19,19 @@ object Modal extends StyleSheet.Inline {
right(0.px),
bottom(0.px),
left(0.px),
- overflowY.scroll
+ overflowY.scroll,
+ opacity(0)
)
val curtain = style(
- Media.desktop(
- width(100.%%),
- height(100.%%),
- position.absolute,
- top(0.px),
- left(0.px),
- backgroundColor(C.black.value),
- opacity(0.5),
- cursor.pointer
- ),
- Media.mobile(
- display.none
- )
+ width(100.%%),
+ height(100.%%),
+ position.absolute,
+ top(0.px),
+ left(0.px),
+ backgroundColor(C.black.value),
+ opacity(0.7),
+ cursor.pointer
)
val content = style(