NextJS实现酒店网站导航栏元素动态变色方案咨询
问题描述
我有一个酒店网站的固定导航栏(代码如下),想要实现导航栏元素颜色根据下方背景动态变化:
- 首屏深色背景时,元素颜色设为
#fff - 滚动到下方或切换到
/lodge页面(白色背景)时,元素颜色改为#313131,避免和背景融合
之前尝试用useRouter和window.addEventListener监听滚动事件(按固定像素值切换颜色),但存在两个问题:
- 切换页面后需要滚动才会更新颜色,有延迟
- 适配不同场景的工作量大,不是最优方案
想了解该功能的实现方式,是否可以根据导航栏后方的背景进行颜色反转?
'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;
解决方案
提供两种实现思路,可根据场景选择:
方案一:基于页面路由与区块标记的精准切换
适合页面背景区块划分明确的场景,性能优异且逻辑简单,解决页面切换后的延迟问题。
实现步骤
- 用
useRouter监听路由变化,页面加载时直接根据当前路由初始化颜色状态 - 给页面中的关键区块添加
data-bg-type属性(比如data-bg-type="dark"或light) - 监听滚动事件,检测导航栏下方的区块类型,更新颜色状态
代码示例
'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>
方案二:基于背景像素检测的自动颜色反转
适合需要自动适配任意背景的场景,无需手动标记区块,但需注意性能优化。
实现步骤
- 创建隐藏Canvas,用于获取导航栏下方区域的像素数据
- 计算区域平均亮度,根据亮度阈值(如128)切换颜色
- 结合路由监听与节流滚动事件,避免性能损耗
代码示例
'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-[
相关产品推荐
相关产品推荐

