You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS实现酒店网站导航栏元素动态变色方案咨询

问题描述

我有一个酒店网站的固定导航栏(代码如下),想要实现导航栏元素颜色根据下方背景动态变化:

  • 首屏深色背景时,元素颜色设为#fff
  • 滚动到下方或切换到/lodge页面(白色背景)时,元素颜色改为#313131,避免和背景融合

之前尝试用useRouter和window.addEventListener监听滚动事件(按固定像素值切换颜色),但存在两个问题:

  1. 切换页面后需要滚动才会更新颜色,有延迟
  2. 适配不同场景的工作量大,不是最优方案

想了解该功能的实现方式,是否可以根据导航栏后方的背景进行颜色反转?

'use client';

import Image from "next/image";
import Link from "next/link";

const Navbar = () => {

  return (
    <div className="fixed w-full backdrop-blur-sm z-10 lg:px-[100px] md:px-[70px] sm:px-[35px] xs:px-[15px]">
        <div className="border-b-[2px] border-[#313131]">
            <div className="lg:pb-5 lg:pt-7 md:pb-5 md:pt-6 sm:pt-5 sm:pb-4 xs:pt-10 xs:pb-[10px]">
                <div className="flex flex-row items-center">

                    {/* logo */}
                    <div className="relative lg:w-[146px] lg:h-[64px] md:w-[132px] md:h-[58px] sm:w-[90px] sm:h-[50px] xs:w-[68px] xs:h-[30px]">
                        <Link href="/">
                            <Image
                                src="/images/navbar/logo-b.svg" 
                                alt="logo-b" 
                                layout="fill"
                            />
                        </Link>
                    </div>
                    
                    {/* lodge */}
                    <div className="lg:text-[28px] md:text-[24px] lg:block md:block sm:hidden xs:hidden font-medium leading-normal uppercase text-[#313131] lg:pl-[610px] md:pl-[284px] lg:pr-[80px] md:pr-[80px]">
                        <Link href="/lodge">
                            LODGE
                        </Link>
                    </div>

                    {/* our blog */}
                    <div className="lg:text-[28px] md:text-[24px] md:block sm:hidden xs:hidden font-medium leading-normal uppercase text-[#313131]">
                        <Link href="/#ourblog">
                            OUR BLOG
                        </Link>
                    </div>

                    {/* num */}
                    <div className="flex flex-row items-center lg:gap-5 md:gap-5 sm:gap-3 lg:pl-[100px] md:pl-[80px] sm:pl-[162px]">
                        <div className="relative lg:w-[24px] lg:h-[24px] md:w-[24px] md:h-[24px] sm:w-[20px] sm:h-[20px] lg:block md:block sm:block xs:hidden">
                            <Image 
                                src="/images/navbar/tel-b.svg" 
                                alt="tel" 
                                layout="fill"
                            />
                        </div>
                        <div className="lg:text-[28px] md:text-[24px] sm:text-[20px] font-medium leading-normal uppercase text-[#313131] lg:block md:block sm:block xs:hidden">
                            +57 (316) 893-1653
                        </div>
                    </div>
                    
                    {/* lang switch */}
                    <div className="lg:text-[28px] md:text-[24px] sm:text-[20px] font-medium leading-normal uppercase text-[#313131] lg:pl-[100px] md:pl-[132px] sm:pl-[50px] xs:pl-[139px]">
                        <div className="flex flex-row items-center">
                            <div className="cursor-pointer underline">
                                EN
                            </div>
                            <div className="lg:px-4 md:px-[15px] sm:px-[10px] xs:px-[10px] cursor-default">
                                |
                            </div>
                            <div className="cursor-pointer">
                                ES
                            </div>
                        </div>
                    </div>

                    <div className="lg:hidden md:hidden sm:block xs:block sm:pl-[60px] xs:pl-[30px]">
                        <Image
                            className="cursor-pointer"
                            src="/images/navbar/burger_menu_b.svg"
                            alt="burger menu"
                            width={24}
                            height={24}                        
                        />

                        {/* <BurgerMenu onClose={() => setShowBurgerMenu(false)} /> */}
                    </div>

                </div>
            </div>
        </div>
    </div>
  )
}

export default Navbar;

解决方案

提供两种实现思路,可根据场景选择:

方案一:基于页面路由与区块标记的精准切换

适合页面背景区块划分明确的场景,性能优异且逻辑简单,解决页面切换后的延迟问题。

实现步骤

  1. 用useRouter监听路由变化,页面加载时直接根据当前路由初始化颜色状态
  2. 给页面中的关键区块添加data-bg-type属性(比如data-bg-type="dark"或light)
  3. 监听滚动事件,检测导航栏下方的区块类型,更新颜色状态

代码示例

'use client';

import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import Image from "next/image";
import Link from "next/link";

const Navbar = () => {
  const [isDarkBg, setIsDarkBg] = useState(false);
  const router = useRouter();

  useEffect(() => {
    // 根据路由初始化状态
    const initialIsDark = router.pathname === '/' ? true : false;
    setIsDarkBg(initialIsDark);

    // 滚动时检测下方背景区块
    const handleScroll = () => {
      const navRect = document.querySelector('.navbar')?.getBoundingClientRect();
      if (!navRect) return;

      // 取导航栏中间位置检测下方元素
      const checkPointY = navRect.top + navRect.height / 2;
      const elementsUnderNav = document.elementsFromPoint(window.innerWidth / 2, checkPointY);
      
      // 查找带背景标记的父元素
      const bgElement = Array.from(elementsUnderNav).find(el => el.hasAttribute('data-bg-type'));
      if (bgElement) {
        setIsDarkBg(bgElement.getAttribute('data-bg-type') === 'dark');
      }
    };

    window.addEventListener('scroll', handleScroll);
    // 页面加载后立即执行一次,避免切换页面后需滚动才更新
    handleScroll();

    return () => window.removeEventListener('scroll', handleScroll);
  }, [router.pathname]);

  // 动态生成样式类与图片路径
  const navTextColor = isDarkBg ? 'text-[#fff]' : 'text-[#313131]';
  const navBorderColor = isDarkBg ? 'border-[#fff]' : 'border-[#313131]';
  const logoSrc = isDarkBg ? '/images/navbar/logo-w.svg' : '/images/navbar/logo-b.svg';
  const telSrc = isDarkBg ? '/images/navbar/tel-w.svg' : '/images/navbar/tel-b.svg';
  const burgerSrc = isDarkBg ? '/images/navbar/burger_menu_w.svg' : '/images/navbar/burger_menu_b.svg';

  return (
    <div className={`fixed w-full backdrop-blur-sm z-10 lg:px-[100px] md:px-[70px] sm:px-[35px] xs:px-[15px] navbar`}>
        <div className={`border-b-[2px] ${navBorderColor}`}>
            <div className="lg:pb-5 lg:pt-7 md:pb-5 md:pt-6 sm:pt-5 sm:pb-4 xs:pt-10 xs:pb-[10px]">
                <div className="flex flex-row items-center">
                    {/* logo */}
                    <div className="relative lg:w-[146px] lg:h-[64px] md:w-[132px] md:h-[58px] sm:w-[90px] sm:h-[50px] xs:w-[68px] xs:h-[30px]">
                        <Link href="/">
                            <Image src={logoSrc} alt="logo" layout="fill" />
                        </Link>
                    </div>
                    
                    {/* lodge */}
                    <div className={`lg:text-[28px] md:text-[24px] lg:block md:block sm:hidden xs:hidden font-medium leading-normal uppercase ${navTextColor} lg:pl-[610px] md:pl-[284px] lg:pr-[80px] md:pr-[80px]`}>
                        <Link href="/lodge">LODGE</Link>
                    </div>

                    {/* our blog */}
                    <div className={`lg:text-[28px] md:text-[24px] md:block sm:hidden xs:hidden font-medium leading-normal uppercase ${navTextColor}`}>
                        <Link href="/#ourblog">OUR BLOG</Link>
                    </div>

                    {/* num */}
                    <div className="flex flex-row items-center lg:gap-5 md:gap-5 sm:gap-3 lg:pl-[100px] md:pl-[80px] sm:pl-[162px]">
                        <div className="relative lg:w-[24px] lg:h-[24px] md:w-[24px] md:h-[24px] sm:w-[20px] sm:h-[20px] lg:block md:block sm:block xs:hidden">
                            <Image src={telSrc} alt="tel" layout="fill" />
                        </div>
                        <div className={`lg:text-[28px] md:text-[24px] sm:text-[20px] font-medium leading-normal uppercase ${navTextColor} lg:block md:block sm:block xs:hidden`}>
                            +57 (316) 893-1653
                        </div>
                    </div>
                    
                    {/* lang switch */}
                    <div className={`lg:text-[28px] md:text-[24px] sm:text-[20px] font-medium leading-normal uppercase ${navTextColor} lg:pl-[100px] md:pl-[132px] sm:pl-[50px] xs:pl-[139px]`}>
                        <div className="flex flex-row items-center">
                            <div className="cursor-pointer underline">EN</div>
                            <div className="lg:px-4 md:px-[15px] sm:px-[10px] xs:px-[10px] cursor-default">|</div>
                            <div className="cursor-pointer">ES</div>
                        </div>
                    </div>

                    <div className="lg:hidden md:hidden sm:block xs:block sm:pl-[60px] xs:pl-[30px]">
                        <Image className="cursor-pointer" src={burgerSrc} alt="burger menu" width={24} height={24} />
                    </div>
                </div>
            </div>
        </div>
    </div>
  )
}

export default Navbar;

页面区块配置

在首页首屏区块添加标记:

<section className="hero-dark" data-bg-type="dark">
  <!-- 首屏内容 -->
</section>

在/lodge页面的所有区块添加:

<section className="lodge-section" data-bg-type="light">
  <!-- 页面内容 -->
</section>

方案二:基于背景像素检测的自动颜色反转

适合需要自动适配任意背景的场景,无需手动标记区块,但需注意性能优化。

实现步骤

  1. 创建隐藏Canvas,用于获取导航栏下方区域的像素数据
  2. 计算区域平均亮度,根据亮度阈值(如128)切换颜色
  3. 结合路由监听与节流滚动事件,避免性能损耗

代码示例

'use client';

import { useState, useEffect, useRef } from 'react';
import { useRouter } from 'next/navigation';
import Image from "next/image";
import Link from "next/link";

const Navbar = () => {
  const [isDarkBg, setIsDarkBg] = useState(false);
  const router = useRouter();
  const canvasRef = useRef(null);

  // 计算区域平均亮度
  const getAverageBrightness = () => {
    const navElement = document.querySelector('.navbar');
    if (!navElement || !canvasRef.current) return 0;

    const navRect = navElement.getBoundingClientRect();
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    
    canvas.width = navRect.width;
    canvas.height = 50;
    // 绘制导航栏下方区域到Canvas
    ctx.drawImage(window.document.body, navRect.left, navRect.bottom, navRect.width, 50, 0, 0, navRect.width, 50);
    
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = imageData.data;
    let totalBrightness = 0;
    
    // RGB转亮度公式:0.299*R + 0.587*G + 0.114*B
    for (let i = 0; i < data.length; i += 4) {
      totalBrightness += 0.299*data[i] + 0.587*data[i+1] + 0.114*data[i+2];
    }
    
    return totalBrightness / (canvas.width * canvas.height);
  };

  // 节流函数优化性能
  const throttle = (func, delay) => {
    let lastCall = 0;
    return (...args) => {
      const now = Date.now();
      if (now - lastCall >= delay) {
        func(...args);
        lastCall = now;
      }
    };
  };

  useEffect(() => {
    const updateNavColor = () => {
      const brightness = getAverageBrightness();
      setIsDarkBg(brightness < 128);
    };

    // 路由切换后等待页面渲染完成再更新
    const handleRouteChange = () => setTimeout(updateNavColor, 100);
    
    router.events.on('routeChangeComplete', handleRouteChange);
    
    const throttledUpdate = throttle(updateNavColor, 100);
    window.addEventListener('scroll', throttledUpdate);
    updateNavColor();

    return () => {
      router.events.off('routeChangeComplete', handleRouteChange);
      window.removeEventListener('scroll', throttledUpdate);
    };
  }, [router]);

  // 样式与图片切换逻辑同方案一
  const navTextColor = isDarkBg ? 'text-[#fff]' : 'text-[#313131]';
  const navBorderColor = isDarkBg ? 'border-[#fff]' : 'border-[#313131]';
  const logoSrc = isDarkBg ? '/images/navbar/logo-w.svg' : '/images/navbar/logo-b.svg';
  const telSrc = isDarkBg ? '/images/navbar/tel-w.svg' : '/images/navbar/tel-b.svg';
  const burgerSrc = isDarkBg ? '/images/navbar/burger_menu_w.svg' : '/images/navbar/burger_menu_b.svg';

  return (
    <>
      <canvas ref={canvasRef} className="hidden" />
      <div className={`fixed w-full backdrop-blur-sm z-10 lg:px-[100px] md:px-[70px] sm:px-[35px] xs:px-[15px] navbar`}>
        {/* 导航栏内容同方案一 */}
        <div className={`border-b-[2px] ${navBorderColor}`}>
            <div className="lg:pb-5 lg:pt-7 md:pb-5 md:pt-6 sm:pt-5 sm:pb-4 xs:pt-10 xs:pb-[10px]">
                <div className="flex flex-row items-center">
                    {/* logo */}
                    <div className="relative lg:w-[146px] lg:h-[64px] md:w-[132px] md:h-[58px] sm:w-[90px] sm:h-[50px] xs:w-[68px] xs:h-[30px]">
                        <Link href="/">
                            <Image src={logoSrc} alt="logo" layout="fill" />
                        </Link>
                    </div>
                    
                    {/* lodge */}
                    <div className={`lg:text-[28px] md:text-[24px] lg:block md:block sm:hidden xs:hidden font-medium leading-normal uppercase ${navTextColor} lg:pl-[610px] md:pl-[284px] lg:pr-[
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 23:43:44