I'm working on a landing page with a scrollspy navigation bar on the left. I want the scrollspy's background and text colors to change dynamically based on the background color.
For example, if the section has a dark background, I want the scrollspy to have a light background (and vice versa). I'm stuck on how to detect the background color of sections and adjust the scrollspy's color accordingly.
here is the behavior i actually want in scrollspy and its text, I have made the navigation and its functional too but all i need is to change the colors, like, on light backgrounds, scrollspy naviagtion should be dark and on light background, it should be dark:
Here is the reference website : MG Motor Europe:
Here's the code I currently have:
"use client";
import { useEffect, useState } from "react";
const sections = [
{ id: "MG HS", name: "MG HS" },
{ id: "MG Info", name: "MG HS" },
{ id: "safety", name: "Safety" },
{ id: "car-performance", name: "Car Performance" },
{ id: "plugin-hybrid", name: "Plugin Hybrid" },
{ id: "mg-pilot", name: "MG Pilot" },
{ id: "car-design", name: "Car Design" },
{ id: "comfort", name: "Comfort" },
{ id: "car-technology", name: "Car Technology" },
{ id: "mg-view", name: "MG View" },
];
const ScrollSpy = () => {
const [activeSection, setActiveSection] = useState<string>("");
const [hoveredSection, setHoveredSection] = useState<string>("");
const [showScrollSpy, setShowScrollSpy] = useState<boolean>(false);
useEffect(() => {
const handleScroll = () => {
const scrollPosition = window.scrollY + 60;
const sectionElements = document.querySelectorAll("div[id]");
let currentSectionId = "";
let anySectionVisible = false;
sectionElements.forEach((element) => {
const section = element as HTMLElement;
const sectionTop = section.getBoundingClientRect().top + window.scrollY;
const sectionBottom = sectionTop + section.offsetHeight;
if (scrollPosition >= sectionTop && scrollPosition < sectionBottom) {
currentSectionId = section.getAttribute("id") || "";
anySectionVisible = true;
}
});
if (!hoveredSection) {
setActiveSection(currentSectionId);
}
setShowScrollSpy(anySectionVisible);
};
window.addEventListener("scroll", handleScroll);
handleScroll();
return () => window.removeEventListener("scroll", handleScroll);
}, [hoveredSection]);
return (
<div
className={`hidden md:flex md:fixed md:top-[160px] md:left-[74px] md:h-full md:flex-col md:items-center md:space-y-2 md:p-2 md:z-50 md:transition-opacity md:duration-300 ${
showScrollSpy ? "md:opacity-100" : "md:opacity-0"
}`}
>
<div className="relative flex flex-col items-center space-y-2">
{sections.map((section) => (
<div
key={section.id}
className="relative flex items-center space-x-2"
onMouseEnter={() => {
setHoveredSection(section.id);
if (activeSection !== section.id) {
setActiveSection("");
}
}}
onMouseLeave={() => {
if (activeSection === "") {
setActiveSection((prev) => (prev === section.id ? "" : prev));
}
setHoveredSection("");
}}
>
<a
href={`#${section.id}`}
className={`block h-8 transition-all duration-300 ${
hoveredSection === section.id || activeSection === section.id
? "bg-[#181818] w-[4px]"
: "bg-[#181818] w-[2px] opacity-[70%]"
}`}
></a>
{(hoveredSection === section.id ||
activeSection === section.id) && (
<div
className={`absolute left-6 w-[60px] text-[10px] font-light text-[#181818] transition-opacity duration-300 ${
hoveredSection === section.id || activeSection === section.id
? "opacity-100"
: "opacity-0"
}`}
>
{section.name}
</div>
)}
</div>
))}
</div>
</div>
);
};
export default ScrollSpy;
I tried to change color on based of sections Id's but it wasn't the right solution
I also asked chatgpt for solution, it told me about background luminance but i couldn't manage it:
this is solution from chatgpt :
"use client";
import { useEffect, useState } from "react";
const sections = [
{ id: "MG HS", name: "MG HS" },
{ id: "MG Info", name: "MG Info" },
{ id: "safety", name: "Safety" },
{ id: "car-performance", name: "Car Performance" },
{ id: "plugin-hybrid", name: "Plugin Hybrid" },
{ id: "mg-pilot", name: "MG Pilot" },
{ id: "car-design", name: "Car Design" },
{ id: "comfort", name: "Comfort" },
{ id: "car-technology", name: "Car Technology" },
{ id: "mg-view", name: "MG View" },
];
const ScrollSpy = () => {
const [activeSection, setActiveSection] = useState<string>("");
const [hoveredSection, setHoveredSection] = useState<string>("");
const [showScrollSpy, setShowScrollSpy] = useState<boolean>(false);
const [scrollSpyColor, setScrollSpyColor] = useState<string>("light");
const getLuminance = (color: string) => {
// Convert rgba/hex to RGB
let rgb = color;
if (color.startsWith("rgba")) {
rgb = color.replace(/rgba?\(|\s+|\)/g, "").split(",").slice(0, 3).join(",");
} else if (color.startsWith("rgb")) {
rgb = color.replace(/rgb\(|\s+|\)/g, "");
} else if (color.startsWith("#")) {
let hex = color.slice(1);
if (hex.length === 3) hex = hex.split("").map(h => h + h).join("");
rgb = [0, 2, 4].map(i => parseInt(hex.slice(i, i + 2), 16)).join(",");
}
const [r, g, b] = rgb.split(",").map(Number).map(value => value / 255);
const luminance = 0.2126 * (r ** 2.2) + 0.7152 * (g ** 2.2) + 0.0722 * (b ** 2.2);
return luminance;
};
useEffect(() => {
const handleScroll = () => {
const scrollPosition = window.scrollY + 60;
const sectionElements = document.querySelectorAll("div[id]");
let currentSectionId = "";
let anySectionVisible = false;
sectionElements.forEach((element) => {
const section = element as HTMLElement;
const sectionTop = section.getBoundingClientRect().top + window.scrollY;
const sectionBottom = sectionTop + section.offsetHeight;
if (scrollPosition >= sectionTop && scrollPosition < sectionBottom) {
currentSectionId = section.getAttribute("id") || "";
anySectionVisible = true;
// Get the computed background color of the current section
const sectionBgColor = window.getComputedStyle(section).backgroundColor;
// Determine the luminance of the background color
const luminance = getLuminance(sectionBgColor);
// Set ScrollSpy color based on luminance
setScrollSpyColor(luminance > 0.5 ? "dark" : "light");
}
});
if (!hoveredSection) {
setActiveSection(currentSectionId);
}
setShowScrollSpy(anySectionVisible);
};
window.addEventListener("scroll", handleScroll);
handleScroll();
return () => window.removeEventListener("scroll", handleScroll);
}, [hoveredSection]);
return (
<div
className={`hidden md:flex md:fixed md:top-[160px] md:left-[74px] md:h-full md:flex-col md:items-center md:space-y-2 md:z-50 md:transition-opacity md:duration-300 ${
showScrollSpy ? "md:opacity-100" : "md:opacity-0"
}`}
>
<div className="relative flex flex-col items-center space-y-2">
{sections.map((section) => (
<div
key={section.id}
className="relative flex items-center space-x-2"
onMouseEnter={() => {
setHoveredSection(section.id);
if (activeSection !== section.id) {
setActiveSection("");
}
}}
onMouseLeave={() => {
if (activeSection === "") {
setActiveSection((prev) => (prev === section.id ? "" : prev));
}
setHoveredSection("");
}}
>
<a
href={`#${section.id}`}
className={`block h-8 transition-all duration-300 ${
hoveredSection === section.id || activeSection === section.id
? scrollSpyColor === "dark"
? "bg-black w-[4px]" // Dark bar on light backgrounds
: "bg-white w-[4px]" // Light bar on dark backgrounds
: scrollSpyColor === "dark"
? "bg-black w-[2px] opacity-[70%]" // Dark bar default on light
: "bg-white w-[2px] opacity-[70%]" // Light bar default on dark
}`}
></a>
{(hoveredSection === section.id || activeSection === section.id) && (
<div
className={`absolute left-6 w-[60px] text-[10px] font-light transition-opacity duration-300 ${
hoveredSection === section.id || activeSection === section.id
? "opacity-100"
: "opacity-0"
}`}
style={{
color: scrollSpyColor === "dark" ? "#000000" : "#ffffff", // Dark text on light, light text on dark
}}
>
{section.name}
</div>
)}
</div>
))}
</div>
</div>
);
};
export default ScrollSpy;
use this tailwind class on the container of your scrollspy "mix-blend-difference" it will automatically invert the colors