Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Headline Hopps #4

Open
9am opened this issue Apr 19, 2022 · 1 comment
Open

Headline Hopps #4

9am opened this issue Apr 19, 2022 · 1 comment
Assignees
Labels
browser extension Browser Extension javascript JavaScript

Comments

@9am
Copy link
Owner

9am commented Apr 19, 2022

Using 50 lines of plain javascript to build a browser add-on to show all the headlines on a page and navigate between them.

headline-hopps

hits

@9am
Copy link
Owner Author

9am commented Apr 19, 2022

Ever been looking through a long article without a side navigation section? Take Wikipedia for example.

wikipedia.mp4

It has navigation contents below the brief description section. But after scrolling down a few sections. I lost the connection between what I had been reading and what I'm about to read. A simple add-on(or extension) should solve the problem. So I built one.

Feature Preview

demo.mp4
  1. List all the headlines in the article.
  2. Click items to jump between sections.
  3. Highlight the section as you scroll the page.
  4. Toggle visibility of level.
  5. Re-select the area to collect headlines.
  6. Change the position.

Build a Simple Version in 50 Lines

  1. Collect headlines on the page to create a navigation list.

    const root = document.body; // DOM from which to collect headlines
    const pick = ["h1", "h2", "h3", "h4"]; // tagNames to pick;
    
    // collect headlines
    const headlines = [...root.querySelectorAll(pick.join(","))];
    // create a <li> for each headlines
    const items = headlines.map(
        (node, index) => `
        <li>
            ${node.textContent}
        </li>
    `
    );
    
    // wrap <li> with <ul>
    const nav = document.createElement("ul");
    nav.id = "nav";
    nav.innerHTML = items.join("");
    
    document.body.prepend(nav);

    Edit step1

step1.mp4
  1. Handle the indent and jumping.
    We add margin to show a different level of the item. And place <a> in the navigation item with the headline id so that it anchors to the headline when clicking.

    const root = document.body; // DOM from which to collect headlines
    const pick = ["h1", "h2", "h3", "h4"]; // tagNames to pick;
    
    // collect headlines
    const headlines = [...root.querySelectorAll(pick.join(","))];
    // create a <li> for each headlines
    const items = headlines.map((node, index) => {
        // respect the origin id
        const id = node.id || `hopps-${index}`;
        // use tagName to level
        const lv = node.tagName.match(/\d/)?.[0] || 1;
        return `
            <li style="margin-left:${lv * 16}px">
                <a href="#${id}">${node.textContent}</a>
            </li>
        `;
    });
    
    // wrap <li> with <ul>
    const nav = document.createElement("ul");
    nav.id = "nav";
    nav.innerHTML = items.join("");
    
    document.body.prepend(nav);

    Notice: The actual situation to choose a level is more complicated. It might be like this:

    h1
    ----h3
    ------h4
    ------h4
    ----h3
    --------h5
    ----------h6
    --------h5
    

    which should be indented like this:

    h1
    --h3
    ----h4
    ----h4
    --h3
    ----h5
    ------h6
    ----h5
    

    So we just simplify it by the tagName <h($level)>

    Throw some CSS to make it look nicer. Whoa, We’re Halfway There.

    scroll-behavior: smooth to make the scroll fancy.
    backdrop-filter: blur() to make a glass effect.

    Edit step2

step2.mp4
  1. Highlight the right item when scrolling the page.

    Let's think about how to do this. There could be only one item highlighted. It should be the topmost in the viewport. So we listen to 'scroll', and loop through the headlines from top to bottom, and the first one in the viewport is the target. Then highlight the item in navigation whose href anchor to id.

    document.addEventListener("scroll", () => {
        const { height } = window.screen;
        let activeID = null;
        headlines
            .map((node) => [node.id, node.getClientRects()?.[0] || {}])
            .some(([id, rect]) => {
                const { top, bottom } = rect;
                if (bottom >= 0 && top < height) {
                    // first one in viewport
                    activeID = id;
                    return true;
                }
                return false;
            });
        if (!activeID) {
            return;
        }
        [...nav.querySelectorAll("a")].forEach((node) => {
            const [, match] = node.href.match(/[^#]*#([^#]*)$/) || [];
            node.classList.toggle("highlight", match === activeID);
        });
    });

    Edit step3

    (Notice: It's better to add throttle to the scroll handler to prevent unnecessary calculation, we're not gonna do that since it's a simple version :)

step3.mp4

The Whole Package

Choose your browser to install the add-on.

The source code is hereheadline hopps.

Hope you enjoy it, I'll see you next time.


@9am 🕘

@9am 9am added javascript JavaScript browser extension Browser Extension labels Apr 19, 2022
@9am 9am self-assigned this Apr 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
browser extension Browser Extension javascript JavaScript
Projects
None yet
Development

No branches or pull requests

1 participant