Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <nav>

	<ul>
    <li><a href="#intro">Intro</a></li>
		<li>
			<a href="#first">First section</a>
			<ul>
				<li>
          <a href="#second">Second section</a>
          <ul>
            <li>
              <a href="#third">Third section</a>
            </li>
          </ul>
        </li>
			</ul>
		</li>
		<li>
			<a href="#fourth">Fourth section</a>
      <ul>
        <li><a href="#fifth">Fifth section</a></li>
        <li><a href="#sixth">Sixth section</a></li>
      </ul>
		</li>
    <li><a href="#seventh">Seventh section</a></li>
    <li><a href="#eighth">Eighth section</a></li>
	</ul>
  
  <svg xmlns="http://www.w3.org/2000/svg">
    <path />
  </svg>
  
</nav>

<article id="top">
  
  <div class="subheading">An intersectionObserver approach to</div>
  <h1>Hakim's Progress Nav Concept</h1>
  
  <section id="intro">
    <h2>Intro</h2>
    <p>Hakim el Hattab published a <a href="https://codepen.io/hakimel/pen/BpKNPg">Progress Nav concept</a> on Codepen back in 2017. As pointed out by Chris in <a href="https://css-tricks.com/table-of-contents-with-intersectionobserver/">a recent blog post</a> over at CSS Tricks, we now have the Intersection Observer API which saves us a good amount of hassle. Here's my attempt at an <code>intersectionObserver</code> version of Hakim's amazing nav concept.</p>
  </section>
  
  <section id="first">
    <h2>First section</h2>
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus nisi nisl, pharetra et odio non, sollicitudin bibendum enim. Integer posuere, est et posuere feugiat, nisi nunc auctor nisi, eu tristique ipsum justo ut tellus. Phasellus molestie ultricies sodales. Nunc nec ligula placerat, vehicula orci a, tincidunt ante. Donec ac metus quis nunc cursus tincidunt.</p>
  <p>Vivamus sed ullamcorper ex. Suspendisse potenti. Nam sodales hendrerit enim, non fringilla mauris gravida maximus. Sed pharetra purus eget quam auctor luctus. Aenean arcu nulla, aliquam ut consectetur non, tincidunt eget sem. Aliquam tristique mattis urna, a posuere justo dapibus nec. Aenean non lacinia nisl. Aenean ac bibendum eros. Integer posuere enim tempor auctor venenatis. Etiam convallis nunc at eros ultrices, et elementum erat ultrices.</p>
  </section>
  
  <section id="second">
    <h2>Second section</h2>
    <p>Phasellus congue felis quis arcu dignissim, pulvinar mollis ante pretium. Sed ac auctor quam. Mauris ac tellus et quam porta mattis quis quis quam. Sed tristique gravida mauris, eget eleifend nunc accumsan in.</p>
    <p>Donec consequat fringilla diam, sit amet aliquam magna faucibus vel. Sed sagittis id nisi et facilisis. Aliquam erat volutpat. Mauris arcu nunc, euismod at enim at, porttitor laoreet lorem. In molestie dui at augue aliquet, in tincidunt nisi dictum. Donec sagittis enim vestibulum est elementum, vel lobortis libero suscipit.</p>
  <p>Vivamus sed ullamcorper ex. Suspendisse potenti. Nam sodales hendrerit enim, non fringilla mauris gravida maximus. Sed pharetra purus eget quam auctor luctus. Aenean arcu nulla, aliquam ut consectetur non, tincidunt eget sem. Aliquam tristique mattis urna, a posuere justo dapibus nec. Aenean non lacinia nisl. Aenean ac bibendum eros. Integer posuere enim tempor auctor venenatis. Etiam convallis nunc at eros ultrices, et elementum erat ultrices.</p>
  </section>
  
  <section id="third">
    <h2>Third section</h2>
    <p>Etiam id ex dolor. Curabitur quis tellus vitae neque finibus suscipit. Donec vel nisl ac justo mattis molestie. Vivamus in interdum est. Fusce sed malesuada tellus. Suspendisse imperdiet condimentum sem in tristique. Donec faucibus dui non pharetra cursus. Praesent congue turpis leo, vitae porttitor elit fringilla vel. Phasellus ut aliquet augue. Donec egestas luctus placerat.</p>
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus nisi nisl, pharetra et odio non, sollicitudin bibendum enim. Integer posuere, est et posuere feugiat, nisi nunc auctor nisi, eu tristique ipsum justo ut tellus. Phasellus molestie ultricies sodales. Nunc nec ligula placerat, vehicula orci a, tincidunt ante. Donec ac metus quis nunc cursus tincidunt.</p>
  <p>Vivamus sed ullamcorper ex. Suspendisse potenti. Nam sodales hendrerit enim, non fringilla mauris gravida maximus. Sed pharetra purus eget quam auctor luctus. Aenean arcu nulla, aliquam ut consectetur non, tincidunt eget sem. Aliquam tristique mattis urna, a posuere justo dapibus nec. Aenean non lacinia nisl. Aenean ac bibendum eros. Integer posuere enim tempor auctor venenatis. Etiam convallis nunc at eros ultrices, et elementum erat ultrices.</p>
    <p>In aliquam maximus dui, nec iaculis nunc gravida et. Cras dapibus porta est, sed finibus arcu viverra in. Vivamus volutpat placerat urna, non varius sapien tempor sit amet. Cras quis dictum enim. Vestibulum ac rhoncus diam, non venenatis dolor. Curabitur ut velit tincidunt, pulvinar dolor sit amet, tincidunt arcu. Integer at ipsum eu ex blandit consectetur.</p>
  </section>
  
  <section id="fourth">
    <h2>Fourth section</h2>
    <p>Aenean purus ex, auctor id dictum in, consequat ac lacus. Fusce aliquam tellus sed ante porttitor eleifend. Nam rutrum vulputate arcu sed euismod. Fusce diam nunc, cursus eget leo eget, interdum tempus arcu. Maecenas vestibulum faucibus dolor non fermentum. Donec tortor dui, aliquet vitae mauris id, condimentum eleifend felis.</p>
    <p>In aliquam maximus dui, nec iaculis nunc gravida et. Cras dapibus porta est, sed finibus arcu viverra in. Vivamus volutpat placerat urna, non varius sapien tempor sit amet. Cras quis dictum enim. Vestibulum ac rhoncus diam, non venenatis dolor. Curabitur ut velit tincidunt, pulvinar dolor sit amet, tincidunt arcu. Integer at ipsum eu ex blandit consectetur.</p>
  <p>Vivamus sed ullamcorper ex. Suspendisse potenti. Nam sodales hendrerit enim, non fringilla mauris gravida maximus. Sed pharetra purus eget quam auctor luctus. Aenean arcu nulla, aliquam ut consectetur non, tincidunt eget sem. Aliquam tristique mattis urna, a posuere justo dapibus nec. Aenean non lacinia nisl. Aenean ac bibendum eros. Integer posuere enim tempor auctor venenatis. Etiam convallis nunc at eros ultrices, et elementum erat ultrices.</p>
    <p>In aliquam maximus dui, nec iaculis nunc gravida et. Cras dapibus porta est, sed finibus arcu viverra in. Vivamus volutpat placerat urna, non varius sapien tempor sit amet. Cras quis dictum enim. Vestibulum ac rhoncus diam, non venenatis dolor. Curabitur ut velit tincidunt, pulvinar dolor sit amet, tincidunt arcu. Integer at ipsum eu ex blandit consectetur.</p>
  </section>
  
  <section id="fifth">
    <h2>Fifth section</h2>
    <p>Vivamus in elit non turpis eleifend egestas eget scelerisque turpis. Integer semper eget sem vel porttitor. Phasellus nec rhoncus elit. Donec feugiat mollis dui ac vehicula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</p>
    <p>In aliquam maximus dui, nec iaculis nunc gravida et. Cras dapibus porta est, sed finibus arcu viverra in. Vivamus volutpat placerat urna, non varius sapien tempor sit amet. Cras quis dictum enim. Vestibulum ac rhoncus diam, non venenatis dolor. Curabitur ut velit tincidunt, pulvinar dolor sit amet, tincidunt arcu. Integer at ipsum eu ex blandit consectetur.</p>
    <p>Fusce quis rutrum turpis. Proin pulvinar, nisi id porttitor laoreet, neque diam finibus eros, a aliquam nibh libero quis nisi. Morbi vehicula tincidunt maximus.</p>
  </section>
  
  <section id="sixth">
    <h2>Sixth section</h2>
    <p>Etiam id ex dolor. Curabitur quis tellus vitae neque finibus suscipit. Donec vel nisl ac justo mattis molestie. Vivamus in interdum est. Fusce sed malesuada tellus. Suspendisse imperdiet condimentum sem in tristique. Donec faucibus dui non pharetra cursus. Praesent congue turpis leo, vitae porttitor elit fringilla vel. Phasellus ut aliquet augue. Donec egestas luctus placerat.</p>
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus nisi nisl, pharetra et odio non, sollicitudin bibendum enim. Integer posuere, est et posuere feugiat, nisi nunc auctor nisi, eu tristique ipsum justo ut tellus. Phasellus molestie ultricies sodales. Nunc nec ligula placerat, vehicula orci a, tincidunt ante. Donec ac metus quis nunc cursus tincidunt.</p>
  <p>Vivamus sed ullamcorper ex. Suspendisse potenti. Nam sodales hendrerit enim, non fringilla mauris gravida maximus. Sed pharetra purus eget quam auctor luctus. Aenean arcu nulla, aliquam ut consectetur non, tincidunt eget sem. Aliquam tristique mattis urna, a posuere justo dapibus nec. Aenean non lacinia nisl. Aenean ac bibendum eros. Integer posuere enim tempor auctor venenatis. Etiam convallis nunc at eros ultrices, et elementum erat ultrices.</p>
    <p>In aliquam maximus dui, nec iaculis nunc gravida et. Cras dapibus porta est, sed finibus arcu viverra in. Vivamus volutpat placerat urna, non varius sapien tempor sit amet. Cras quis dictum enim. Vestibulum ac rhoncus diam, non venenatis dolor. Curabitur ut velit tincidunt, pulvinar dolor sit amet, tincidunt arcu. Integer at ipsum eu ex blandit consectetur.</p>
  </section>
  
  <section id="seventh">
    <h2>Seventh section</h2>
    <p>Aenean purus ex, auctor id dictum in, consequat ac lacus. Fusce aliquam tellus sed ante porttitor eleifend. Nam rutrum vulputate arcu sed euismod. Fusce diam nunc, cursus eget leo eget, interdum tempus arcu. Maecenas vestibulum faucibus dolor non fermentum. Donec tortor dui, aliquet vitae mauris id, condimentum eleifend felis.</p>
    <p>In aliquam maximus dui, nec iaculis nunc gravida et. Cras dapibus porta est, sed finibus arcu viverra in. Vivamus volutpat placerat urna, non varius sapien tempor sit amet. Cras quis dictum enim. Vestibulum ac rhoncus diam, non venenatis dolor. Curabitur ut velit tincidunt, pulvinar dolor sit amet, tincidunt arcu. Integer at ipsum eu ex blandit consectetur.</p>
  <p>Vivamus sed ullamcorper ex. Suspendisse potenti. Nam sodales hendrerit enim, non fringilla mauris gravida maximus. Sed pharetra purus eget quam auctor luctus. Aenean arcu nulla, aliquam ut consectetur non, tincidunt eget sem. Aliquam tristique mattis urna, a posuere justo dapibus nec. Aenean non lacinia nisl. Aenean ac bibendum eros. Integer posuere enim tempor auctor venenatis. Etiam convallis nunc at eros ultrices, et elementum erat ultrices.</p>
    <p>In aliquam maximus dui, nec iaculis nunc gravida et. Cras dapibus porta est, sed finibus arcu viverra in. Vivamus volutpat placerat urna, non varius sapien tempor sit amet. Cras quis dictum enim. Vestibulum ac rhoncus diam, non venenatis dolor. Curabitur ut velit tincidunt, pulvinar dolor sit amet, tincidunt arcu. Integer at ipsum eu ex blandit consectetur.</p>
  </section>
  
  <section id="eighth">
    <h2>Eighth section</h2>
    <p>Vivamus in elit non turpis eleifend egestas eget scelerisque turpis. Integer semper eget sem vel porttitor. Phasellus nec rhoncus elit. Donec feugiat mollis dui ac vehicula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.</p>
    <p>In aliquam maximus dui, nec iaculis nunc gravida et. Cras dapibus porta est, sed finibus arcu viverra in. Vivamus volutpat placerat urna, non varius sapien tempor sit amet. Cras quis dictum enim. Vestibulum ac rhoncus diam, non venenatis dolor. Curabitur ut velit tincidunt, pulvinar dolor sit amet, tincidunt arcu. Integer at ipsum eu ex blandit consectetur.</p>
    <p>Fusce quis rutrum turpis. Proin pulvinar, nisi id porttitor laoreet, neque diam finibus eros, a aliquam nibh libero quis nisi. Morbi vehicula tincidunt maximus.</p>
  </section>
  
</article>
              
            
!

CSS

              
                 @import url('https://fonts.googleapis.com/css2?family=PT+Mono&family=PT+Sans:wght@400;700&display=swap');

/* === Grid and base layout === */

:root {
  --highlight: #91cb3e;
  --body-pad-top: 70px;
  margin: 0;
  padding: 0;
}

body {
  display: grid;
  margin: 0;
  padding:  var(--body-pad-top) 45px;
  min-width: 350px;
  grid: auto / 200px minmax(300px, 500px);
  align-items: start;
  justify-content: center;
  gap: 20px;
  font: 16px/1.3 'PT Sans', sans-serif;
  color: #333;
  background: #edf2f4;
}

nav {
  position: sticky;
  left: 0;
  top: var(--body-pad-top);
  line-height: 2;
}

article {
  margin-top: -50px;
  padding-top: 50px;
}

/* === Navbar and marker === */

nav ul {
  margin: 0;
  padding-left: 10px;
  list-style: none;
}

nav li > ul {
  padding-left: 30px;
}

nav a {
  display: inline-block;
  color: #7f7f7f;
  text-decoration: none;
  transition: all 300ms ease-in-out;
}
  
nav li.visible > a {
  color: #222;
  transform: translate(5px);
}

nav svg {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
}

nav path {
  transition: all 300ms ease;
  fill: transparent;
  stroke: var(--highlight);
  stroke-width: 3px;
  stroke-dasharray: 0 0 0 1000;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* === Body contents === */

section[id] {
  margin: 25px 0;
  padding: 25px 0;
}

h1, h2 {
  margin: 0;
  padding: 0;
  font-size: 36px;
}

h2 {
  padding-left: 10px;
  border-left: 4px solid var(--highlight);
  font-size: 24px;
}

h1 span,
.subheading {
  display: block;
  color: #666;
  font: 700 20px sans-serif;
}

article a {
  color: #222;
  text-decoration-color: var(--highlight);
}

article a:hover {
  margin: -2px;
  padding: 2px;
  border: 0;
  background: var(--highlight);
}

code {
  margin: -2px;
  padding: 2px;
  background: rgba(0,0,0,.05);
  font-family: 'PT Mono', monospace;
  color: #333;
}

/* === Adjustments === */

/* Scroll all the way up even if the first section starts a couple hundred pixels down the page */
section:first-of-type {
  margin-top: -300px;
  padding-top: 350px;
}

article,
section:last-of-type,
section:last-of-type p:last-of-type {
  margin-bottom: 0;
  padding-bottom: 0;
}
              
            
!

JS

              
                
// === Vars ===

const elementsToObserve = document.querySelectorAll('section[id]'),
			visibleClass = 'visible',
      nav = document.querySelector('nav'),
      navPath = nav.querySelector('svg path'),
      navListItems = [...nav.querySelectorAll('li')],
      navItems = navListItems.map(listItem => {

          const anchor = listItem.querySelector('a'),
                targetID = anchor && anchor.getAttribute('href').slice(1),
                target = document.getElementById(targetID);

          return { listItem, anchor, target };

        })
        .filter(item => item.target);

// === Functions ===

function drawPath() {

  let path = [], 
      pathIndent;

  navItems.forEach((item, i) => {
    const 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 {
      
      if(pathIndent !== x)
        path.push('L', pathIndent, y);
      
      path.push('L', x, y);
      
      navPath.setAttribute('d', path.join(' '));
      item.pathStart = navPath.getTotalLength() || 0;
      path.push('L', x, y + height);  
    }
    
    pathIndent = x;
    navPath.setAttribute('d', path.join(' '));
    item.pathEnd = navPath.getTotalLength();
  });
}

function syncPath() {
  
  const someElsAreVisible = () => 
          nav.querySelectorAll(`.${visibleClass}`).length > 0,
        thisElIsVisible = el =>
          el.classList.contains(visibleClass),
        pathLength = navPath.getTotalLength();

  let pathStart = pathLength,
      pathEnd = 0,
      lastPathStart,
      lastPathEnd;
  
  navItems.forEach(item => {
    if(thisElIsVisible(item.listItem)) {
      pathStart = Math.min(item.pathStart, pathStart);
      pathEnd = Math.max(item.pathEnd, pathEnd);
    }
  });

  if(someElsAreVisible() && pathStart < pathEnd) {
    
    if(pathStart !== lastPathStart || pathEnd !== lastPathEnd) {
      
      const dashArray = `1 ${pathStart} ${pathEnd - pathStart} ${pathLength}`;
      
      navPath.style.setProperty('stroke-dashoffset', '1');
      navPath.style.setProperty('stroke-dasharray', dashArray);
      navPath.style.setProperty('opacity', 1);
		}
		
  } else { 
    navPath.style.setProperty('opacity', 0);
  }
	
  lastPathStart = pathStart;
  lastPathEnd = pathEnd;
}

function markVisibleSection(observedEls) {
  
  observedEls.forEach(observedEl => {
    
    const id = observedEl.target.getAttribute('id'),
        anchor = document.querySelector(`nav li a[href="#${ id }"]`);
    
    if(!anchor)
      return false
    
    const listItem = anchor.parentElement;

    if (observedEl.isIntersecting) {
      listItem.classList.add(visibleClass);
    } else {
      listItem.classList.remove(visibleClass);
    }
    syncPath();
  }); 
}

// === Draw path and observe ===

drawPath();

const observer = new IntersectionObserver(markVisibleSection);
elementsToObserve.forEach(thisEl => observer.observe(thisEl));
              
            
!
999px

Console