简介滚动渐变导航栏是前端开发中常见的交互效果本资源基于HTML5CSS3JavaScript完整实现这一小实例适合刚接触前端三件套的初学者也供需要快速实现页面导航栏动态效果的开发者参考。资源共包含5个文件涵盖HTML结构、CSS样式、JavaScript脚本以及两张JPG素材图和一张GIF预览动图压缩包大小仅1.48MB轻量易下载便于直接运行与拆解学习。目前已有155人学习下载内容编排清晰既能作为课堂练习也能应用到个人站点或项目作品集中。通过源码可重点掌握滚动事件监听、样式类切换和背景渐变过渡等核心技巧配合GIF预览图能直观理解滚动前后视觉变化有效提升前端页面交互设计能力。 滚动渐变导航栏这个效果前端圈子里大家都见过——刚打开页面时导航栏是透明的logo和菜单直接压在首屏图上往下滚过一段距离导航栏慢慢变成白底深色字底部浮起一层阴影。很多新手觉得这就是“滚动一下加个class”的事但实际写完你会发现从HTML结构预留状态位到CSS3 transition的插值机制再到JS滚动监听怎么触发才不卡每个环节都能踩出花来。这篇拿这个经典实例完整拆一遍适合刚学完HTML5CSS3JS基础、想找项目练手的同学也适合做网页设计作业不知道写什么的老铁参考。我没有用任何框架纯原生三件套完整代码放在后面你照着敲一遍基本就能跑起来。更重要的是我希望你把这篇文章当成一份“避坑笔记”而不是代码仓库——因为真正值钱的部分是那些不运行一遍根本发现不了的细节。1. 滚动渐变导航栏的效果拆解它到底在解决什么问题1.1 一个效果背后的三层需求视觉、功能、交互先说视觉。绝大多数带滚动渐变导航栏的页面首屏都是一张大图或者一个全屏banner。导航栏如果直接是白色实底会像一块创可贴贴在脸上把画面的沉浸感破坏掉。所以初始状态必须是透明或半透明让图片“透”上来文字浮在图上干净利落。功能需求出现在用户滚动之后。首屏被滚走了页面进入正文区域这时候背景变成白色、灰色或者深色内容密度变高如果导航栏还透明着导航文字就会和正文内容叠在一起黑字压黑字根本分不清。所以导航栏需要一个实色背景把自己从内容里“提”出来保证任何滚动位置都有可读性。交互需求则是中间那段过渡不能“啪”地一下从透明变成白色要顺着滚动动作丝滑地变化。这个效果的本质就三件事初始透明、滚动后实色、变化过程有过渡。所有代码都是围绕这三件事展开的。1.2 为什么渐变比跳变更“专业”你可以试想一下如果不用过渡动画用户往下滚几下导航栏突然从透明变成白底视觉上是会觉得闪了一下的。这种“闪”在交互体验里属于明显的割裂感用户会下意识觉得页面不跟手、很生硬。渐变的作用是让状态切换在连续的多帧渲染里完成看起来就像导航栏“感知”到了滚动深度。这里要理解一个关键点CSS transition 不是一种持续播放的动画而是“当属性值发生变化时浏览器自动用插值补齐中间帧”。也就是说你只需要定义起点和终点中间每一帧的颜色、阴影、尺寸浏览器都会帮你算出来。这样用户感知到的是“融合”而不是“切换”。这个实例之所以经典就是因为它绕不开 HTML 结构、CSS 状态切换、JS 事件监听这三板斧小而完整非常适合拿来建立前端手感。2. 搭建页面骨架HTML结构决定了后面好不好写2.1 语义化标签不白用header/nav让代码有骨头HTML5 提供了header、nav、main、section这些语义化标签很多人用的时候没什么感觉觉得 div 一把梭也能实现。但在导航栏这个场景里语义化标签是有实际收益的。导航区域直接包在header里里面再放一个nav结构上就是“页头 - 导航”的层级关系。浏览器和搜索引擎解析的时候能快速识别出这是站点导航而不是正文内容。对辅助阅读设备来说语义化标签也能让用户直接跳到导航区域这些是纯 div 给不了的。更实际的一点是语义化标签默认带一些浏览器预设样式比如nav本来就是块级元素不用再写 display: blockul默认有列表样式你需要清掉才能做横向菜单。这些细节就要求你理解标签默认样式而不是稀里糊涂地堆 class。2.2 类名设计结构类名与状态类名分开写导航栏的时候我强烈建议你养成“结构类名 状态类名”分开的习惯。所谓结构类名描述的是“这个元素是什么”比如.site-header、.nav-list、.logo它不会因为页面状态而改变。状态类名描述的是“这个元素当前处于什么状态”我用.scrolled表示已经滚过阈值。这样设计的好处非常明显。如果以后要支持夜间模式我可以给body加一个.dark然后导航栏根据.dark去调整配色而不是把几十个状态混在一起。如果以后要支持“导航栏在滚动时缩小高度”的特效我只需要再写一个.scrolled .nav的样式规则不需要改结构。配合 JS 时状态类名统一由一个函数控制整个逻辑就是“滚过了就加滚回来就删”清晰得不能再清晰。你要是把这种命名习惯保持下去以后写再复杂的项目都不容易乱。下面是基础结构代码header classsite-header idsiteHeader nav classnav container a href# classlogoLOGO/a ul classnav-list lia href#home首页/a/li lia href#about关于/a/li lia href#works作品/a/li lia href#contact联系/a/li /ul /nav /header main section classbanner idhome h1滚动渐变导航栏实例/h1 p向下滚动观察导航栏的变化/p /section section classsection about idabout h2关于/h2 p这里是占位内容用于撑起页面滚动距离。/p /section section classsection works idworks h2作品/h2 p这几个区块只是占位实际项目中可以放任何内容。/p /section section classsection contact idcontact h2联系/h2 p页面够长导航栏的滚动效果才能体现出来。/p /section /mainbanner 区做高一点比如min-height: 100vh下面的 section 也各自给一个高度。这样页面才有足够的滚动空间否则导航栏还没变完就滚到底了看不到完整效果。3. CSS3核心机制transition是怎样把“变色”变丝滑的3.1 fixed定位与z-index层级是导航栏的命门导航栏要始终固定在页面顶部position: fixed是唯一选择。这里要注意的是fixed 定位的元素会脱离文档流意味着它会浮在内容上方所以必须给足z-index。我见过不少项目翻车就是导航栏只写了个position: fixed忘记写层级结果页面里有轮播图、弹窗或者动画元素时导航栏被压在了下面。尤其是滚动渐变的效果透明状态看不出来一旦变了色才发现被其他元素盖住了半截。建议给.site-header设置z-index: 1000以上这个数值在大多数业务场景里够用了真遇到弹层组件再单独调弹层的层级就行。在实际开发里层级管理是个容易忽视的大问题养成“固定层级的元素一定要显式声明 z-index”的习惯能少踩很多坑。3.2 颜色过渡的原理浏览器在逐帧插值这回是重点中的重点。很多人写了下面的代码发现过渡不生效问我为什么.site-header { background-color: rgba(255, 255, 255, 0.95); transition: background-color 0.3s ease; }问题在于transition 的触发条件是“同一个元素的同一个属性从一个可计算值变成另一个可计算值”。如果你一开始就写了白色背景那滚动后加不上别的颜色等于没有状态变化自然没有过渡。正确做法是先把初始状态写清楚.site-header { position: fixed; top: 0; left: 0; width: 100%; background-color: transparent; transition: background-color 0.3s ease, box-shadow 0.3s ease; z-index: 1000; } .site-header.scrolled { background-color: rgba(255, 255, 255, 0.95); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); }当 JS 把.scrolled加到这个元素上时background-color从transparent变成rgba(255,255,255,0.95)transition 就会介入。浏览器做的插值计算大概是这样的red 通道从 0 插值到 255green 从 0 到 255blue 从 0 到 255alpha 从 0 到 0.95每一帧按当前时间进度算出中间值并重绘。0.3 秒内完成全部插值人眼看起来就是连续渐变。这里有一个非常容易踩的坑过渡动画不是一次性全局开启的它只作用于你显式声明了 transition 的属性。如果我把box-shadow漏掉了滚动后阴影就会瞬间出现和背景渐变不同步视觉上非常割裂。所以你要养成习惯需要保持一致体验的属性都放到 transition 列表里。3.3 别只盯着背景文字、阴影、尺寸一起过渡背景色变化只是其中一半。大多数设计里导航栏透明状态用的是白色文字因为首屏图通常偏暗滚动了就变成深色文字。文字颜色如果瞬间切换同样会产生闪一下的廉价感。所以.site-header .nav a和.site-header .logo都要单独设置color和transition.site-header .nav a { color: #fff; transition: color 0.3s ease; } .site-header.scrolled .nav a { color: #333; }如果把这么写好的页面跑起来你会发现背景和文字是同时渐变的整体非常统一。除了颜色有些设计稿里导航栏的高度也会变化比如初始高度 80px滚动后变成 60px再加一点阴影视觉上会显得更精致。高度变化同样可以用 transition 实现只要把height或者padding加进去就行。不过要注意height 参与过渡时如果导航栏内部元素高度不固定可能会出现内容跳动的现象。所以更稳妥的做法是只过渡 padding或者依赖 flex 布局让内容垂直居中减少跳动感。4. JS滚动监听别用最直觉的写法那里全是坑4.1 判断滚动距离的正确姿势JS 这边的核心是不断判断“滚动条滚过了多少距离”。我用的是window.scrollY它返回文档在垂直方向已经滚过去的像素值。在较旧的浏览器里你可能看到过document.documentElement.scrollTop两者作用相同但window.scrollY更直观现代浏览器都支持。然后设定一个阈值。我习惯用 80px也就是说滚过了 80px 就认为“用户已经离开首屏”。这个数字不是拍脑袋定的它通常接近导航栏自身的高度。当用户把导航栏滚出视觉区域时宣告进入第二种状态比较符合直觉。const header document.getElementById(siteHeader); const threshold 80; function onScroll() { if (window.scrollY threshold) { header.classList.add(scrolled); } else { header.classList.remove(scrolled); } } window.addEventListener(scroll, onScroll, { passive: true }); onScroll();注意最后手动调用一次onScroll()这是为了处理刷新后浏览器已经滚动到页面中部的情况——后面会单独展开讲。4.2 scroll监听为什么容易卡以及passive怎么救如果你把监听直接绑上去不假思索地处理大量逻辑滚动起来页面会明显掉帧。原因是 scroll 事件在滚动过程中触发频率非常高性能差的设备上甚至一帧里触发多次每次都去操作 DOM、读写布局主线程就被塞满了。这个实例里只做一次 classList 的增删开销其实不大直接绑也基本能跑。但既然写了这篇文章我还是希望你把正确姿势养成习惯。第一招是加passive: true。这个参数告诉浏览器这个事件监听器不会调用preventDefault()你可以放心地即时触发不用停下来等 JS 执行完。对 scroll 和 touchmove 这类高频事件passive 能显著减少卡顿感。第二招是用requestAnimationFrame合并触发。简单说就是不管 scroll 回调触发了多少次同一帧里只执行一次真正的判断逻辑let ticking false; function onScroll() { if (!ticking) { window.requestAnimationFrame(() { if (window.scrollY threshold) { header.classList.add(scrolled); } else { header.classList.remove(scrolled); } ticking false; }); ticking true; } }初次看这段代码可能觉得绕但它解决的核心问题是滚动事件触发频率高于屏幕刷新率不需要每次都跑逻辑把同一次渲染周期内的多次触发合并成一次就够了。这个思维以后你用在上滑加载、懒加载、拖拽等等场景里都一样。4.3 初始状态判断刷新后别让导航栏“失忆”我见过很多初学者写完这个效果在页面上从顶部往下滚一切正常。但只要刷新一下页面——浏览器恢复了之前滚动位置——导航栏就“失忆”了明明滚到了页面一半导航栏却还是透明的文字压在正文上根本看不清。原因很简单刷新后滚动位置被浏览器恢复了但 JS 的滚动监听是在后续滚动时才触发的如果用户不滚事件就不会触发状态就停留在初始值。解决办法就是上面代码里那行手动调用的onScroll()。它在页面加载时就基于当前滚动距离执行一次判断刷新到哪都能正确恢复状态。这个细节在纯静态页面里看起来不痛不痒但在单页应用里很关键——路由切换、回到顶部这类操作之后导航栏的状态都必须重新计算。5. 完整代码与常见问题排查照着抄也踩不到坑5.1 一版可运行的完整代码把上面所有片段拼起来就是一个可以直接跑起来的完整页面。我把它整理成一份代码你复制到本地 HTML 文件里浏览器打开就能看到效果。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title滚动渐变导航栏/title style * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: PingFang SC, Microsoft YaHei, sans-serif; line-height: 1.6; } .site-header { position: fixed; top: 0; left: 0; width: 100%; background-color: transparent; transition: background-color 0.3s ease, box-shadow 0.3s ease; z-index: 1000; } .site-header.scrolled { background-color: rgba(255, 255, 255, 0.95); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); } .nav { max-width: 1200px; margin: 0 auto; height: 64px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; } .logo { font-size: 24px; font-weight: 700; color: #fff; text-decoration: none; transition: color 0.3s ease; } .site-header.scrolled .logo { color: #333; } .nav-list { display: flex; list-style: none; gap: 24px; } .nav-list a { color: #fff; text-decoration: none; font-size: 16px; transition: color 0.3s ease; } .site-header.scrolled .nav-list a { color: #333; } .nav-list a:hover { opacity: 0.7; } .banner { min-height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); color: #fff; } .section { min-height: 60vh; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 40px 24px; } .section:nth-of-type(even) { background: #f5f7fa; } /style /head body header classsite-header idsiteHeader nav classnav a href# classlogoLOGO/a ul classnav-list lia href#home首页/a/li lia href#about关于/a/li lia href#works作品/a/li lia href#contact联系/a/li /ul /nav /header main section classbanner idhome h1滚动渐变导航栏/h1 p向下滚动看看会发生什么/p /section section classsection idabout h2关于/h2 p这里是占位内容用于撑起页面滚动距离。/p /section section classsection idworks h2作品/h2 p这几个区块只是占位实际项目中可以放任何内容。/p /section section classsection idcontact h2联系/h2 p页面够长导航栏的滚动效果才能体现出来。/p /section /main script const header document.getElementById(siteHeader); const threshold 80; function toggleHeader() { if (window.scrollY threshold) { header.classList.add(scrolled); } else { header.classList.remove(scrolled); } } window.addEventListener(scroll, toggleHeader, { passive: true }); toggleHeader(); /script /body /html这段代码里我特意加了box-sizing: border-box这是一个极其重要的细节。如果没有它你给.nav设置的 padding 会导致整体宽度超出视口出现横向滚动条看起来就像布局“多出来一截”。有了 border-boxpadding 不会撑大盒子的宽度省掉很多布局调校时间。5.2 四个高频翻车现场与排查思路现象可能原因解决方案滚动后背景瞬间变白没有过渡transition 没写在初始状态里或者 transition 属性列表里漏了 background-color在.site-header里显式补上transition: background-color 0.3s ease透明状态下菜单文字看不见首屏图太亮白色文字对比度不足在 banner 上加一层半透明暗色遮罩或者给文字加 text-shadow滚动后导航栏被内容盖住z-index 没设置或设置太低给.site-header设置z-index: 1000页面横向出现滚动条宽度计算出了问题多半和 box-sizing 有关设置* { box-sizing: border-box; }第一个问题的排查思路值得说一下。如果你发现过渡没生效先别急着改代码打开浏览器开发者工具选中导航栏元素看两个东西一是transition属性是否真的加上了二是背景色的初始值和终态值是不是都在计算样式里。很多时候问题出在样式覆盖——某个更靠后的选择器把背景色盖掉了或者!important参与了战斗浏览器直接放弃了插值。5.3 把渐变阈值变柔和按滚动距离实时计算透明度有些设计稿要求的不是“到 80px 就切换”而是“滚动得越深背景越实”。这种效果要动态计算透明度让导航栏颜色和滚动位置绑定起来而不是单纯的 class 切换。思路很简单把threshold当做一个“渐变区间”滚动距离除以阈值得到一个 0 到 1 之间的进度值然后用这个进度值去拼背景色。比如 threshold 设为 300px滚动 150px 时进度就是 0.5背景透明度就是 0.475。const header document.getElementById(siteHeader); const maxScroll 300; function updateHeader() { const progress Math.min(window.scrollY / maxScroll, 1); const alpha Math.round(progress * 0.95 * 100) / 100; header.style.backgroundColor rgba(255, 255, 255, ${alpha}); header.style.boxShadow progress 0 ? 0 2px 12px rgba(0, 0, 0, ${0.1 * progress}) : none; } window.addEventListener(scroll, updateHeader, { passive: true }); updateHeader();用这段代码时有个关键操作必须把 CSS 里的 transition 去掉否则你手动改透明度transition 又去补一帧插值两边会打架效果会显得迟钝。这种方法的好处是视觉上更细腻适合对质感要求高的官网首页。6. 进阶方向毛玻璃、方向感知与渐变阈值软化6.1 backdrop-filter毛玻璃一行CSS换来的质感你如果追求更现代的视觉效果可以给滚动后的导航栏加一层毛玻璃。backdrop-filter这个属性可以直接作用在导航栏后面的内容上做出“透过玻璃看下面内容”的模糊效果视觉层次感瞬间上一个档次。.site-header.scrolled { background-color: rgba(255, 255, 255, 0.6); backdrop-filter: saturate(180%) blur(20px); -webkit-backdrop-filter: saturate(180%) blur(20px); }注意两点。一是背景色要调得比之前更透明一些因为毛玻璃本身就会让底下内容变模糊太实了反而看不出玻璃质感。二是-webkit-前缀还是得写毕竟 mac 上 Safari 的占有率不算低。加这行之后导航栏的精致度提升非常明显很多大厂官网的导航栏其实就是这么做的。6.2 方向感知导航栏向下滚隐藏向上滚露出还有一个比渐变更进阶的交互向下滚动时整个导航栏收起通过translateY(-100%)移出屏幕向上滚动时再弹出来。这种模式在移动端页面里特别常见因为它把宝贵的垂直空间让给了内容需要导航时只需轻微上滚。实现原理是记录上一次的scrollY和当前值做比较。向下滚且超过阈值给 header 加一个隐藏类名向上滚移除隐藏类名。.site-header.hidden { transform: translateY(-100%); }这个transform可以和之前的渐变过渡共存因为transition同时作用于背景色、阴影和 transform互不干扰。注意要确保 transform 的过渡也写进 transition 列表里否则收起和弹出会非常生硬。let lastScrollY window.scrollY; function onScroll() { if (window.scrollY lastScrollY window.scrollY 100) { header.classList.add(hidden); } else { header.classList.remove(hidden); } lastScrollY window.scrollY; }我建议你先实现基础版再往上叠加这个方向感知逻辑每加一层都跑一遍看看效果这样不容易出现“哪一步出了问题但找不到”的情况。最后再说一个我的个人习惯这类小实例我每次写的时候都会故意把导航栏初始状态和滚动后的状态对比着多测几轮尤其是点击锚点跳转、刷新页面、快速滚动这些操作。导航栏虽然只是页面上一个小组件但它承担着整站的入口引导功能一旦状态错乱用户体验的下降是立竿见影的。这套写法我后来在好几个官网和活动页里复用最常调的就是 threshold 和过渡时长——过渡 0.3s 是我觉得最舒服的节奏太快显得机械太慢又拖沓。你根据自己的设计稿调整就行。本文还有配套的精品资源点击获取