reactjsscrollcolors

How to change text color based on background color dynamically


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;

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;


Solution

  • use this tailwind class on the container of your scrollspy "mix-blend-difference" it will automatically invert the colors