html {
height: 100%;
}
body {
display: flex;
flex-direction: column;
margin: auto;
background-color: white;
font-family: 'Cabin', sans-serif;
font-weight: 400;
line-height: 1.65;
color: #333;
font-size: 20px;
&::before {
content: "";
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: .05;
z-index: -1;
background: url("https://static.thenounproject.com/png/2590971-200.png");
background-size: 200px 200px;
background-repeat: repeat;
background-position: center center;
}
}
h1, h2, h3, h4, h5 {
font-family: 'Oswald', sans-serif;
margin: 2.75rem 0 1.05rem;
font-weight: 400;
line-height: 1.15;
}
h1 {
font-size: 3.052em;
}
h2 {font-size: 2.441em;}
h3 {font-size: 1.953em;}
h4 {font-size: 1.563em;}
h5 {font-size: 1.25em;}
.wrapper {
margin: 0 auto;
display: grid;
padding: 10px;
grid-template-columns: 300px auto;
grid-gap: 30px;
align-items: flex-start;
}
.is-active {
font-weight: 700;
color: #bb2205;
}
View Compiled
async function fetchAndParseMarkdown() {
const url = 'https://gist.githubusercontent.com/lisilinhart/e9dcf5298adff7c2c2a4da9ce2a3db3f/raw/2f1a0d47eba64756c22460b5d2919d45d8118d42/red_panda.md'
const response = await fetch(url)
const data = await response.text()
const htmlFromMarkdown = marked(data, { sanitize: true });
return htmlFromMarkdown
}
function generateLinkMarkup($headings) {
console.log($headings)
const parsedHeadings = $headings.map(heading => {
return {
title: heading.innerText,
depth: heading.nodeName.replace(/\D/g,''),
id: heading.getAttribute('id')
}
})
const htmlMarkup = parsedHeadings.map(h => `
<li class="${h.depth > 1 ? 'pl-4' : ''}">
<a href="#${h.id}">${h.title}</a>
</li>
`)
const finalMarkup = `
<ul>${htmlMarkup.join('')}</ul>
`
return finalMarkup
}
function updateLinks(visibleId, $links) {
$links.map(link => {
let href = link.getAttribute('href')
link.classList.remove('is-active')
if(href === visibleId) link.classList.add('is-active')
})
}
function handleObserver(entries, observer, $links) {
entries.forEach((entry)=> {
const { target, isIntersecting, intersectionRatio } = entry
if (isIntersecting && intersectionRatio >= 1) {
const visibleId = `#${target.getAttribute('id')}`
updateLinks(visibleId, $links)
}
})
}
function createObserver($links) {
const options = {
rootMargin: "0px 0px -200px 0px",
threshold: 1
}
const callback = (e, o) => handleObserver(e, o, $links)
return new IntersectionObserver(callback, options)
}
async function init() {
// Part 1
const $main = document.querySelector('#content');
const $aside = document.querySelector('#aside');
const htmlContent = await fetchAndParseMarkdown();
$main.innerHTML = htmlContent
// Part 2
const $headings = [...$main.querySelectorAll('h1, h2')];
const linkHtml = generateLinkMarkup($headings);
$aside.innerHTML = linkHtml
// Part 3
const motionQuery = window.matchMedia('(prefers-reduced-motion)')
const $links = [...$aside.querySelectorAll('a')]
const observer = createObserver($links)
$headings.map(heading => observer.observe(heading))
}
init();
View Compiled