]> git.ipfire.org Git - thirdparty/bulma.git/commitdiff
Add scroll spy
authorJeremy Thomas <bbxdesign@gmail.com>
Wed, 11 Apr 2018 00:14:29 +0000 (01:14 +0100)
committerJeremy Thomas <bbxdesign@gmail.com>
Wed, 11 Apr 2018 00:14:29 +0000 (01:14 +0100)
docs/_includes/elements/improve-page.html
docs/_javascript/main.js
docs/_sass/main.sass
docs/css/bulma-docs.css
docs/lib/main.js

index 05b3c6fc78c50f0a6823b82d8cc5ba4197180dd1..f431d11c7a21f284fe1d8854cf42d7154281e0e6 100644 (file)
@@ -1,4 +1,4 @@
-<div class="bd-typo">
+<div id="typo" class="bd-typo">
   <p class="has-text-grey-light">
     <a href="{{ site.url }}/made-with-bulma/">
       <img src="{{ site.url }}/images/made-with-bulma.png" alt="Made with Bulma" width="128" height="24">
index 7df0b12f71f1f13f02f6ad2feb452b3fd8c8932d..b3392c8da0d81d9d354a2c556382b35461ccf534 100644 (file)
@@ -33,16 +33,31 @@ document.addEventListener('DOMContentLoaded', () => {
   const anchors_el = document.getElementById('anchors');
   const anchor_links_el = getAll('.bd-anchor-link');
 
+  let anchors_by_id = {};
+  let anchors_order = [];
+  let anchor_nav_els = [];
+
   if (anchors_el && anchor_links_el.length > 0) {
     const anchors_el_list = anchors_el.querySelector('.bd-anchors-list');
 
-    anchor_links_el.forEach(el => {
+    anchor_links_el.forEach((el, index) => {
       const link_target = el.getAttribute('href');
       const link_text = el.previousElementSibling.innerText;
 
       if (link_text != '') {
         const item_el = createAnchorLink(link_text, link_target);
         anchors_el_list.appendChild(item_el);
+
+        const anchor_key = link_target.substring(1); // #target -> target
+        anchors_by_id[anchor_key] = {
+          id: anchor_key,
+          index,
+          target: link_target,
+          text: link_text,
+          nav_el: item_el,
+        };
+        anchors_order.push(anchor_key);
+        anchor_nav_els.push(item_el);
       }
     });
 
@@ -272,18 +287,76 @@ document.addEventListener('DOMContentLoaded', () => {
     }
   });
 
-  function whenScrolling(lastY, currentY) {
+  // Anchors highlight
+
+  let past_anchors = [];
+  anchor_links_el.reverse();
+  const trigger_offset = 24 ; // In pixels
+  const typo_el = document.getElementById('typo');
+
+  function whenScrolling() {
     if (anchors_ref_el) {
       const bounds = anchors_ref_el.getBoundingClientRect();
+      const anchors_height = anchors_el.clientHeight;
+      const typo_bounds = typo_el.getBoundingClientRect();
+      const typo_height = typo_el.clientHeight;
 
-      if (bounds.top < 1) {
+      if (bounds.top < 1 && typo_bounds.top - anchors_height + typo_height > 0) {
         anchors_el.classList.add('is-pinned');
       } else {
         anchors_el.classList.remove('is-pinned');
       }
+
+      anchor_links_el.some(el => {
+        const bounds = el.getBoundingClientRect();
+        const href = el.getAttribute('href');
+        const key = href.substring(1); // #target -> target
+
+        if (bounds.top < 1 + trigger_offset && past_anchors.indexOf(key) == -1) {
+          past_anchors.push(key);
+          highlightAnchor();
+          return;
+        } else if (bounds.top > 0 + trigger_offset && past_anchors.indexOf(key) != -1) {
+          removeFromArray(past_anchors, key);
+          highlightAnchor();
+          return;
+        }
+      });
+    }
+  }
+
+  function highlightAnchor() {
+    const future_anchors = anchors_order.diff(past_anchors);
+    let highest_index = -1;
+    let highest_anchor_key = '';
+
+    if (past_anchors.length > 0) {
+      past_anchors.forEach((key, index) => {
+        const anchor = anchors_by_id[key];
+        anchor.nav_el.className = 'is-past';
+
+        // Keep track of the bottom most item
+        if (anchor.index > highest_index) {
+          highest_index = anchor.index;
+          highest_anchor_key = key;
+        }
+      });
+
+      if (highest_anchor_key in anchors_by_id) {
+        anchors_by_id[highest_anchor_key].nav_el.className = 'is-current';
+      }
+    }
+
+    if (future_anchors.length > 0) {
+      future_anchors.forEach((key, index) => {
+        const anchor = anchors_by_id[key];
+        anchor.nav_el.className = '';
+      });
     }
   }
 
+  // Scroll
+
   function upOrDown(lastY, currentY) {
     if (currentY >= lastY) {
       return goingDown(currentY);
@@ -362,7 +435,7 @@ document.addEventListener('DOMContentLoaded', () => {
     if (!ticking) {
       window.requestAnimationFrame(function() {
         // upOrDown(lastY, currentY);
-        whenScrolling(lastY, currentY);
+        whenScrolling();
         ticking = false;
         lastY = currentY;
       });
@@ -371,4 +444,19 @@ document.addEventListener('DOMContentLoaded', () => {
     ticking = true;
   });
 
+  // Utils
+
+  function removeFromArray(array, value) {
+    if (array.includes(value)) {
+      const value_index = array.indexOf(value);
+      array.splice(value_index, 1);
+    }
+
+    return array;
+  }
+
+  Array.prototype.diff = function(a) {
+    return this.filter(function(i) {return a.indexOf(i) < 0;});
+  };
+
 });
index 53f2fb923843306e1021fd1c7fef0c34566c7bc8..ac739fddb00a671eeab3563ec3232ba49520c0d8 100644 (file)
@@ -82,6 +82,9 @@
   li
     &:not(:last-child)
       margin-bottom: 0.5em
+    &.is-past
+      a
+        color: $grey-light
     &.is-current
       a
         color: $link
 
 .bd-anchors
   padding-top: calc(1.5rem - 1px)
-  &.is-pinned
-    position: fixed
-    top: 0
+  +tablet
+    &.is-pinned
+      position: fixed
+      top: 0
 
 .bd-anchors-title
   color: $grey-light
   li
     &:last-child
       margin-top: 1em
+  a
+    color: $text-strong
 
 +touch
   .bd-lead,
index 975aec779a32defbbcf2be54089044ccf2fd8963..6804174eb23a6155105fd6b53f3cf7f35aee6527 100644 (file)
@@ -9612,6 +9612,10 @@ label.panel-block:hover {
   margin-bottom: 0.5em;
 }
 
+.bd-category-list li.is-past a, .bd-anchors-list li.is-past a {
+  color: #b5b5b5;
+}
+
 .bd-category-list li.is-current a, .bd-anchors-list li.is-current a {
   color: #3273dc;
 }
@@ -9676,9 +9680,11 @@ label.panel-block:hover {
   padding-top: calc(1.5rem - 1px);
 }
 
-.bd-anchors.is-pinned {
-  position: fixed;
-  top: 0;
+@media screen and (min-width: 769px), print {
+  .bd-anchors.is-pinned {
+    position: fixed;
+    top: 0;
+  }
 }
 
 .bd-anchors-title {
@@ -9692,6 +9698,10 @@ label.panel-block:hover {
   margin-top: 1em;
 }
 
+.bd-anchors-list a {
+  color: #363636;
+}
+
 @media screen and (max-width: 1087px) {
   .bd-lead,
   .bd-side {
index 5ff21d05c6fdba685bf54ead4d9eb32c325e714a..880f855175287a5256127dab15e0f88dff6a8bf1 100644 (file)
@@ -35,16 +35,31 @@ document.addEventListener('DOMContentLoaded', function () {
   var anchors_el = document.getElementById('anchors');
   var anchor_links_el = getAll('.bd-anchor-link');
 
+  var anchors_by_id = {};
+  var anchors_order = [];
+  var anchor_nav_els = [];
+
   if (anchors_el && anchor_links_el.length > 0) {
     var anchors_el_list = anchors_el.querySelector('.bd-anchors-list');
 
-    anchor_links_el.forEach(function (el) {
+    anchor_links_el.forEach(function (el, index) {
       var link_target = el.getAttribute('href');
       var link_text = el.previousElementSibling.innerText;
 
       if (link_text != '') {
         var item_el = createAnchorLink(link_text, link_target);
         anchors_el_list.appendChild(item_el);
+
+        var anchor_key = link_target.substring(1); // #target -> target
+        anchors_by_id[anchor_key] = {
+          id: anchor_key,
+          index: index,
+          target: link_target,
+          text: link_text,
+          nav_el: item_el
+        };
+        anchors_order.push(anchor_key);
+        anchor_nav_els.push(item_el);
       }
     });
 
@@ -274,18 +289,76 @@ document.addEventListener('DOMContentLoaded', function () {
     }
   });
 
-  function whenScrolling(lastY, currentY) {
+  // Anchors highlight
+
+  var past_anchors = [];
+  anchor_links_el.reverse();
+  var trigger_offset = 24; // In pixels
+  var typo_el = document.getElementById('typo');
+
+  function whenScrolling() {
     if (anchors_ref_el) {
       var bounds = anchors_ref_el.getBoundingClientRect();
+      var anchors_height = anchors_el.clientHeight;
+      var typo_bounds = typo_el.getBoundingClientRect();
+      var typo_height = typo_el.clientHeight;
 
-      if (bounds.top < 1) {
+      if (bounds.top < 1 && typo_bounds.top - anchors_height + typo_height > 0) {
         anchors_el.classList.add('is-pinned');
       } else {
         anchors_el.classList.remove('is-pinned');
       }
+
+      anchor_links_el.some(function (el) {
+        var bounds = el.getBoundingClientRect();
+        var href = el.getAttribute('href');
+        var key = href.substring(1); // #target -> target
+
+        if (bounds.top < 1 + trigger_offset && past_anchors.indexOf(key) == -1) {
+          past_anchors.push(key);
+          highlightAnchor();
+          return;
+        } else if (bounds.top > 0 + trigger_offset && past_anchors.indexOf(key) != -1) {
+          removeFromArray(past_anchors, key);
+          highlightAnchor();
+          return;
+        }
+      });
     }
   }
 
+  function highlightAnchor() {
+    var future_anchors = anchors_order.diff(past_anchors);
+    var highest_index = -1;
+    var highest_anchor_key = '';
+
+    if (past_anchors.length > 0) {
+      past_anchors.forEach(function (key, index) {
+        var anchor = anchors_by_id[key];
+        anchor.nav_el.className = 'is-past';
+
+        // Keep track of the bottom most item
+        if (anchor.index > highest_index) {
+          highest_index = anchor.index;
+          highest_anchor_key = key;
+        }
+      });
+
+      if (highest_anchor_key in anchors_by_id) {
+        anchors_by_id[highest_anchor_key].nav_el.className = 'is-current';
+      }
+    }
+
+    if (future_anchors.length > 0) {
+      future_anchors.forEach(function (key, index) {
+        var anchor = anchors_by_id[key];
+        anchor.nav_el.className = '';
+      });
+    }
+  }
+
+  // Scroll
+
   function upOrDown(lastY, currentY) {
     if (currentY >= lastY) {
       return goingDown(currentY);
@@ -362,7 +435,7 @@ document.addEventListener('DOMContentLoaded', function () {
     if (!ticking) {
       window.requestAnimationFrame(function () {
         // upOrDown(lastY, currentY);
-        whenScrolling(lastY, currentY);
+        whenScrolling();
         ticking = false;
         lastY = currentY;
       });
@@ -370,4 +443,21 @@ document.addEventListener('DOMContentLoaded', function () {
 
     ticking = true;
   });
+
+  // Utils
+
+  function removeFromArray(array, value) {
+    if (array.includes(value)) {
+      var value_index = array.indexOf(value);
+      array.splice(value_index, 1);
+    }
+
+    return array;
+  }
+
+  Array.prototype.diff = function (a) {
+    return this.filter(function (i) {
+      return a.indexOf(i) < 0;
+    });
+  };
 });
\ No newline at end of file