diff options
Diffstat (limited to 'examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala')
-rw-r--r-- | examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala | 74 |
1 files changed, 66 insertions, 8 deletions
diff --git a/examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala b/examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala index 9c0fbd2..7b238f4 100644 --- a/examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala +++ b/examples/demos/src/main/scala/scrollmenu/ScrollMenu.scala @@ -2,6 +2,8 @@ package scrollmenu import org.scalajs.dom +import scalatags.JsDom.all._ + case class Tree[T](value: T, children: Vector[Tree[T]]) case class MenuNode(frag: dom.HTMLElement, start: Int, end: Int) @@ -10,18 +12,69 @@ case class MenuNode(frag: dom.HTMLElement, start: Int, end: Int) * High performance scrollspy to work keep the left menu bar in sync. * Lots of sketchy imperative code in order to maximize performance. */ -class ScrollSpy(headers: Vector[Double], domTrees: Seq[Tree[MenuNode]]){ - var scrolling = false - def apply(threshold: Double) = if (!scrolling){ +class ScrollSpy(structure: Tree[String], main: dom.HTMLElement){ + val (headers, domTrees) = { + var i = 0 + def recurse(t: Tree[String], depth: Int): Tree[MenuNode] = { + val curr = + li( + a( + t.value, + href:="#"+Controller.munge(t.value), + cls:="menu-item" + ) + ) + val originalI = i + i += 1 + val children = t.children.map(recurse(_, depth + 1)) + Tree( + MenuNode( + curr(ul(paddingLeft := "15px",children.map(_.value.frag))).render, + originalI, + if (children.length > 0) children.map(_.value.end).max else originalI + 1 + ), + children + ) + } + def offset(el: dom.HTMLElement, parent: dom.HTMLElement): Double = { + if (el == parent) 0 + else el.offsetTop + offset(el.offsetParent.asInstanceOf[dom.HTMLElement], parent) + } + val headers = { + val menuItems = { + def rec(current: Tree[String]): Seq[String] = { + current.value +: current.children.flatMap(rec) + } + rec(structure).tail + } + menuItems.map(Controller.munge) + .map(dom.document.getElementById) + .map(offset(_, main)) + .toVector + } + val domTrees = structure.children.map(recurse(_, 0)) + (headers, domTrees) + } + + + private[this] var scrolling = false + def apply(threshold: => Double) = if (!scrolling){ scrolling = true - dom.requestAnimationFrame((d: Double) => start(threshold)) + dom.setTimeout(() => start(threshold), 200) } - def start(threshold: Double) = { + private[this] def start(threshold: Double) = { scrolling = false + def scroll(el: dom.HTMLElement) = { + val rect = el.getBoundingClientRect() + if (rect.top <= 0) + el.scrollIntoView(true) + else if (rect.bottom > dom.innerHeight) + el.scrollIntoView(false) + } def walkTree(tree: Tree[MenuNode]): Boolean = { - val Tree(MenuNode(menuItem, index, next), children) = tree - val before = headers(index) < threshold - val after = (next >= headers.length) || headers(next) > threshold + val Tree(MenuNode(menuItem, start, end), children) = tree + val before = headers(start) < threshold + val after = (end >= headers.length) || headers(end) > threshold val win = before && after if (win){ menuItem.classList.remove("hide") @@ -34,7 +87,10 @@ class ScrollSpy(headers: Vector[Double], domTrees: Seq[Tree[MenuNode]]){ winFound = winFound | newWinFound } if (!winFound) { + // This means it's the leaf element, because it won but there + // aren't any children which won, so it must be the actual leaf tree.children.foreach(_.value.frag.classList.remove("selected")) + scroll(menuItem) } menuItem.children(0).classList.add("pure-menu-selected") }else{ @@ -46,4 +102,6 @@ class ScrollSpy(headers: Vector[Double], domTrees: Seq[Tree[MenuNode]]){ } domTrees.map(walkTree) } + + }
\ No newline at end of file |