I'm using a single page template with GatsbyJS on which the menu scrolls to the different sections of the same page (#home, #about, #portfolio, etc). Is there a way to set an active classname on the links, highlighting the link the user is on?
I'm using a single page template with GatsbyJS on which the menu scrolls to the different sections of the same page (#home, #about, #portfolio, etc). Is there a way to set an active classname on the links, highlighting the link the user is on?
Share asked Feb 3, 2018 at 8:11 YavorYavor 1491 silver badge15 bronze badges 1- makotot.github.io/react-scrollspy - Using this quick easy plugin can help anyone looking for this info – brooksrelyt Commented Jun 17, 2019 at 18:35
5 Answers
Reset to default 3Link provides two options for adding styles to the active link:
activeStyle
— a style object that will only be applied when the current item is active
activeClassName
— a class name that will only be added to the Link when the current item is active
Follow official docs: https://www.gatsbyjs/docs/gatsby-link/#add-custom-styles-for-the-currently-active-link
import React from "react"
import { Link } from "gatsby"
const SiteNavigation = () => (
<nav>
<Link
to="/"
{/* This assumes the `active` class is defined in your CSS */}
activeClassName="active"
>
Home
</Link>
<Link
to="/about/"
activeStyle={{ color: "red" }}
>
About
</Link>
</nav>
)
You can set the activeStyle
or activeClassName
prop to add styling attributes to the rendered element when it matches the current URL, and Gatsby also supports React Router's props exact
, strict
, isActive
, and location
. If any of these props are set, then React Router's NavLink
ponent will be used instead of the default Link.
Example:
import Link from "gatsby-link"
render () {
<div>
<Link
to="/another-page/"
activeStyle={{
color: 'red'
}}
innerRef={(el) => { this.myLink = el }}
>
Another page
</Link>
</div>
}
Visit https://github./gatsbyjs/gatsby/tree/master/packages/gatsby-link
I did it the hard way as I couldn't find another solution:
import React, { Component } from 'react';
import './Menu.css';
class Menu extends Component {
constructor(props) {
super(props)
this.state = {
home: true,
about: false,
portfolio: false
}
this.handleActive = this.handleActive.bind(this)
}
handleActive(button) {
switch (button) {
case 'home':
this.setState({
home: true,
about: false,
portfolio: false
});
break;
case 'about':
this.setState({
home: false,
about: true,
portfolio: false
});
break;
case 'portfolio':
this.setState({
home: false,
about: false,
portfolio: true
});
break;
default: break;
}
}
render() {
return (
<div id="nav-wrap">
<nav>
<input type="checkbox" id="checkbox1" />
<label htmlFor="checkbox1">
<ul className="menu first">
<li><a
className={this.state.home ? 'active' : null}
onClick={() => this.handleActive('home')}
href="#home">HOME</a></li>
<li><a
className={this.state.about ? 'active' : null}
onClick={() => this.handleActive('about')}
href="#about">ABOUT МЕ
</a></li>
<li><a
className={this.state.portfolio ? 'active' : null}
onClick={() => this.handleActive('portfolio')}
href="#portfolio">PORTFOLIO</a></li>
</ul>
<span className="toggle">☰</span>
</label>
</nav>
</div>
)
}
}
export default Menu;
As brooksrelyt noted in the ment above, you can easily use react-scrollspry
to add a unique class name to the hash link that has been clicked. Here is how I use it:
import { Text } from 'rebass/styled-ponents'
import Scrollspy from 'react-scrollspy'
<Scrollspy
items={['home', 'features', 'faq']}
currentClassName="isCurrent"
>
<Link to="#home">Home</Link>
<Link to="#features">Features</Link>
<Link to="#faq">FAQ</Link>
</Scrollspy>
<Text id="home">Home</Text>
<Text id="features" mt={'400vh'}>Features</Text>
<Text id="faq" mt={'150vh'}>FAQ</Text>
In short, you wrap your links with a Scrollspy
ponent and include (at the very least) two mandatory props: items
and currentClassName
.
items
is an array of the hash names (without the hash character)currentClassName
is the name of the class you want to add to the selected link.
NOTE: I included the rebass
Text ponent because it did not work properly for me when I used a simple div. You should read the documentation for how to use it in your particular case - as there are other props that may be needed in different situations.
I did a website some weeks ago with this feature. I created a function to know what section are the active and put it on the state in react. This function is called everytime the user move the scrollbar.
In render, I change de className of the elements depending the element of the state. In every section/element that i want to track I put an ID.
Util function:
/**
* Helper function to get an element's exact position
* @param {element} element
* @return {x,y}
*/
export function getPosition(el) {
var xPos = 0;
var yPos = 0;
while (el) {
if (el.tagName == "BODY") {
// deal with browser quirks with body/window/document and page scroll
var xScroll = el.scrollLeft || document.documentElement.scrollLeft;
var yScroll = el.scrollTop || document.documentElement.scrollTop;
xPos += (el.offsetLeft - xScroll + el.clientLeft);
yPos += (el.offsetTop - yScroll + el.clientTop);
} else {
// for all other non-BODY elements
xPos += (el.offsetLeft - el.scrollLeft + el.clientLeft);
yPos += (el.offsetTop - el.scrollTop + el.clientTop);
}
el = el.offsetParent;
}
return {
x: xPos,
y: yPos
};
}
On React ponent:
ponentDidMount() {
window.addEventListener('scroll', this.handleScroll);
this.handleScroll();
}
ponentWillUnmount() {
window.removeEventListener('scroll', this.handleScroll);
}
handleScroll() {
const {inAnimation} = this.state;
let activeElement = false;
this.props.data.items.forEach((value,i) => {
let element = document.getElementById(value.url.substring(1));
if(getPosition(element).y <= 0){
activeElement = value.url;
}
});
this.setState({
activeElement
});
}
}
render(){
...
items.map((item, i) => {
return <li key={i}>
<a className={'menu-item' + (activeElement == item.url ? ' active': '')}>{item.title}</a>
</li>;
});
...
}