Create section navigation with React and Intersection Observer
Maciek Grzybek

Maciek Grzybek @maciekgrzybek

About: Senior software developer with ❤️ for JS, TS and React. Check out my blog www.maciekgrzybek.dev

Location:
Białystok
Joined:
Feb 20, 2019

Create section navigation with React and Intersection Observer

Publish Date: Jul 5 '19
96 43

Create section navigation with React and Intersection Observer

Introduction

For one of the last project at work, I had to create a section based navigation. The one that will highlight the correct navigation item when you scroll to the particular section of the page. After doing some reading and research I figured I could use Intersection Observer API. A really great, browser-native API that will fire up an event every time the desired element will show up in the viewport. You can read more about it here.

Today I want to show you what I’ve learned from that project. In this tutorial, we’ll build a mini page that will contain a description of different kind of Ricks from various dimensions(?). Check the working demo and github repo.

That is almost the exact copy of the project I’ve created at work (as much as I would want to, I’m not creating Rick and Morty websites). Anyway, let’s get to it.

Let’s get it started

Boilerplate

First of all, we’ll start by creating our project scaffold. We’ll use Create React App. If you ever used it before, I don’t have to explain the pros of it. If you haven’t quickly fix that mistake and go check the project website. Run this in your terminal:



    $ npx create-react-app rick-morty-section-navigation
    $ cd rick-morty-section-navigation
    $ npm start


Enter fullscreen mode Exit fullscreen mode

Boom, there we go — working boilerplate. Let clean up some default stuff that we won’t need here. Remove and move around some files so your project structure looks like this.



    rick-morty-section-navigation
    ├── README.md
    ├── node_modules
    ├── package.json
    ├── .gitignore
    ├── public
    │   ├── favicon.ico
    │   ├── index.html
    │   └── manifest.json
    └── src
        ├── index.js
        └── components
            ├── App.js
            └── app.css


Enter fullscreen mode Exit fullscreen mode

Don’t forget to remove references to removed files (index.css, serviceWorker.js, etc).

Data

As for the data layer, I decided to use Rick and Morty API (because why not?). Check it out here — it’s totally free and got lots of information about my favorite TV show. As a bonus, there’s a GraphQL endpoint, which we going to use instead of a classic REST API.

Go on and install urql, graphql and graphql-tag. Urql is a really great GraphQL client for React apps, which you can use as a component or hook (so hot right now).



    $ npm install --save urql graphql


Enter fullscreen mode Exit fullscreen mode

Now let’s wrap our App component in urql provider. It’s really straightforward, create a client with API URL and pass it to the provider.



    // src/index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import App from './components/App';
    import {Provider, createClient} from 'urql';

    const client = createClient({
      url: 'https://rickandmortyapi.com/graphql/',
    });

    ReactDOM.render(
        <Provider value={client}>
          <App />
        </Provider>,
        document.getElementById('root'));


Enter fullscreen mode Exit fullscreen mode

Now you can start querying data from the endpoint.



    // src/compoments/App.js
    import React from 'react';
    import {useQuery} from 'urql';
    import gql from 'graphql-tag';

    const getCharacters = gql`
      query AllCharacters{
        characters(filter: {name: "rick"}) {
          info {
            count
          }
          results {
            id
            name
            image
            species
            status
            location {
              name
            }
            origin {
              dimension
            }
          }
        }
      }
    `;

    export default function App() {
      const [res] = useQuery({
        query: getCharacters,
      });
    if (res.fetching || typeof res.data === 'undefined') {
        return (
          <div>Loading page...</div>
        );
      } else {
        return (
          <div>
                {
                  res.data.characters.results.map((item) => {
                    return (
                      <>
                        <div>
                          <img src={data.image}/>
                        </div>
                        <div className="character-block__text">
                          <h2>{data.name}</h2>
                          <p><b>Status</b>: {data.status}</p>
                          <p><b>Location</b>: {data.location ? data.location.name : '-'}</p>
                          <p><b>Species</b>: {data.species}</p>
                          <p><b>Dimension</b>: {data.origin.dimension || '-'}</p>
                        </div>
                      </>
                    );
                  })
                }
          </div>
        );
      }
    }


Enter fullscreen mode Exit fullscreen mode

Let’s see what’s happening here:

  • we create a simple query to the API

  • in our App component, we’re using useQuery to actually fetch the data from the API

  • if URQL is still fetching the data, we’re returning loading component,

  • if URQL has fetched the data, we’re looping through results and return list of character blocks

Structure

We have few unstyled divs with some simple data, but that’s obviously not enough. Before we add some styling and create two main components — Navigation, Character, let’s think about the state. In order to make it work, we need an active/current character state in the top component.



    // src/compoments/App.js

    *import* React, {useState} *from* 'react';
    ...
    ...
    const [activeCharacter, setActiveCharacter] = useState();


Enter fullscreen mode Exit fullscreen mode

Now we can pass the state, and method that will update the state to child components.



    // src/components/Navigation.js

    import React from 'react';

    export function Navigation({items, activeCharacter}) {
      function renderItems() {
        return items.map((item) => {
          const activeClass = activeCharacter === item.name
            ? 'navigation-list__item--active'
            : '';
          return (
            <li
              key={item.name}
              id={item.name}
              className={`navigation-list__item ${activeClass}`}>{item.name}</li>
          );
        });
      }
      return (
        <ul className="navigation-list">{renderItems()}</ul>
      );
    }

    // src/components/Character

    import React from 'react';

    export function Character({
      data,
      activeCharacter,
      setActiveCharacter,
    }) {
      const activeClass = activeCharacter === data.name
        ? 'character-block--active'
        : '';

    return (
        <div
          className={`character-block ${activeClass}`}
          id={data.name}>
          <div>
            <img src={data.image} alt="" className="character-block__image"/>
          </div>
          <div className="character-block__text">
            <h2>{data.name}</h2>
            <p><b>Status</b>: {data.status}</p>
            <p><b>Location</b>: {data.location ? data.location.name : '-'}</p>
            <p><b>Species</b>: {data.species}</p>
            <p><b>Dimension</b>: {data.origin.dimension || '-'}</p>
          </div>
        </div>
      );
    }

    // src/components/App.js

    ...

    import {Navigation} from './Navigation';
    import {Character} from './Character';

    export default function App() {

    ...

    if (res.fetching || typeof res.data === 'undefined') {
        return (
          <div>Loading...</div>
        );
      } else {
        const characters = res.data.characters.results.slice(0, 9);
        return (
          <>
            <div className="page-wrapper">
              <aside className="sidebar">
                <Navigation
                  items={characters}
                  activeCharacter={activeCharacter}/>
              </aside>
              <div className="content">
                <div className="page-intro">
                  <h1 className="page-title">Check out these cool Morty&apos;s!</h1>
                  <p>This simple page is an example of using Intersection Observer API with React.
                  </p>
                </div>
                {
                  characters.map((item) => {
                    return (
                      <Character
                        key={item.name}
                        activeCharacter={activeCharacter}
                        data={item}
                        setActiveCharacter={setActiveCharacter}/>
                    );
                  })
                }
              </div>
            </div>
          </>
        );
      }


Enter fullscreen mode Exit fullscreen mode

Also, let’s add some basic styles (don’t forget to import them in app.js):



    /* Mobile styles */
    * {
      box-sizing: border-box;
    }
    body {
      color: #282c34;
      font-family: 'Roboto Mono', monospace;
      padding: 0;
      margin: 0;
      width: 100%;
      position: relative;
      overflow-x: hidden;
    }
    .page-title {
      margin-bottom: 2rem;
    }
    .page-intro {
      max-width: 700px;
      margin-bottom: 3rem;
    }
    .page-wrapper {
      padding: 20px 15px 20px;
      width: 100%;
      max-width: 1300px;
      display: flex;
    }
    .sidebar {
      display: none;
    }
    .character-block {
      display: flex;
      margin-bottom: 2rem;
      transition: .3s;
      flex-direction: column;
    }
    .character-block--active {
      background: #faf575;
    }
    .character-block__image {
      width: 100%;
    }
    .character-block__text {
      padding: 1rem;
    }

    /* Tablet landscape styles */
    @media screen and (min-width: 768px) {
      .page-wrapper {
        padding-bottom: 120px;
      }
      .sidebar {
        display: flex;
        flex: 1;
      }
      .content {
        flex: 2.1;
      }
      .character-block {
        flex-direction: row;
      }
      .character-block__image {
        margin-right: 2rem;
        display: flex;
        align-self: center;
      }
      .character-block__text {
        padding: 0 1rem;
        align-self: center;
      }

    .navigation-list {
        position: fixed;
        top: 50%;
        transform: translate3d(0,-50%,0);
        left: -10px;
        list-style: none;
      }
      .navigation-list__item {
        font-size: 0.9rem;
        max-width: 200px;
        margin-bottom: 0.5em;
        transition: .3s;
        cursor: pointer;
      }
      .navigation-list__item:hover {
        padding-left: 5px;
        background: #faf575;
      }
      .navigation-list__item--active {
        background: #faf575;
        padding-left: 15px;
      }
    }

    /* Tablet vertical styles */
    @media screen and (min-width: 1024px) {
      .sidebar {
        min-width: 250px;
      }
      .content {
        flex: 2.5;
      }
    }
    /* Desktop styles */
    @media screen and (min-width: 1140px) {
      .sidebar {
        min-width: 250px;
      }
      .character-block {
        margin-bottom: 5rem;
      }
      .character-block__image {
        margin-right: 2rem;

      }
      .character-block__text {
        align-self: center;
      }
    }


Enter fullscreen mode Exit fullscreen mode

So far, so good. If you followed the instructions you should get something similar to this:

Nothing cool about it, just a bunch of Ricks. To make it more interactive, we need to add Intersection Observer, to detect which Rick-section is currently in the middle and make it the active one.

Intersection Observer API

What exactly is Intersection Observer API? It allows observing the intersection of an element with viewport or ancestor element. We could use it to determine if, for example, target visible to the user. What is really great about that API is it’s not causing reflow/layout trashing which is a very common performance issue (check this out for reference).

If you want to learn more about Intersection Observer, I encourage you to read the MDN documentation.

The code

We’ve covered the theory, now let’s get to the actual code. We want to add an observer to each Character component to detect if it’s intersecting with the viewport.



    // src/components/Character.js

    import React, {useEffect, useRef} from 'react';

    import React from 'react';

    export function Character({
      data,
      activeCharacter,
      setActiveCharacter,
    }) {
      const activeClass = activeCharacter === data.name
        ? 'character-block--active'
        : '';
     const characterRef = useRef(null);

    useEffect(() => {
        const handleIntersection = function(entries) {
          entries.forEach((entry) => {
            if (entry.target.id !== activeCharacter && entry.isIntersecting) {
              setActiveCharacter(entry.target.id);
            }
          });
        };
        const observer = new IntersectionObserver(handleIntersection);
        observer.observe(characterRef);
        return () => observer.disconnect(); // Clenaup the observer if 
        component unmount.
      }, [activeCharacter, setActiveCharacter, data, characterRef]);

    return (
        <div
          className={`character-block ${activeClass}`}
          id={data.name}
          ref={characterRef}>
          <div>
            <img src={data.image} alt="" className="character-block__image"/>
          </div>
          <div className="character-block__text">
            <h2>{data.name}</h2>
            <p><b>Status</b>: {data.status}</p>
            <p><b>Location</b>: {data.location ? data.location.name : '-'}</p>
            <p><b>Species</b>: {data.species}</p>
            <p><b>Dimension</b>: {data.origin.dimension || '-'}</p>
          </div>
        </div>
      );
    }


Enter fullscreen mode Exit fullscreen mode

Let’s see what happened here:

  • useEffect hook has been added

  • handleIntsersection method that will be fired every time the intersection event will occur, has been defined; if entry target is intersecting with the viewport, the function will set its ID as a new activeCharacter and lift the state up to the parent component

  • new Intersection Observer instance (with handleIntsersection as a callback) has been created

  • observer method has been invoked, with reference to current character wrapper (useRef hook was used)

Now every time the character component will become visible, it will trigger the observer callback, and set up the new active character. But we don’t want to section to become active, as soon as it reaches the viewport. We’re aiming for the center of the viewport. To achieve that we can pass rootMargin configuration to the observer. This property uses CSS-like syntax and allows us to extend or reduce the area in which element will trigger the callback.

In simple words: when our element will get into this blue area, the event will fire. We want the blue area to have a height of 1px and be placed in the center of the viewport. Let’s add some code then.



    // src/components/App.js

    export default function App() {

    ...

    const [pageHeight, setPageHeight] = useState();

    useEffect(() => {
        setPageHeight(window.innerHeight);
        window.addEventListener('resize', (e) => {
          setTimeout(() => {
            setPageHeight(window.innerHeight);
          }, 300);
        });
      }, []);

    ...

    }


Enter fullscreen mode Exit fullscreen mode

We’re setting the page height as a piece of state in here with useState. Also on window resize, we want to update that state to make sure is up to date. To make it more performant, we wrap it with setTimeout method to debounce the function. Let’s update the Character.js now.



    export function Character({
      data,
      activeCharacter,
      setActiveCharacter,
      pageHeight
    }) {

    ...

    const observerMargin = Math.floor(pageHeight / 2);
    useEffect(() => {

    const observerConfig = {
          rootMargin: `-${pageHeight % 2 === 0 ? observerMargin - 1 :    
    observerMargin}px 0px -${observerMargin}px 0px`,
        };
        const handleIntersection = function(entries) {
          entries.forEach((entry) => {
            if (entry.target.id !== activeCharacter && entry.isIntersecting) {
              setActiveCharacter(entry.target.id);
            }
          });
        };
        const observer = new IntersectionObserver(handleIntersection, observ);
        observer.observe(characterRef);
        return () => observer.disconnect(); // Clenaup the observer if 
        component unmount.
      }, [activeCharacter, setActiveCharacter, data, characterRef]);

    ...

    }


Enter fullscreen mode Exit fullscreen mode

We’re passing the page height as a props to Character.js component, calculate the correct rootMargin and pass it as a configuration object to new IntersectionObserver.



    // pageHeight === 700
    rootMargin: '349px 0px 350px 0px'
    // pageHeight === 701
    rootMargin: '350px 0px 350px 0px'


Enter fullscreen mode Exit fullscreen mode

That way we ensure that the target area will always have 1px height, and will be located in the center. At this point, you should have an almost fully working example. How cool and simple is that, right?

NOTE: To make it work on Internet Explorer browser, install Intersection Observer Polyfill and React App Polyfill.

Clickable links

One last thing we need to add is a clickable link feature. We’re going to use React’s createRef API and native scrollIntoView method.



    // src/components/App.js

    ...

    if (res.fetching || typeof res.data === 'undefined') {
        return (
          <div>Loading...</div>
        );
      } else {
        const characters = res.data.characters.results.slice(0, 9);

       const refs = characters.reduce((refsObj, character) => {
          refsObj[character.name] = createRef();
          return refsObj;
        }, {});

        const handleCLick = (name) => {
          refs[name].current.scrollIntoView({
            behavior: 'smooth',
            block: 'center',
          });
        };   

       return (
          <>
            <div className="page-wrapper">
              <aside className="sidebar">
                <Navigation
                  items={characters}
                  activeCharacter={activeCharacter}
                  handleCLick={handleCLick}/>
              </aside>
              <div className="content">
                <div className="page-intro">
                  <h1 className="page-title">Check out these cool Morty&apos;s!</h1>
                  <p>This simple page is an example of using Intersection Observer API with React.
                  </p>
                </div>
                {
                  characters.map((item) => {
                    return (
                      <Character
                        key={item.name}
                        activeCharacter={activeCharacter}
                        data={item}
                        setActiveCharacter={setActiveCharacter}
                        refs={refs}/>
                    );
                  })
                }
              </div>
            </div>
          </>
        );
      }

    // src/components/Navigation.js
    import React from 'react';

    export function Navigation({items, activeCharacter, handleCLick}) {
      function renderItems() {
        return items.map((item) => {
          const activeClass = activeCharacter === item.id
            ? 'navigation-list__item--active'
            : '';
          return (
            <li
              key={item.name}
              id={item.name}
              onClick={() => handleCLick(item.name)}
              className={`navigation-list__item ${activeClass}`}>{item.name}</li>
          );
        });
      }
      return (
        <ul className="navigation-list">{renderItems()}</ul>
      );
    }

    // src/components/Character.js
    import React, {useEffect} from 'react';

    export function Character({
      data,
      activeCharacter,
      setActiveCharacter,
      pageHeight = 100,
      refs,
    }) {
      const observerMargin = Math.floor(pageHeight / 2);
      const activeClass = activeCharacter === data.id
        ? 'character-block--active'
        : '';
      useEffect(() => {
        const observerConfig = {
          rootMargin: `-${pageHeight % 2 === 0 ? observerMargin - 1 : observerMargin}px 0px -${observerMargin}px 0px`,
        };
        const handleIntersection = function(entries) {
          entries.forEach((entry) => {
            if (entry.target.id !== activeCharacter && entry.isIntersecting) {
              setActiveCharacter(entry.target.id);
            }
          });
        };
        const observer = new IntersectionObserver(
            handleIntersection,
            observerConfig);
        observer.observe(refs[data.name].current);
        return () => observer.disconnect(); // Clenaup the observer if 
        component unmount.
      }, [activeCharacter, setActiveCharacter, observerMargin, refs, data, pageHeight]);

    return (
        <div
          className={`character-block ${activeClass}`}
          ref={refs[data.name]}
          id={data.id}>
          <div>
            <img src={data.image} alt="" className="character-block__image"/>
          </div>
          <div className="character-block__text">
            <h2>{data.name}</h2>
            <p><b>Status</b>: {data.status}</p>
            <p><b>Location</b>: {data.location ? data.location.name : '-'}</p>
            <p><b>Species</b>: {data.species}</p>
            <p><b>Dimension</b>: {data.origin.dimension || '-'}</p>
          </div>
        </div>
      );
    }


Enter fullscreen mode Exit fullscreen mode

Let go through that big chunk of code and figure out what just happened:

  1. we’ve created an object with refs for each character and passed it to the Character components, to populate it later with correct elements references

  2. we’ve created a method to handleClick event on navigation links and pass it to Navigation component, and attach it to each link element

  3. in Character.js we’ve removed createRef API, assigned ref to refs object instead and use refs[data.name].current as a target element in the observer

That’s all folks

As you can see, it’s really straightforward to set up Intersection Observer in your React project. Obviously, there are some ready-to-go components with that functionality, which I encourage you to use. I just thought it would be good to show you how the API really works.

I hope you enjoyed this tutorial, and if you’ve got any questions or comments, let me know in the comments section.

Comments 43 total

  • Maciek Grzybek
    Maciek GrzybekJul 6, 2019

    Hi Akmal, thanks for the comment. Good spot, I totally forgot about it :) I've already updated example code and the demo. Not sure what do you mean about that Codesandbox example though. Looks like the code it's spot on. Could you explain? Thanks :)

  • Luis Henrique
    Luis HenriqueJan 26, 2020

    Unnecessarily complicated, mixing graphql and a lot of non related stuff on an article about Intersection Observer + React. I'm sorry for the newbie people on web development who are reading this and have no idea on what is graphql and gql etc.

    • Maciek Grzybek
      Maciek GrzybekJan 26, 2020

      Well, that's your opinion :)

    • Maciek Grzybek
      Maciek GrzybekJan 26, 2020

      I can explain it to you if you need.

      • Aniketh Nair
        Aniketh NairFeb 11, 2020

        I believe this gives beginners the opportunity to upgrade themselves so thanks for that!

  • sandeshsapkota
    sandeshsapkotaMar 28, 2020

    HI Maciek ! I get so confuesd to implement on my project because of gql things. do you have same kind of more simplified examples ?

  • Sebastian Grąz
    Sebastian GrązApr 30, 2020

    Hi Maciek, thanks for the write-up! It helped me a lot in getting started with IO + hooks. In my own implementation I found a way to make the rootMargin configuration simpler than using a 1px pixel window height listener. While achieving the same thing.

    {
      rootMargin: "-50% 0px -50% 0px",
      threshold: 0,
    }
    
    Enter fullscreen mode Exit fullscreen mode

    Hope this helps someone else stumbling upon this post.

    • Maciek Grzybek
      Maciek GrzybekMay 1, 2020

      Oh wow, that's way better than my approach :) Awesome stuff, thanks :) BTW - great designs mate :)

  • Elias Günther
    Elias GüntherDec 9, 2020

    Thanks! Helped a lot!!

  • Lucius Emmanuel Emmaccen
    Lucius Emmanuel EmmaccenFeb 17, 2021

    Apt!

  • thatFemicode
    thatFemicodeDec 14, 2021

    Hi Maceik, came across this article and i must say it is really helpful but i am trying to create something like this website olaolu.dev/ where those boxes by the sides are the navigators but i keep getting error, do you by any means have a solution to something like that, i'd be really grateful, tried using static data and i am still getting the errors

    • Maciek Grzybek
      Maciek GrzybekDec 14, 2021

      Hey, I'd need more details, a repo or a code sandbox, it's really hard to help without more information :)

      • thatFemicode
        thatFemicodeDec 14, 2021

        I'd make a repo and share now

        • Maciek Grzybek
          Maciek GrzybekDec 14, 2021

          I'll take a look later today and will let you know :)

          • thatFemicode
            thatFemicodeDec 14, 2021

            Wow, thanks a lot you are so kind 🙏
            this is a link to a codesandbox
            codesandbox.io/s/tender-oskar-rvbz...

            • Maciek Grzybek
              Maciek GrzybekDec 14, 2021

              So it looks like you're not really storing anything in your refs object. As you can see in my example, ref needs to be attached to html element, like this:

               <div  ref={characterRef} />
              
              Enter fullscreen mode Exit fullscreen mode

              If you want to get the ref from inside of some other component, you'll have to use forwardRef -> reactjs.org/docs/forwarding-refs.html

              That's why you're getting errors

              • thatFemicode
                thatFemicodeDec 14, 2021

                Please is this an issue you could assist me in solving, just started using react of recent and i need this for the project.

                • thatFemicode
                  thatFemicodeDec 14, 2021

                  I'd be really grateful if you could assist me with this

                  • thatFemicode
                    thatFemicodeDec 14, 2021

                    @maciekgrzybek can you please assist with this, i have literally tried everything i know nothing just works, been on it sice you told me about the forwardref and still not gotten anywhere

                    • Maciek Grzybek
                      Maciek GrzybekDec 15, 2021

                      Sorry mate, I wish I could help but I'm really busy with my other projects. Everything you need to know should be in this article, basically ref needs to be attached to an actual dom element and currently you just passing them to the components. Some more reading ->

                      • thatFemicode
                        thatFemicodeDec 15, 2021

                        Thanks, I'd look for an alternative if i cant find a solution, really appreciate 🙏

                        • thatFemicode
                          thatFemicodeDec 17, 2021

                          Hi there @maciekgrzybek sorry for disturbing you agai, i found a way to make the scrolling to each section work but then i cant make the io and the active class work, could you please assit me with that in your free time. Here is a codepen link
                          codesandbox.io/s/eager-agnesi-virvq

                          • Maciek Grzybek
                            Maciek GrzybekDec 28, 2021

                            Hey man, sorry wasn't here for a while, did you manage do sort this out?

                            • thatFemicode
                              thatFemicodeDec 28, 2021

                              Sort of, found another way but your method looked really nice also wish I could have broken it down better
                              Here is a link to what I did with it
                              nba-landing.netlify.app/

                              • Maciek Grzybek
                                Maciek GrzybekDec 28, 2021

                                You were really close, you just didn't pass the correct name to activeCharacter state :)

                                • thatFemicode
                                  thatFemicodeDec 28, 2021

                                  Sorry could you do that and let me see where I made the mistake,probably fork the sandbox and send an updated link. Happy holidays

                                  • Maciek Grzybek
                                    Maciek GrzybekDec 28, 2021

                                    In this useEffect:

                                      useEffect(() => {
                                        const observerConfig = {
                                          rootMargin: `-${
                                            pageHeight % 2 === 0 ? observerMargin - 1 : observerMargin
                                          }px 0px -${observerMargin}px 0px`,
                                        };
                                        const handleIntersection = function (entries) {
                                          entries.forEach((entry) => {
                                            if (entry.target.id !== activeCharacter && entry.isIntersecting) {
                                              setActiveCharacter(entry.target.id);
                                            }
                                          });
                                        };
                                        const observer = new IntersectionObserver(
                                          handleIntersection,
                                          observerConfig
                                        );
                                        // observer.observe(refs.name);
                                        return () => observer.disconnect(); // Clenaup the observer if component unmount.
                                      }, [
                                        activeCharacter,
                                        setActiveCharacter,
                                        observerMargin,
                                        refs,
                                        items,
                                        pageHeight,
                                        name,
                                      ]);
                                    
                                    Enter fullscreen mode Exit fullscreen mode

                                    You need to observe for each element, now you're not really observing anything. Notice how in my tutorial, each character is a separate component, and each one of them has its own Observer attached to it.

                                    • thatFemicode
                                      thatFemicodeDec 28, 2021

                                      I keep getting the husband error
                                      Argument 1 ('target') to IntersectionObserver.observe must be an instance of Element

                                      • thatFemicode
                                        thatFemicodeDec 28, 2021

                                        Now I am observing for with refs.name but still getting that error

                                        • thatFemicode
                                          thatFemicodeDec 28, 2021

                                          Now I am observing with refs.name but still getting that error

                                          • Maciek Grzybek
                                            Maciek GrzybekDec 28, 2021

                                            There is no such a thing like refs.name in you code. Console log it and see what you are getting. If you want to access ref with specific name you need to access it with refs[name]. Also, you need to add a observer for each name.

                                            • thatFemicode
                                              thatFemicodeDec 28, 2021

                                              Thanks Maceik, i'll look at your approach again once i am free cus right now tbh i am not really getting what i am doing again cus i had to learn a lot two weeks ago

      • thatFemicode
        thatFemicodeDec 14, 2021

        github.com/thatFemicode/Landing
        Here is a link to the repo, i am trying to recreate what you did with the intersection observer api but for navigation like the link i sent to you where each section that comes in makes the circles background turn black

        • thatFemicode
          thatFemicodeDec 14, 2021

          I am not sure i am explaining well, instead of looping over several characters, the sections will have their own ref, i keep getting errors, been on it since yesterday night, if you notice from the link i sent earlier olaolu.dev/, there are boxes that keeps track of the section. do you get me now?, i'd be really grateful if you can assist me in making this work, thanks a lot

          • thatFemicode
            thatFemicodeDec 14, 2021

            I think i have a better explanation now, yes
            so with refernce to that website i sent olaolu.dev/ what i want to achieve with your approach using the IO API is to have a navigator (the boxes or circles) that can be clicked on and goes directly to a section either be it home or gallery and once a particular section is in view, the circle or box now has a style it gives like as shown in the website i sent. Just like yours using static data where you click on the names which serves as navigator and the particular character comes into the viewport then both the navigator and character get the yellow background, I keep getting errors since i am not looping over. i have done somethings on the github repo i sent. I'd be really greatful if you can assist me.

  • Alanominator
    AlanominatorJun 22, 2022

    I found bag. When you click navigation button and stop scroll by touching screen, button, that has been clicked, has active class until another button is clicked.

    I created own section-navigation. The same idea, but other decisions.

Add comment