Skip to content

Instantly share code, notes, and snippets.

@ryan-haskell
Created January 28, 2017 02:32
Show Gist options
  • Save ryan-haskell/2321a9f5cd8c1bd32abbd9a3d8f3e8a9 to your computer and use it in GitHub Desktop.
Save ryan-haskell/2321a9f5cd8c1bd32abbd9a3d8f3e8a9 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<title>VueJS | Scrollspy</title>
<style type="text/css">
body {
margin: 0;
font-family: Arial;
padding: 800px 0;
}
.left-nav {
position: fixed;
top: 100px;
left: 0;
padding: 0;
margin: 0;
border: solid 2px #ccc;
border-bottom: 0;
}
li {
display: flex;
}
.left-nav a {
padding: 10px 20px;
min-width: 100px;
border-bottom: solid 2px #ccc;
background-color: white;
}
.left-nav a:hover,
.left-nav a.active {
background-color: #ccc;
}
.sections {
position: relative;
margin-left: 200px;
border: solid 2px #ccc;
border-top: 0;
}
.sections > section {
border-top: solid 2px #ccc;
padding: 20px 40px;
min-height: 20vw;
}
</style>
</head>
<body>
<div id="app">
<div class="sections" v-scrollspy="scrollspy">
<ul class="left-nav">
<li v-for="id in [1,2,3,4]">
<a v-bind:data-scrollspy-link="id"
v-bind:href="'#' + id"
v-bind:class="{ 'active': scrollspy && scrollspy.activeLink ? scrollspy.activeLink.id == id : false }">
Link {{id}}
</a>
</li>
</ul>
<section v-bind:data-scrollspy-section="num" v-for="num in [1,2,3,4]" v-bind:id="num">
<h1>Section {{num}}</h1>
</section>
</div>
</div>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script type="text/javascript">
var utils = {
convertQueryToArray: function(querySelector) {
return Array.prototype.slice.call(querySelector);
},
getElemOffset: function(elem) {
return elem.getBoundingClientRect().top + window.scrollY;
},
sortByOffsetDescending: function(a,b) {
var LT = -1,
GT = 1,
EQ = 0;
if (a.offset > b.offset) {
return LT;
} else if (a.offset < b.offset) {
return GT;
} else {
return EQ;
}
},
getActiveLink: function(sortedSections) {
var currentScrollPosition = window.scrollY;
var matchedElems = sortedSections.filter(function(elem){
return elem.offset <= currentScrollPosition + 1;
});
if(matchedElems.length > 0) {
return matchedElems[0];
} else {
return sortedSections[sortedSections.length - 1];
}
},
getSections: function(sectionElems) {
var _self = this;
return sectionElems.map(function(elem) {
return {
elem: elem,
id: elem.getAttribute('id'),
offset: _self.getElemOffset(elem)
};
}).sort(_self.sortByOffsetDescending);
}
};
var alreadyUpdated = false;
Vue.directive('scrollspy', {
inserted: function(el, binding, vnode) {
if(alreadyUpdated)
return;
alreadyUpdated = true;
// Get elements
var linkElems = utils.convertQueryToArray(document.querySelectorAll('[data-scrollspy-link]'));
var sectionElems = utils.convertQueryToArray(document.querySelectorAll('[data-scrollspy-section]'));
// Initialize links
var links = linkElems.map(function(elem) {
return {
elem: elem,
id: elem.getAttribute('data-scrollspy-link')
};
});
// Initialize sections
var sections = utils.getSections(sectionElems);
// Initialize active link
var activeLink = utils.getActiveLink(sections);
// Bind to data object
vnode.context[binding.expression] = {
links: links,
sections: sections,
activeLink: activeLink
};
// Set up listeners
window.addEventListener('scroll', function() {
vnode.context[binding.expression].activeLink = utils.getActiveLink(vnode.context[binding.expression].sections);
});
window.addEventListener('resize', function() {
var sectionElems = utils.convertQueryToArray(document.querySelectorAll('[data-scrollspy-section]'));
vnode.context[binding.expression].sections = utils.getSections(sectionElems);
});
}
});
var vm = new Vue({
el: '#app',
data: {
currentSection: 1,
scrollspy: undefined
}
});
</script>
</body>
</html>
@ryan-haskell
Copy link
Author

#alexMoraskForTheWin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment