Progress Nav
by kuonyuu
HTML
<nav class="toc">
<ul>
<li><a href="#intro">Intro</a></li>
<li>
<a href="#dev">Developer Mode</a>
<ul>
<li><a href="#dev-edit-html">Edit HTML</a></li>
<li><a href="#dev-element-classes">Element Classes</a></li>
<li><a href="#dev-slide-classes">Slide Classes</a></li>
<li><a href="#dev-export-html">Export HTML</a></li>
</ul>
</li>
<li>
<a href="#css">CSS Editor</a>
<ul>
<li><a href="#css-fonts">Custom Fonts</a></li>
<li><a href="#css-developer-mode">Developer Mode</a></li>
<li><a href="#css-examples">Examples</a></li>
</ul>
</li>
</ul>
<svg class="toc-marker" width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<path stroke="#444" stroke-width="3" fill="transparent" stroke-dasharray="0, 0, 0, 1000" stroke-linecap="round" stroke-linejoin="round" transform="translate(-0.5, -0.5)" />
</svg>
</nav>
<article class="contents">
<section id="intro">
<h2>Progress Nav</h2>
<p>
This page demonstrates an idea for how progress can be visualized inside of a standard page nav. Scroll the page and note how the marker animates to highlight all of the sections that are currently on screen.
</p>
<p>
The rest of the content below is taken from <a href="https://slides.com/developers" target="_top">slides.com/developers</a>. Take a look at that to see how the progress nav looks on a real page.
</p>
<p>
Created by Hakim El Hattab | <a href="http://hakim.se" target="_top">hakim.se</a> | <a href="http://twitter.com/hakimel" target="_top">@hakimel</a>
</p>
<h2>Slides for Developers</h2>
<p>
We strive to make Slides a great and flexible tool for developers. Presentations created on Slides are HTML documents under the hood, so generally anything that HTML can do, Slides can do. We make it easy to access and edit the underlying HTML and CSS through the <a href="#dev">Developer Mode</a>.
</p>
<p>
There's also an API for creating new...
SCSS
$menuWidth: 14em;
$menuPaddingLeft: 3em;
* {
box-sizing: border-box;
}
body {
width: 100vw;
height: 100vh;
overflow: auto;
background-color: #fff;
}
body {
padding: 2em 2em 2em $menuWidth+$menuPaddingLeft;
font-size: 16px;
line-height: 1.6;
font-family: 'Roboto', sans-serif;
}
.toc {
position: fixed;
left: $menuPaddingLeft;
top: 5em;
padding: 1em;
width: $menuWidth;
line-height: 2;
ul {
list-style: none;
padding: 0;
margin: 0;
}
ul ul {
padding-left: 2em;
}
li a {
display: inline-block;
color: #aaa;
text-decoration: none;
transition: all 0.3s cubic-bezier(0.230, 1.000, 0.320, 1.000);
}
li.visible>a {
color: #111;
transform: translate( 5px );
}
}
.toc-marker {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
path {
transition: all 0.3s ease;
}
}
.contents {
padding: 1em;
max-width: 800px;
font-size: 1.2em;
font-family: 'Frank Ruhl Libre', sans-serif;
img {
max-width: 100%;
}
.code-block {
white-space: pre;
overflow: auto;
max-width: 100%;
code {
display: block;
background-color: #f9f9f9;
padding: 10px;
}
}
.code-inline {
background-color: #f9f9f9;
padding: 4px;
}
h2,
h3 {
padding-top: 1em;
}
}
@media screen and (max-width: 1200px) {
body {
font-size: 14px;
}
}
JavaScript
var toc = document.querySelector( '.toc' );
var tocPath = document.querySelector( '.toc-marker path' );
var tocItems;
// Factor of screen size that the element must cross
// before it's considered visible
var TOP_MARGIN = 0.1,
BOTTOM_MARGIN = 0.2;
var pathLength;
window.addEventListener( 'resize', drawPath, false );
window.addEventListener( 'scroll', sync, false );
drawPath();
function drawPath() {
tocItems = [].slice.call( toc.querySelectorAll( 'li' ) );
// Cache element references and measurements
tocItems = tocItems.map( function( item ) {
var anchor = item.querySelector( 'a' );
var target = document.getElementById( anchor.getAttribute( 'href' ).slice( 1 ) );
return {
listItem: item,
anchor: anchor,
target: target
};
} );
// Remove missing targets
tocItems = tocItems.filter( function( item ) {
return !!item.target;
} );
var path = [];
var pathIndent;
tocItems.forEach( function( item, i ) {
var x = item.anchor.offsetLeft - 5,
y = item.anchor.offsetTop,
height = item.anchor.offsetHeight;
if( i === 0 ) {
path.push( 'M', x, y, 'L', x, y + height );
item.pathStart = 0;
}
else {
// Draw an additional line when there's a change in
// indent levels
if( pathIndent !== x ) path.push( 'L', pathIndent, y );
path.push( 'L', x, y );
// Set the current path so that we can measure it
tocPath.setAttribute( 'd', path.join( ' ' ) );
item.pathStart = tocPath.getTotalLength() || 0;
path.push( 'L', x, y + height );
}
pathIndent = x;
tocPath.setAttribute( 'd', path.join( ' ' ) );
item.pathEnd = tocPath.getTotalLength();
} );
pathLength = tocPath.getTotalLength();
sync();
}
function sync() {
var windowHeight = window.innerHeight;
var pathStart = pathLength,
pathEnd = 0;
var visibleItems = 0;
tocItems.forEach( function( item ) {
...