Left ArrowBack

notes / JavaScript / Web API / intersection observer / dynamic header

dynamic header

A website header & article footer. Including the site's logo, menu button, author name & publish date.

Dynamic Header

Planted

Status: seed

inverse.com have designed a sophiscated color theme technique. The page has a series of articles running down the page. Each article is assigned a different color. Artifacts within each article, such as the footer, use this color. Providing contrast between articles, reducing the cognitive load required to differentiate between them. Only some artifacts use the color. Just enough to get the point across. The majority of an article's text uses a white shade. This prevents the color from being overwhelming & making the copy hard to read.

Another technique being used is articles having an empty space at the top. The page's header is fixed. When the user scrolls a new article into view, the header fits into this space. Like 2 pieces of a puzzle coming together. This idea reinforced by the header's color changing to match an article's theme color. Together, these communicate the header & articles are parts of a single thing, rather than 2 seperate items.

Articles

To create this page, start with the articles (without color). An article is made up of:

  • footer containing an author link & publish date,
  • drop head (a headline that elaborates on the main heading),
  • heading &
  • body copy.
placeholder

This note focuses on specific elements so there will be some differences between this & inverse.com's page.

localhost:3000

  <article>
    <footer>
      <div class="line"></div>
      <a rel="author">George Orwell</a>
      <time datetime="1984-01-05">Jan. 05, 1984</time>
    </footer>
    <p class="dropHead">Drop head</p>
    <h1>Article 1</h1>
    <p class="copy">
      <span>Lorem ipsum</span> dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
    </p>
  </article>

Themes

To add a color theme to each article:

  1. 1. Add a data-attribute to each article.
        Example: <article data-color="orange">
  2. 2. For each color, create a CSS variable.
        Example: --color-orange: hsl(17, 100%, 58%);
  3. 3. Create another CSS variable, color-theme, & set its value depending on the attribute.
        Example: [data-color="orange"] { --color-theme: var(--color-orange); }
  4. 4. For each element that uses a theme color, set its color to color-theme.
        Example: article footer time { color: var(--color-theme); }

localhost:3000

:root {
  --color-orange: hsl(17, 100%, 58%);
  --color-yellow: hsl(38, 76%, 49%);
  --color-green: hsl(131, 63%, 30%);
}

[data-color="orange"] {
  --color-theme: var(--color-orange);
}

[data-color="yellow"] {
  --color-theme: var(--color-yellow);
}

[data-color="green"] {
  --color-theme: var(--color-green);
}

article footer .line {
  background: var(--color-theme);
}

article footer a[rel="author"] {
  color: var(--color-theme);
}

article footer time {
  color: var(--color-theme);
}

article p.dropHead {
  color: var(--color-theme);
}

Page Header

The page header is made up of 2 SVG images; a site logo & menu icon. Both of these need to change color based on what article they are intersecting with. Therefore, the images' code needs to be moved inline (moved from an .svg to the .html file). When doing this, we lose the alt attribute for each image. To substitute, add a <title> with the alt value.

To set the header's color, we use the same approach as the articles, a data attribute & CSS variable.

localhost:3000

<header data-color="orange">
  <svg class="siteLogo" viewBox="0 0 180 27">
    <title>Inverse</title>
    <path d="M0,26.2h7.9c-0.2-4-0.2-7.4-0.2-12.7c0-5.4,0-8.8,0.2-12.7H0c0.1,3.9,0.1,7.3,0.1,12.7C0.1,18.8,0.1,22.3,0,26.2z" />
    <path d="M9.9,26.2H17c-0.1-1.6-0.1-5.7-0.1-11.5c0-2-0.1-4-0.3-6.1c0.9,1.4,2,2.8,3.2,4.2c4.3,4.6,11.1,11,12.7,13.4h8 c-0.1-3.3-0.1-7.1-0.1-12.7c0-5.4,0-9.8,0.1-12.7h-6.9c0.2,2.1,0.3,6.8,0.3,11.3c0,1.6,0.1,3.5,0.2,5.4c-0.7-1.3-1.7-2.4-2.6-3.6 C27.6,9.5,20.1,2.5,19.1,0.8H9.9C10,4.2,10,8.1,10,13.5C10,18.9,9.9,22.8,9.9,26.2z" />
    <path d="M42.7,26.2h7.2c1.6-2.4,8.4-9.1,12.7-13.7c1.2-1.4,7-8,9.9-11.7h-8.3C63,2.5,59.8,5.6,56.9,9c-3.3,3.8-7.4,7.8-7.8,8.6 c0.1-1.9,0.2-3.8,0.2-5.4c0-4.5,0.1-9.3,0.3-11.3h-6.9c0.1,2.9,0.1,7.3,0.1,12.7C42.8,19,42.8,22.8,42.7,26.2z" />
    <path d="M64.5,26.2h26.6c-0.1-0.8-0.1-2-0.1-3c0-1.1,0-2.4,0.1-3.1l-18.8,0v-4.3c5.7,0,14,0,18.4,0.1v-5.3 c-4.5,0.1-12.7,0.1-18.4,0.1V6.5c4.8,0,13.1,0,18.8,0c-0.1-0.6-0.1-2-0.1-2.9c0-0.9,0-2.1,0.1-2.8H74.2c-2.1,2.7-6.9,8.2-9.6,11.4 v1.4C64.6,19,64.6,22.8,64.5,26.2z" />
    <path d="M153.4,26.2H180c-0.1-0.8-0.1-2-0.1-3c0-1.1,0-2.4,0.1-3.1l-18.7,0v-4.3c5.6,0,13.9,0,18.3,0.1c0-1.3,0-4.1,0-5.3 c-4.5,0.1-12.8,0.1-18.4,0.1V6.5c4.8,0,13,0,18.7,0c-0.1-0.6,0-2,0-2.9c0-0.9,0-2.1,0-2.8h-26.6c0.1,3.3,0.1,7.3,0.1,12.7 C153.5,18.9,153.5,22.8,153.4,26.2z" />
    <path d="M130.7,18.2c0,1.8,1.5,2.6,7.1,2.6c6.1,0,7.1-0.8,7.1-1.9c0-1.5-0.8-2.2-8.3-3c-10.9-0.7-13.8-3.3-13.8-7.9 c0-4.8,4.5-7.9,14.6-7.9c10.8,0,14.6,2.3,14.6,7.1v1.5h-7.5V8c0-1.5-1.1-2.2-7.5-2.2c-5.3,0-6.4,0.7-6.4,1.8s0.7,1.8,8.6,2.6 c9.8,0.7,13.1,3,13.1,8.2s-3.8,8.3-14.6,8.3c-10.8,0-15-2.6-15-7.5v-1.5h7.9V18.2z" />
    <path d="M110.7,0.8c7.4,0,11.3,2.5,11.3,7.7c0,4.4-2.1,6.8-7.4,7.4v0.1c4.4,0.5,6.2,1.4,7.3,4.9c0.7,2.2,1.3,4.1,1.7,5.1h-7.2 c-1.2,0-1.4-0.3-2.2-3.4c-0.9-3.7-1.7-4.2-5.7-4.2h-8c0.1,3.1,0.1,5.8,0.4,7.7h-8c0-3.7,0.1-7.3,0.1-12.7c0-5.3,0-8.9-0.1-12.5 l0-0.2H110.7z M108.7,6.6c-1.6-0.1-5.9,0-8.4,0.1c0,2.1,0,4.2,0,6.2c1.7,0.1,5,0.1,8.7,0.1c3.9,0,5.5-1,5.5-3.3 C114.5,7.3,112.8,6.6,108.7,6.6z" />
  </svg>

  <svg class="menuIcon" viewBox="0 0 32 10">
    <title>Menu</title>
    <rect width="32" height="2" />
    <rect y="4" width="32" height="2" />
    <rect y="8" width="16" height="2" />
  </svg>
</header>

Observe

To make the header change color depending on which article it is intersecting, we first need to execute a callback when an intersection happens. This can be done with the Intersection Observer. We want:

  • the intersection area to be the viewport, minus the height of the header &
  • the callback to execute when 1px of an article enters the area or the last 1px leaves the area.
placeholder

localhost:3000

const pageHeader = document.querySelector("header");
const articles = document.querySelectorAll("article");

const options = {
  // root is only required here because this sandbox is in an iframe.
  // Omit to set viewport as root
  root: document,
  // Removes pageHeader height from top of the intersection area
  rootMargin: `${pageHeader.offsetHeight * -1}px 0px 0px 0px`,
  // Execute callback based on first / last pixel of an observed element
  threshold: 0,
};

function onObserve(entries) {
  console.log(`callback executed`)
}

const observer = new IntersectionObserver(onObserve, options);

articles.forEach((article) => {
  observer.observe(article);
});

Dynamic Color

If the user is scrolling down the page, the header's color should change when an article leaves the intersection area. Its new color should be the next article's.

placeholder

If the user is scrolling up the page, the color should change when an article enters the intersection area. Its new color should be the entering article's color.

placeholder

localhost:3000

// Turning a NodeList into an array because we require .findIndex
const articles = [...document.querySelectorAll("article")];

const DOWN = "DOWN";
const UP = "UP";

let prevScrollTop = 0;

function calcScrollDirection() {
  const { scrollTop } = document.documentElement;
  const direction = prevScrollTop < scrollTop ? DOWN : UP;
  prevScrollTop = scrollTop;

  return direction;
}

function getNextArticle(entry) {
  const index = articles.findIndex((article) => 
    article == entry.target);
  
  return articles[index + 1];
}

function shouldUpdate(entry, direction) {
  return (direction === DOWN && !entry.isIntersecting) || 
    (direction === UP && entry.isIntersecting);
}

function onObserve(entries) {
  entries.forEach((entry) => {
    const direction = calcScrollDirection();

    if (shouldUpdate(entry, direction)) {
      const target = direction === DOWN 
        ? getNextArticle(entry) 
        : entry.target;
      const color = target.getAttribute("data-color");
      pageHeader.setAttribute("data-color", color);
    }
  });
}

We now have a dynamic header.

Beyond Skeuomorphic

Skeuomorphic is the idea of moving something from the physical world to the digital. For example, during the early days of the Internet, newspapers were recreated as web pages. This was a good starting point, as it makes something new feel familiar & can speed understanding and acclimation. However, it doesn't take full advantage of what a computer can do. The idea of a fixed page header, a header that moves with the user, is a step up from skeuomorphism. It leverages what is possible in the digital space to provide a better UI. A dynamic header that changes color, depending on where the user is looking, takes this step even further.

Where to Next?

A sci-fi robot taxi driver with no lower body
└── JavaScript
└── Web API
└── intersection observer
Arrow pointing down
YOU ARE HERE
├── dynamic header
├── infinite scroll
└── Table of Contents