最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to highlight navbar links on page scroll in React - Stack Overflow

programmeradmin2浏览0评论

I found an example in Codepen of what I am wanting to do in my React Application. When I scroll down the the page it should highlight that section in my navbar, which is sticky at the top of the page. My app is a single page that contains about 5 to 6 sections that will be index/inline pages, so as the user scrolls down, it will take them to that next section. What approach should I take to incorporate this? More specifically, should I just use React, should I implement React Router Dom? What is the best approach to acplish this functionality in the most general sense?

App 

import React, {Component} from 'react';
import Footer from './ponents/footer/Footer';
import Hero from './ponents/hero/Hero';
import Resume from './ponents/resume/Resume';
import About from './ponents/about/About';
import Contact from './ponents/contact/Contact';
import Navbar from './ponents/navbar/Navbar';
import './App.css';

class App extends Component {
  constructor() {
    super();
    this.state = {

    }
  }

  ponentWillMount() {
    window.addEventListener('scroll', this.navHighlighter);
  }

  navHighlighter() {}

  render() {
    return (
      <div className="App">
        <Navbar />
        <Hero/>
        <About/>
        <Resume/>
        <Contact/>
        <Footer/>
      </div>
    );
  }
}

Navbar 

mport React, {Component} from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import { faBars } from '@fortawesome/free-solid-svg-icons'
import './navbar.css';

class Navbar extends Component {
    constructor() {
        super();
        this.state = {
            addClass: '',
            showMenu: ''
        }
        this.handleScroll = this.handleScroll.bind(this);
        this.toggleMenu = this.toggleMenu.bind(this);
    }

    ponentDidMount() {
        window.addEventListener("scroll", this.handleScroll)
    }

    handleScroll() {
        if (window.scrollY > 0) {
            this.setState({addClass: 'sticky'})
        } else {
            this.setState({addClass: ''})
        }
    }

    toggleMenu() {
        let { showMenu } = this.state;
        this.setState({showMenu: showMenu === 'open' ? '' : 'open'})
    }

    render() {
        return (
            <div>
                <header onScroll={this.handleScroll} className={this.state.addClass}>
                    <a href="#" className="logo">Jeremy</a>
                    <nav className={this.state.showMenu}>
                        <ul>
                            <li><a href="#" className="active">Home</a></li>
                            <li><a href="#about">About</a></li>
                            <li><a href="#skills">Skills</a></li>
                            <li><a href="#projects">Projects</a></li>
                            <li><a href="#contact">Contact</a></li>
                        </ul>
                    </nav>
                    <div className="menu-toggle" onClick={this.toggleMenu}><FontAwesomeIcon className='icon' icon={faBars} /></div>
                </header>
            </div>
        );
    }
}

export default Navbar;

I found an example in Codepen https://codepen.io/dbilanoski/pen/LabpzG?editors=1010 of what I am wanting to do in my React Application. When I scroll down the the page it should highlight that section in my navbar, which is sticky at the top of the page. My app is a single page that contains about 5 to 6 sections that will be index/inline pages, so as the user scrolls down, it will take them to that next section. What approach should I take to incorporate this? More specifically, should I just use React, should I implement React Router Dom? What is the best approach to acplish this functionality in the most general sense?

App 

import React, {Component} from 'react';
import Footer from './ponents/footer/Footer';
import Hero from './ponents/hero/Hero';
import Resume from './ponents/resume/Resume';
import About from './ponents/about/About';
import Contact from './ponents/contact/Contact';
import Navbar from './ponents/navbar/Navbar';
import './App.css';

class App extends Component {
  constructor() {
    super();
    this.state = {

    }
  }

  ponentWillMount() {
    window.addEventListener('scroll', this.navHighlighter);
  }

  navHighlighter() {}

  render() {
    return (
      <div className="App">
        <Navbar />
        <Hero/>
        <About/>
        <Resume/>
        <Contact/>
        <Footer/>
      </div>
    );
  }
}

Navbar 

mport React, {Component} from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import { faBars } from '@fortawesome/free-solid-svg-icons'
import './navbar.css';

class Navbar extends Component {
    constructor() {
        super();
        this.state = {
            addClass: '',
            showMenu: ''
        }
        this.handleScroll = this.handleScroll.bind(this);
        this.toggleMenu = this.toggleMenu.bind(this);
    }

    ponentDidMount() {
        window.addEventListener("scroll", this.handleScroll)
    }

    handleScroll() {
        if (window.scrollY > 0) {
            this.setState({addClass: 'sticky'})
        } else {
            this.setState({addClass: ''})
        }
    }

    toggleMenu() {
        let { showMenu } = this.state;
        this.setState({showMenu: showMenu === 'open' ? '' : 'open'})
    }

    render() {
        return (
            <div>
                <header onScroll={this.handleScroll} className={this.state.addClass}>
                    <a href="#" className="logo">Jeremy</a>
                    <nav className={this.state.showMenu}>
                        <ul>
                            <li><a href="#" className="active">Home</a></li>
                            <li><a href="#about">About</a></li>
                            <li><a href="#skills">Skills</a></li>
                            <li><a href="#projects">Projects</a></li>
                            <li><a href="#contact">Contact</a></li>
                        </ul>
                    </nav>
                    <div className="menu-toggle" onClick={this.toggleMenu}><FontAwesomeIcon className='icon' icon={faBars} /></div>
                </header>
            </div>
        );
    }
}

export default Navbar;
Share Improve this question asked Aug 23, 2020 at 7:06 Jeremy MarkJeremy Mark 831 gold badge1 silver badge5 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 3

Create refs for each of your sections say sampleRef that you need to keep track of. With the sampleRef you can get position of section by using sampleRef.current.offsetTop. This gives absolute position of elements in the page. To get position of page use window.scrollY. Then you can pare offsetTop of all the sections with scrollY. The section which has smallest difference between offsetTop and scrollY is the active section. To find that I use divide and conquer to optimize the pare method.

I have create a codesandbox check it here. There you can checkout the file ScrollHighlightNabbar.js

I just had the same exact problem too and this is how I implemented it.

  1. Create forward refs for each of your sections.
  2. In your onScroll event handler, create the logic for making a nav item active. Use the previously created refs to access the height properties of your section.
  3. Set the active/inactive states of your nav items. In my case I made refs of the nav items and edited the classes directly through them instead of relying on states because I wanted to prevent a rerender on each scroll.
const aboutRef = useRef();

useEffect(() => {
        const handleScroll = (e) => {
            if (window.scrollY >= 0 && window.scrollY <= window.innerHeight / 2) {
                // Set states for nav items here if the user is on the first section
            } else if (aboutRef.current.offsetTop - window.scrollY < window.innerHeight / 2 && stuffRef.current.offsetTop - window.scrollY >= window.innerHeight / 2) {
                // For the about section
            } else {
                // Etc...
            }
        }
        document.addEventListener('scroll', handleScroll);
        return () => {
            document.removeEventListener('scroll', handleScroll);
        }
    }, [])

return (
    <nav>
        ...

        <li className={stateActiveAbout ? 'active' : 'not-active'}>About</li>
        ...

    </nav>

    <About ref={aboutRef} .../>
)

Something to that effect. I'm sure the code can be improved upon but I hope you get the idea. The above is a functional ponent so you'll have to convert yours.

I'm not quite sure if this approach violates any React 'rules' but it works.

The best and easiest method I got was:

  • First: Add a CSS property scroll-behaviour:smooth; to Html and body tags, this will make the scrolling smooth when section HashLink is clicked.

  • Second: The auto highlight of Nav-links can be done by a using the react-scrollspy-highlight package it will do the task perfectly. goto https://www.npmjs./package/react-scrollspy-highlight for docs.

You can use <NavLink /> with activeClassName attribute.

<NavLink to="#about" activeClassName={"active"} className={"anything"}>About</NavLink>
发布评论

评论列表(0)

  1. 暂无评论