Scrollspy Navigation Example
See the live example
This is an advanced example demonstrating how multiple features of ScrollView may be used together to create a navigation system tied to the location of content on the page. This is the most involved example found in this guide, so I suggest reading the ScrollView docs in their entirety before diving into this.
Given some hierarchical data, we can start building a page. Consider an abbreviated version of the structure that both the page sections and navigation will be constructed from:
...
{
name: 'Frameworks',
key: 's2',
children: [
{
name: 'JavaScript',
key: 's2c1',
children: [
{ name: 'Vue.js', key: 's2c1c1' },
{ name: 'React.js', key: 's2c1c2' },
{ name: 'Angular.js', key: 's2c1c3' }
]
},
{
name: 'CSS',
key: 's2c2',
children: [
{ name: 'Foundation', key: 's2c2c1' },
{ name: 'Bootstrap', key: 's2c2c2' }
]
}
]
}
...
This is a snippet of some of the data the navigation and page sections are built from. It is a tree data structure where each node may have children. We build the page by flattening the tree structure into an array of objects in a computed prop, while still preserving the nesting depth with an added level
property to each node:
[
{ name: 'Frameworks', key: 's2', level: 1 },
{ name: 'JavaScript', key: 's2c1', level: 2 },
{ name: 'Vue.js', key: 's2c1c1', level: 3 },
{ name: 'React.js', key: 's2c1c2', level: 3 }
{ name: 'Angular.js', key: 's2c1c3', level: 3 }
]
We then iterate this resulting structure in the template and create a page section for each item. Every page section contains a scroll-view
with a singular scroll-marker
, a heading
component, and some placeholder text. The scroll-marker
's are used to update the currently visible page sections in the component state when they enter visibility. heading
is a presentational component that accepts a weight
, which we pass the node's level property we added earlier. heading
uses weight
to render a corresponding heading element (h1, h2, h3, etc).
Note, the key property on each node is completely arbitrary -- the only constraint is that they must be unique because they're used as the keys on the
scroll-markers
.
At this point, the page is constructed using the structure we defined in the initial data file. We now use the same tree structure to construct the navigation in the scroll-nav
component.
Here, a render function is utilized so we can programmatically construct the UI of this component. Starting at the top level, the menu tree is recursively crawled to create the resulting HTML markup (this is pseudo code, but similar to the actual output in the live example):
<ul class="nav-level-1">
<li class="active">
Frameworks
<ul class="nav-level-2">
<li class="active">
JavaScript
<ul class="nav-level-3">
<li>Vue.js</li>
<li class="active">React.js</li>
<li>Angular.js</li>
</ul>
</li>
</ul>
</li>
</ul>
For the final piece of logic, we need to determine how to add the 'active' classes from the top level of the structure down to the currently active item. Put simply, we're operating under the assumption that if the "React.js" section is visible in the viewport, it's list item is given an active class, along with it's ascendants -- "JavaScript" and "Frameworks", in this case.
The implementation of this is as follows:
scroll-marker
for a section becomes visible and theisVisible
listener executes, receiving the key as a parameter.- We recursively search our menu tree data structure for this key, keeping the ascendants keys' along the way as a sort of path or "breadcrumbs".
- When this completes, we have an array of keys ending with the key of the section that just became visible. This array is passed to the
active
prop on thescroll-nav
component. - When the menu is built, an active class is conditionally bound to each
<li>
if the key for that item is included in the array of active keys.
That's pretty much it! For the sake of brevity, certain implementation details were excluded from this overview. If you're interested in implementing a Scrollspy Navigation in your app, I suggest you visit the GitHub Repo and take a closer look at the source code for this example. You can also experiment with examples locally following the instructions here.