简介:在线黑色响应式全屏滚动主页HTML源码是一套面向网页设计者、前端学习者及需要快速上线展示页的开发者的响应式网站模板,整体采用黑色高对比主题与全屏滚动布局,适用于品牌官网、个人作品集、产品发布或活动专题页等场景。压缩包共包含37个文件,其中2个HTML页面负责结构,3个CSS样式表控制黑色配色与媒体查询断点,6个JavaScript脚本实现平滑滚动与交互效果,另配齐jpg/png图片、woff/ttf字体、mp4视频等展示素材,整体仅2.72MB,目录结构清晰,便于直接解压预览和二次定制。目前该资源已有187人学习下载。源码基于CSS3媒体查询自动适配桌面、平板和手机屏幕,又借助JS实现全屏滚动时的流畅过渡,兼顾视觉冲击力与阅读舒适性;包内说明文本对文件用途和修改入口做了交代,开发者可快速替换图片文字、调整断点与动画参数,也可结合HTML5语义化标签理解SEO友好的页面骨架,是一份能帮助初学者将响应式布局与全屏滚动原理落到实际项目的完整示例。 前两天接了个小活儿:做一个黑色调的全屏滚动主页。客户没给设计稿,只留了一句"要高端的、沉浸式的、一屏一屏滑下去那种"。这些年类似的页面我写过不少,黑色响应式全屏滚动主页这类 html 源码,单看每个技术点好像都不难,可真要做顺了,坑全在暗处——黑暗色调的层次怎么铺、滚动切换的节奏怎么控、移动端视口会不会穿帮,每一处都能把人折磨到怀疑人生。
这篇文章我把整套实现思路完整拆开。不是给你一份能跑的最小 demo 就完事,而是把为什么这样设计、哪里最容易翻车、代码每一块在干什么都讲明白。想学网页制作、想独立完成全屏滚动主页的人可以照着走一遍,手头有类似需求急着出活的朋友也可以直接拿走核心代码。
1. 黑色主页不是"把背景改成黑":配色、字体与视觉层次
黑色调主页为什么受欢迎?答案很简单:深色背景能天然把视觉焦点压到内容上,配合高对比文字和克制的强调色,很容易营造出作品集、品牌官网应有的"高级感"。但我也见过太多黑色页面翻车——上来就用#000铺满整张画布,结果整个页面一片死黑,区块之间没有层次,文字和背景糊在一起,用户看十秒眼睛就累了。
1.1 一套能直接抄的黑色系色板
黑色页面真正需要的是一个灰阶层级,而不是单一的黑。我做这类主页时一般会用下面这套色板,稳了三年没改过:
| 角色 | 色值 | 用途 |
|---|---|---|
| 主背景 | #0a0a0a | 页面底色,接近黑但不刺眼 |
| 次级背景 | #141414 | 卡片、内容区块、页脚 |
| 描边/分隔 | #262626 | 边框、分隔线、输入框描边 |
| 主文字 | #f5f5f5 | 标题、正文主要文字 |
| 次要文字 | #a3a3a3 | 辅助说明、时间、标签 |
| 强调色 | #d4af37或#00e5a0 | 按钮、链接高亮、关键数据 |
注意主背景这里故意不用#000。纯黑在普通屏幕上会丢层次,所有深色元素都会糊在一起;换成#0a0a0a之后,卡片#141414和底色之间会有一点细微的明度差,空间感一下子就出来了。这就像穿一身黑西装,也要靠面料光泽度区分上衣和裤子,纯黑反而显得廉价。
1.2 强调色和字体:决定高级感的两只手
强调色选什么,决定了整个页面的气质。黑色配金色,往"奢侈品、摄影工作室、腕表品牌"那个方向走;黑色配青色或荧光绿,是科技、电竞、极客感;黑色配大面积的留白和白色细线,走当代艺术展览的路线。我的建议是一个页面只保留一种强调色,最多用它做按钮、当前状态和关键数字,其他地方保持黑白灰。黑色页面最怕五颜六色,一花就廉价。
字体方面,中文我习惯用思源黑体(Noto Sans SC)或 HarmonyOS Sans,英文用 Inter、Space Grotesk,标题想要点性格就上 Space Grotesk 加字重。这里有个很容易被忽略的点:黑色页面上字体的加载体验比白色页面更敏感。白色页面字体闪一下,用户可能注意不到;黑色页面如果字体晚加载,先是系统字体挤一版,再突然变成设计字体,整个视觉节奏就断了。后面第 5 章我会专门讲怎么处理。
2. 全屏滚动到底怎么实现:选型、原生逻辑与代码骨架
全屏滚动,说白了就是把页面内容切成若干个正好等于视口高度的区块,每次滚动切换一整屏。这个交互在作品集、产品介绍页、活动落地页里非常常见。很多人第一反应是上 fullPage.js 这种现成库,我反而建议你自己写。
2.1 为什么要自己写,而不是直接上 fullPage.js
先给个结论:纯展示型的全屏滚动主页,手写是更划算的选择。
fullPage.js 老版本要依赖 jQuery,新版本虽然不依赖了,但体积和功能对"一个会滚动的页面"来说还是偏重。更关键的问题是,全屏滚动真正需要的是滚动手感的控制权——多快切换、能不能中途打断、滚动触发阈值是多少。用现成库时这些虽然也能配,但为了调一个手感去翻库的文档和 CSS 变量,反而比手写还慢。
手写核心逻辑大概就三五十行 JavaScript,不依赖任何第三方包,加载快、好维护,而且所有状态都捏在自己手里,加导航联动、加分页指示器、加内容渐入动画,都是顺理成章的事。当然,如果你的需求是"每一屏里还要横向滑子屏""多个区块嵌套滚动"这种复杂结构,那确实应该用库。但单页全屏展示,手写完全够用。
2.2 一屏一屏滚动的原生实现方案
先说页面结构。每个区块是一个<section class="slide">,在 CSS 里让它们占满视口:
.slide { min-height: 100vh; display: flex; align-items: center; justify-content: center; }然后监听wheel事件判断滚动方向,再调用window.scrollTo平滑滚到对应区块。核心逻辑看这段:
const slides = document.querySelectorAll('.slide'); let current = 0; let isScrolling = false; function goTo(index) { if (index < 0 || index >= slides.length || isScrolling) return; current = index; isScrolling = true; window.scrollTo({ top: slides[index].offsetTop, behavior: 'smooth' }); setTimeout(() => { isScrolling = false; }, 1000); } window.addEventListener('wheel', (e) => { e.preventDefault(); if (isScrolling) return; const delta = e.deltaY; if (delta > 0) goTo(current + 1); else if (delta < 0) goTo(current - 1); }, { passive: false });这里e.preventDefault()必须有,同时addEventListener第三个参数要传{ passive: false },否则浏览器会忽略拦截,页面还是会按原生滚轮连续滚动。isScrolling是一个节流锁,因为部分鼠标和触控板的wheel事件会高频触发,没有这把锁,一次滚动操作可能会连跳两三屏,体验完全崩掉。锁的释放时间要和scrollTo的平滑动画时长匹配,1000 毫秒是我测试下来比较稳的值。
移动端没有滚轮,需要监听触摸的开始和结束位置,滑动距离超过阈值再切屏:
let startY = 0; window.addEventListener('touchstart', (e) => { startY = e.touches[0].clientY; }, { passive: true }); window.addEventListener('touchend', (e) => { const deltaY = startY - e.changedTouches[0].clientY; if (Math.abs(deltaY) > 50) { if (deltaY > 0) goTo(current + 1); else goTo(current - 1); } }, { passive: true });移动端阈值我一般设在 50px 左右,太灵敏会导致"只是想轻轻滑一下却切了屏",太迟钝又让用户觉得页面"滑不动"。
2.3 滚动手感调优:动画时长、边界处理与按键支持
全屏滚动能不能让人产生"顺滑"的感觉,主要在细节。
第一,动画时长和缓动曲线。window.scrollTo的behavior: 'smooth'用的是浏览器默认的滚动插值,在部分浏览器里偏"机械"。想完全掌控手感,可以用 CSS 的scroll-behavior: smooth配合scroll-padding,或者干脆用requestAnimationFrame自己做缓动动画。个人经验是,一屏切换的动画时长控制在 800 毫秒左右最舒服,太短显得急躁,太长又拖沓。
第二,第一屏和最后一屏要有"到此为止"的意识。在goTo函数里判断索引边界,已经到最后一屏时继续往下滚,不能再触发滚动。很多初次实现的人会漏掉这个,结果页面到底了还在空滚,浏览器地址栏被反复拉出来又缩回去,观感很差。
第三,补上键盘操作。触控板和鼠标滚轮都解决了,但还有一部分用户习惯用空格键、Page Down 和方向键。这几个按键的默认行为本身就是"滚动一屏"或"滚动一屏半",如果没有显式拦截,会和我们的节流锁打架。可以这样补:
window.addEventListener('keydown', (e) => { if (e.key === 'ArrowDown' || e.key === 'PageDown' || e.key === ' ') { e.preventDefault(); goTo(current + 1); } else if (e.key === 'ArrowUp' || e.key === 'PageUp') { e.preventDefault(); goTo(current - 1); } });最后提醒一句:全屏滚动的页面,每一屏的内容量必须控制在视口以内。这是全屏滚动的天条。如果某块内容在手机上超过一屏,滚动到该屏顶部后内部内容会被"截断",用户既看不到完整内容,又没法自然滚动。要么精简文案,要么把超长内容改成弹层、独立页面,不要在窄屏上硬塞。
3. 响应式适配:断点、流式文字与移动端导航重构
黑色调解决的是"好看",全屏滚动解决的是"交互",响应式解决的是"在任何屏幕上都成立"。三个维度缺一个,项目都会垮。而响应式这块,最容易出问题的是断点设置、字体单位和导航菜单。
3.1 断点与单位:别再用固定 px 写大标题
移动优先是我一贯的做法,先写手机端样式,再逐步往大屏加。这套页面我常用的断点如下:
| 断点 | 适配场景 |
|---|---|
| 375px 及以下 | 小屏手机,移动端基础样式 |
| 768px | 大屏手机横屏、平板竖屏,开始调整布局列数 |
| 1024px | 平板横屏、小笔记本,导航从汉堡恢复为横排 |
| 1280px 及以上 | 大屏桌面端,加大留白和字号上限 |
单位方面,全屏滚动页面的大标题如果直接用固定px,到了大屏会显得单薄,小屏又会溢出。推荐用clamp()做流式字号,比如首页主标题这样写:
.slide-title { font-size: clamp(2.2rem, 6vw, 5rem); line-height: 1.15; }clamp的三个参数分别是最小值、首选值、最大值。6vw 是相对视口宽度的比例,所以屏幕越宽标题越大,但不会无限增大,到 5rem 就封顶了。间距也优先用rem或vh,这样整个页面在竖屏手机和横屏桌面之间切换时,比例是"弹性"的,不是硬邦邦地缩放。
3.2 导航在移动端的正确打开方式
桌面端顶部一个黑底半透明导航栏,横向摆四五个链接,很常规。但到了移动端,横向链接会占满本就有限的宽度,所以必须换成汉堡菜单 + 全屏菜单遮罩的组合。这里最大的坑是滚动穿透——点击汉堡按钮弹出全屏菜单后,手指在遮罩上滑动,底下的页面居然还在跟着滚。
解决思路分成两层:
body.menu-open { overflow: hidden; }这层负责锁住 PC 端和大部分安卓浏览器的滚动。但 iOS Safari 有时候光靠overflow: hidden不够,还要在菜单打开时给touchmove事件加一把preventDefault锁,或者在菜单内部捕获滚动事件:
const menuOverlay = document.querySelector('.menu-overlay'); menuOverlay.addEventListener('touchmove', (e) => { e.preventDefault(); }, { passive: false });全屏菜单本身也要有设计感——黑色大字号链接纵向排列,配合逐个渐入的小动画,体验就完全不一样了。汉堡按钮的三条线在展开后变成叉号,这个小细节我是在 CSS 里用span的旋转实现的,记得给按钮加aria-label,屏幕阅读器才能读出来。
3.3 背景图与内容密度:小屏上的减法
全屏滚动页通常会用大图做背景,桌面端一张 1920 宽的图很有冲击力,但直接搬到手机端就是灾难——流量大、加载慢、还容易变形。我的做法是在 CSS 里给不同断点换不同分辨率的背景图:
.slide-bg { background: url('../img/bg-desktop.jpg') center/cover no-repeat; } @media (max-width: 768px) { .slide-bg { background-image: url('../img/bg-mobile.jpg'); } }移动端背景图宽度 750px 左右足够,体积能压掉一半。同时移动端的文案要"做减法":副标题可以隐藏,次要说明折叠进按钮点击后的弹层。全屏页面在小屏上最怕信息过载,一屏放不下就删,删到只留最关键的那句话。
4. 源码拆解:从 HTML 骨架到 JS 状态管理逐步过一遍
到这里,设计思路、交互逻辑和响应式方案都定了,下面是一份可以直接落地的核心源码。为了看起来清爽,我拆成三块讲。
4.1 HTML 结构:一屏一个 section,导航与分页指示器
<!doctype html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>在线黑色响应式全屏滚动主页</title> <link rel="stylesheet" href="css/style.css"> </head> <body> <header class="site-header"> <div class="logo">BLACK</div> <nav class="site-nav" id="siteNav"> <a href="#home" class="active">首页</a> <a href="#about">关于</a> <a href="#works">作品</a> <a href="#contact">联系</a> </nav> <button class="menu-toggle" id="menuToggle" aria-label="打开菜单"> <span></span><span></span><span></span> </button> </header> <main class="slide-container"> <section class="slide active" id="home"> <div class="slide-content"> <p class="eyebrow">PORTFOLIO / 2025</p> <h1 class="slide-title">把每个屏幕<br>当作一页作品</h1> <p class="slide-desc">黑色背景下的全屏滚动主页,是作品集与品牌展示的经典模板。</p> <a href="#about" class="btn">开始浏览</a> </div> </section> <section class="slide" id="about"> <div class="slide-content"> <p class="eyebrow">ABOUT</p> <h2 class="slide-title">关于这个项目</h2> </div> </section> <section class="slide" id="works"> <div class="slide-content"> <p class="eyebrow">WORKS</p> <h2 class="slide-title">精选作品</h2> </div> </section> <section class="slide" id="contact"> <div class="slide-content"> <p class="eyebrow">CONTACT</p> <h2 class="slide-title">联系我</h2> </div> </section> </main> <div class="pagination" id="pagination"> <span class="active"></span> <span></span> <span></span> <span></span> </div> <div class="menu-overlay" id="menuOverlay"> <nav> <a href="#home">首页</a> <a href="#about">关于</a> <a href="#works">作品</a> <a href="#contact">联系</a> </nav> </div> <script src="js/main.js"></script> </body> </html>HTML 这块有个值得注意的设计:导航链接的href和 section 的id是对应的。我既可以通过 JS 拦下来做全屏切换,也可以在 JS 失效的时候让锚点跳转兜底,保证了最基本的前端可用性。
4.2 CSS 核心样式:黑色主题与内容渐入的衔接
* { margin: 0; padding: 0; box-sizing: border-box; } html { scroll-behavior: smooth; } body { background: #0a0a0a; color: #f5f5f5; font-family: "Inter", "Noto Sans SC", sans-serif; line-height: 1.6; } .site-header { position: fixed; top: 0; left: 0; right: 0; z-index: 100; display: flex; justify-content: space-between; align-items: center; padding: 24px 5vw; background: rgba(10, 10, 10, 0.8); backdrop-filter: blur(12px); border-bottom: 1px solid #262626; } .site-nav a { color: #a3a3a3; text-decoration: none; margin-left: 32px; transition: color 0.3s; } .site-nav a.active, .site-nav a:hover { color: #f5f5f5; } .slide { min-height: 100vh; display: flex; align-items: center; padding: 100px 8vw 60px; } .slide-content { opacity: 0; transform: translateY(40px); transition: opacity 0.8s ease 0.3s, transform 0.8s ease 0.3s; } .slide.active .slide-content { opacity: 1; transform: translateY(0); } .btn { display: inline-block; margin-top: 28px; padding: 14px 36px; border: 1px solid #d4af37; color: #d4af37; text-decoration: none; letter-spacing: 0.15em; transition: background 0.3s, color 0.3s; } .btn:hover { background: #d4af37; color: #0a0a0a; }这里的核心技巧是内容渐入动画依赖.active类的切换。每一屏的内容默认是透明且向下偏移的,当 JS 把.active加给当前屏时,内容才渐变进入。这个动画延迟了 0.3 秒,为的是让"整屏切换"先有一个位置变化,内容再接着淡入,视觉层次感更强,不会觉得所有东西都在同时动。
4.3 JS 滚动控制:状态、节流与事件绑定的完整实现
const slides = document.querySelectorAll('.slide'); const navLinks = document.querySelectorAll('.site-nav a'); const overlayLinks = document.querySelectorAll('.menu-overlay nav a'); const dots = document.querySelectorAll('.pagination span'); const menuToggle = document.getElementById('menuToggle'); const menuOverlay = document.getElementById('menuOverlay'); const total = slides.length; let current = 0; let isScrolling = false; function goTo(index) { if (index < 0 || index >= total || isScrolling) return; current = index; isScrolling = true; slides.forEach((slide, i) => { slide.classList.toggle('active', i === index); }); navLinks.forEach((link, i) => { link.classList.toggle('active', i === index); }); dots.forEach((dot, i) => { dot.classList.toggle('active', i === index); }); window.scrollTo({ top: slides[index].offsetTop, behavior: 'smooth' }); setTimeout(() => { isScrolling = false; }, 1000); } window.addEventListener('wheel', (e) => { e.preventDefault(); if (isScrolling) return; if (e.deltaY > 0) goTo(current + 1); else if (e.deltaY < 0) goTo(current - 1); }, { passive: false }); let startY = 0; window.addEventListener('touchstart', (e) => { startY = e.touches[0].clientY; }, { passive: true }); window.addEventListener('touchend', (e) => { const deltaY = startY - e.changedTouches[0].clientY; if (Math.abs(deltaY) > 50) { if (deltaY > 0) goTo(current + 1); else goTo(current - 1); } }, { passive: true }); navLinks.forEach((link, index) => { link.addEventListener('click', (e) => { e.preventDefault(); goTo(index); }); }); overlayLinks.forEach((link, index) => { link.addEventListener('click', (e) => { e.preventDefault(); closeMenu(); goTo(index); }); }); dots.forEach((dot, index) => { dot.addEventListener('click', () => { goTo(index); }); }); window.addEventListener('keydown', (e) => { if (e.key === 'ArrowDown' || e.key === 'PageDown' || e.key === ' ') { e.preventDefault(); goTo(current + 1); } else if (e.key === 'ArrowUp' || e.key === 'PageUp') { e.preventDefault(); goTo(current - 1); } }); // 移动端全屏菜单 menuToggle.addEventListener('click', () => { menuOverlay.classList.add('open'); document.body.classList.add('menu-open'); }); function closeMenu() { menuOverlay.classList.remove('open'); document.body.classList.remove('menu-open'); } menuOverlay.addEventListener('touchmove', (e) => { e.preventDefault(); }, { passive: false });这套 JS 用goTo(index)一个方法统一管理所有入口:滚轮、触摸、导航点击、分页点、键盘。好处是状态同步不会乱——无论用户从哪个入口切屏,当前索引、导航高亮、分页指示器和屏内渐入动画都是同一套逻辑在驱动。如果每个入口各自写一套滚动代码,最后必然出现"导航高亮了但屏没切"或者"分页点没跟着更新"的 bug。
5. 实战踩坑记录:移动端视口、滚动穿透与首屏加载
源码能跑通只是第一步,真正决定一个黑色全屏滚动主页质量的是这些"不到真机上看不出来"的细节。这几个坑我几乎每个项目都会踩一遍,写出来给你打个预防针。
5.1 100vh 在手机浏览器上并不可靠
min-height: 100vh在桌面端没有问题,但在 iOS Safari 和部分安卓浏览器上,100vh 计算的是浏览器窗口外沿的高度,包含了底部地址栏和顶部状态栏。用户滑到底部时地址栏会自动收起,100vh 就比可视区域高了一截,导致最后一屏内容被裁切,或者滚动到底还有一段空白。
现在的通用解法是用动态视口单位dvh:
.slide { min-height: 100vh; min-height: 100dvh; }先写100vh兜底兼容老浏览器,再写100dvh覆盖支持动态视口的现代浏览器。Safari 16.4 之后的版本对dvh支持已经很稳了,可以放心用。如果团队里还有不得不在老 iOS 上看的场景,也可以监听resize事件用window.innerHeight手动设置每个 section 的高度,但这套方案代码量会大不少,能不碰就不碰。
5.2 打开全屏菜单后,背景页面还在偷偷滚动
前面已经提到,iOS 上overflow: hidden有时候锁不住背景滚动。更隐蔽的一个场景是:全屏菜单本身内容不满一屏,手指在菜单下半部分的空白区域滑动,事件会"穿透"到背景页面。
我的办法是双保险。第一层给 body 加overflow: hidden,第二层给菜单遮罩的touchmove事件加preventDefault。注意passive: false一定要加,不加的话preventDefault会被浏览器直接忽略掉,这个坑在安卓 Chrome 上尤其明显。
另外提醒一点,菜单里的链接点击后要记得关闭菜单,并且用一个closeMenu()把 body 的滚动锁解开。如果漏掉了menu-open类的移除,用户关掉菜单后发现页面滚不动,那就是这个原因。
5.3 黑色页面上最容易被忽视的首屏加载体验
黑色页面的首屏加载有两个隐藏问题。第一个是大背景图的闪白。图片加载完成前,黑色背景区域是正常的,但如果某张图片本身偏暗,未加载完成的透明状态下页面可能短暂露出白色底色,在暗色页面上这一闪非常刺眼。解决方法是给图片容器先铺一层深色占位:
.slide-bg { background-color: #141414; }第二个是字体加载导致的样式跳动。英文字体和中文字体可以从 Google Fonts 或字库 CDN 加载,但要用font-display: swap配合预加载。我在 HTML 里会这样写:
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>同时把页面首屏用到的标题字体用rel="preload"提前拉下来,避免用户看到"先用系统字体显示,几秒后突然变成设计字体"的尴尬切换。
顺手再分享一个我做黑色页面的体感:黑色页面上的阴影和光晕要克制。白色页面上一个淡淡的投影能让卡片"浮起来",黑色页面上如果投影太重,反而会让卡片糊成一块。需要区隔时,多用描边1px solid #262626,少用box-shadow。这条经验帮我少返了无数次工。
这套黑色响应式全屏滚动主页的源码,核心其实就三件事:一套有层次的暗色视觉体系、一个可靠的滚动控制核心、一组覆盖真机细节的响应式方案。拿到这里讲过的代码,改成自己的内容和配色,部署到任意静态托管平台就能直接用。我每次做这类页面都会把节流时长、切换动画、移动端阈值这些参数单独抽成变量,方便客户说要"再慢一点""再灵敏一点"的时候,五分钟就能调完收工。
本文还有配套的精品资源,点击获取