最近整理课件时翻出一个挺典型的网页设计案例,之前拿它给培训班学生讲课,也拿来作为面试作品集的工作室官网改过一版。页面不算花哨,但它覆盖了多数中小型站点最关心的事:完整 HTML 结构、CSS 响应式适配、JavaScript 交互,以及一套能直接替换成自己资料的代码。很多人看教程喜欢刷那种动辄上百个特效的大项目,真到自己上手反而不知道该从哪一行开始写。
我会把这份含完整代码的网页案例从头拆开讲一遍,技术栈就是最常见的 html css js,不引框架、不依赖构建工具。页面以“设计工作室落地页”为例,包含顶部导航、首屏、服务、作品、联系表单等常用模块,还加了一点动态交互:导航高亮、问候语、实时时间、滚动入场动画。整站代码在文末一次性贴出,复制成一个 index.html 就能在浏览器里直接打开。
1. 为什么先拆内容模块,比先选酷炫风格更重要
很多零基础拿到“做个网页”这句话,第一反应是去翻版式灵感、找字体配色,结果浏览器的标签页开了一堆,半天没动一行代码。这个习惯不是不能有,而是顺序反了。设计一个页面之前,最先应该回答的其实是:这个页面到底要帮访客解决什么问题。
1.1 客户的一句话,怎么翻译成页面模块
假设现在要做一个设计工作室官网。甲方需求可能只有一句:我想展示作品,让访客知道我能做什么,并且能联系到我。
这句话听起来很虚,但落到网页设计案例里时,可以翻译成三个明确模块:
| 甲方原话 | 页面要完成的任务 | 对应模块 |
|---|---|---|
| 展示作品 | 用图片或卡片让访客快速看到成品 | 作品展示区 |
| 知道我能做什么 | 把擅长领域、服务项目说清楚 | 服务介绍区 |
| 能联系到我 | 提供表单、邮箱或社交入口 | 联系表单/页脚 |
如果还有一句“让打开的人觉得这个工作室有质感”,那么顶部首屏区域就是解决问题的地方。首屏可以是简洁的一句定位语,放大字号,配合干净的背景处理,让第一眼形成品牌记忆。这就是内容模块拆解的成果,它直接决定页面滚动节奏。
1.2 单页滚动与多页组合的取舍
拆完模块后还需要决定:做成一个页面往下滚,还是分成首页、作品页、联系页等多张子页面?
多页结构适合内容量大的商城或企业门户,单页滚动则适合个人品牌、产品发布会、作品集展示,好处是访客不需要跳来跳去,浏览成本低,特别适合首次接触品牌的陌生用户。本案例的目标是“让访客快速了解小团队”,所以我用了单页加锚点导航。顶部导航点击后不是跳新页面,而是平滑滚动到对应区块。
这里有一个非常实用的经验:如果单个模块信息超过 3 屏,强烈建议这个模块单独拆页,比如作品很多,就单独做一个作品列表页。否则单页滚动会变成无休止长列表,视觉效果和加载体验都会大打折扣。
单页的好处是开发时模块边界清晰,每个 <section> 对应一段独立内容,后续修改作品卡片、改联系方式、调整服务文案都不容易动到其他区域。这个“边界清晰”听起来不像功能,却是后续所有代码能写下去的基础。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 把HTML骨架当“楼层”:合理楼层才不会越改越乱
网页设计案例到了具体编码阶段,最容易出的问题不是 CSS 写不出来,而是 HTML 乱。
2.1 顶层结构:header/main/footer 三大区域
即便是第一次做完整页面,也应该从一开始就遵守最基础的语义化结构:
html复制<body>
<header>
<!-- 顶部导航、Logo -->
</header>
<main>
<!-- 首屏、服务、作品、联系表单 -->
</main>
<footer>
<!-- 版权信息、备案号、社媒入口 -->
</footer>
</body>
header 放导航栏,它在一整页里是“常驻楼层”,浏览器滚动时一般会固定在顶部;main 放当前页面的核心内容,全页主体内容都放进来;footer 放版权和补充说明。这在一个纯中文环境里看起来像常识,但实际项目中,经常能看到所有内容全塞满 <div> 的写法,语义标签却一个没用。
语义化标签不只是给开发者看的,也是给搜索引擎和读屏软件看的。访客用读屏软件访问时,它可以快速跳过重复导航直接跳到 <main> 内容。搜索引擎的爬虫爬页面时也更容易判断内容优先级。所以别再问“div 和 section 有什么区别”,简单判断:div 是装内容的通用盒子,section 代表内容里相对独立的一个区块,header/footer/main 则代表页面级楼层。
2.2 一个元素到底用a还是button,应该怎么判断
页面里按钮很多:首屏有“查看作品”,导航有菜单项,表单有“提交留言”。有些新手会全部用 <button>,也有的全用 <a href="#">,区别没搞清就会埋坑。
判断标准非常简单:
- 点击后要跳转到某个位置或新页面,用
<a>加href。锚点跳转如href="#works"就属于这种。 - 当前页发生交互但不改变地址,比如展开菜单、提交表单、弹层,用
<button>。
这个案例里导航菜单的链接是 <a>,首屏“查看更多作品”也是 <a>,因为它们都要帮访客跳到页面的其他区块。而右上角移动端的“菜单”按钮和表单的提交按钮,我用 <button>。如果分类按钮用错了,访问者回车或某些读屏软件点击时不一定会按预期工作,样式处理上也会平白多出很多 cursor 和 type 的问题。
HTML 骨架写成什么样,直接决定后续 CSS 能不能选到元素、JS 能不能稳定监听到事件。我在指导新手时最常说的就是:写页面就像盖楼,第一层砌歪了,后面装修得再好看也是危房。
本案例里的 HTML 关键结构是按下图顺序组织:顶部导航包含 4 个锚点链接;主体 <main> 内有 4 个区块,分别对应首屏、服务、作品、联系;底部 <footer> 放版权信息。完整代码在文末会给出,配合读代码时能更直观看到层级。
3. CSS不靠拼特效:这套配色、栅格和卡片效果怎么落地
CSS 是最容易让人产生“炫技冲动”的部分。有人把玻璃态、渐变、粒子背景、鼠标拖尾全部堆到首页,结果页面打开又慢又乱,这是网页设计案例里最典型的反面教材。
我建议普通展示页先做好三件事:统一变量、正确布局、精致细节。这三点都做好后,再加一两处动态效果已经足够。
3.1 CSS变量:让颜色和间距修改不用全站翻
如果整个页面里写着十几组接近但不一样的橙色,后期改品牌色时只能逐条搜索替换,很容易漏。更好的做法是用 CSS 自定义属性,也就是 CSS 变量。
css复制:root {
--primary: #ff6b35;
--primary-soft: #fff0e8;
--text-main: #1f2937;
--text-sub: #6b7280;
--bg-page: #ffffff;
--bg-light: #f8fafc;
--space-lg: 80px;
}
CSS 变量像项目里的“公共变量表”。把主色、文字色、间距统一放在 :root,后面任何地方要用主色,直接写 color: var(--primary)。哪天品牌色从橙色改成蓝色,只需要改这一行,全站自动同步,比在每个选择器里刷颜色高一个层级。
3.2 卡片区用什么布局系统:Flex 还是 Grid
页面里服务区是三张卡片、作品区是六张大小不等的卡片。这种情况下我会采用 Grid 来处理多列容器布局,因为 Grid 对二维行和列的控制更直观:
css复制.service-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
@media (max-width: 768px) {
.service-grid {
grid-template-columns: 1fr;
}
}
这段代码在桌面端让三张卡片平均占满一行,每张卡片等宽;手机端自动切换成单列纵向排列,访客不需要横向滑动。Flex 更偏向一维的排列,比如导航栏里几个链接横向排开、按钮组内部对齐,用 Flex 更顺手。
Grid 与 Flex 不是二选一的关系,而是要看使用场景。服务卡片和作品卡片用 Grid,因为它们既要控制行间距,也要控制列间距。栅格系统内部如果有“一行按钮居中排列”的需求,小范围再用 Flex。实战里 90% 的基础页面都逃不过这组组合。
3.3 卡片不“廉价”的三个细节
作品区往往是最能体现页面质感的地方。很多人的卡片做出来没高级感,不是内容差,而是细节缺失。我在作品卡片里做了三个处理:
- 把图片占位区做成渐变色块,而不是用空白
<img>,视觉上先有“内容感”。 - 卡片 hover 时有轻微上浮和阴影加深,让访客知道这块可以点。
- 卡片内部文字控制行高、字号层级,标题与描述拉开差距。
对应 CSS 片段就像这样:
css复制.work-card {
background: #fff;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.06);
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.work-card:hover {
transform: translateY(-6px);
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.10);
}
上浮的位移不能太大,稍微动 4 到 8 像素就刚好;阴影要柔和,硬阴影会显得很“塑料”。动效时间控制在 0.2 到 0.3 秒之间,太慢会让页面拖泥带水,太快则感受不到反馈。这套原则不只是对一个案例有效,任何带作品卡片的页面都能直接用。
4. 三个JavaScript交互:导航态、问候语、滚动入场
标题里写的 js 部分,在完整代码里主要表现为三个交互:移动端菜单展开收起、页面问候语与实时时间、元素在滚动时进入视野的淡入效果。每个交互不算特别难,但它们组合起来会让页面从“静态文档”变成“能回应操作的应用”。
4.1 汉堡菜单切换和ARIA状态
窄屏下导航链接不可能全部横向展示,所以要在 HTML 里放置一个“菜单”按钮,通过 JS 点击后把导航菜单显示出来。
javascript复制const navToggle = document.getElementById('navToggle');
const navMenu = document.getElementById('navMenu');
navToggle.addEventListener('click', () => {
const isOpen = navToggle.getAttribute('aria-expanded') === 'true';
navMenu.classList.toggle('open');
navToggle.setAttribute('aria-expanded', String(!isOpen));
});
这里我特别加了 aria-expanded 属性,它是个容易被忽略但很重要的无障碍细节。屏幕阅读器靠这个属性判断菜单当前是展开还是收起,按钮点击后 JS 会同步更新属性,否则视觉上菜单开了,读屏软件里依然认为菜单是关着的。
菜单里的锚点链接点击后,菜单还应该自动收起。实现起来就是再给每个链接加一个 click 事件,若菜单处于 open 状态,就移除 open 并同步 aria-expanded。这段 JS 在完整代码里都有,细节处理方式完全可以直接复用。
4.2 问候语逻辑:时间不只是“显示出来”
页面首屏那句“你好,欢迎来到我的工作室”如果只是写死的文本,用户每次打开看到的是同一句话,动态感就弱了。实现方法很简单,用 Date 对象获取当前小时,再映射成问候语:
javascript复制function getGreeting() {
const hour = new Date().getHours();
if (hour < 5) return '夜深了';
if (hour < 11) return '上午好';
if (hour < 13) return '中午好';
if (hour < 18) return '下午好';
return '晚上好';
}
变量 hour 的范围是 0 到 23,第 4 行判断“早上 5 点之前”的是深夜访客,这在一些挂着页面忘了关的电脑上会很贴切。再配合一个实时时间显示,比如“当前北京时间 20:42:33”,可以用 setInterval 每秒更新一次时间文本,形成一个轻量级的时间模块。
这种“把时间变成动态变量,而不是写死页面文案”的思路,和那些罗盘时钟、倒计时牌、八卦罗盘时钟类组件的底层逻辑是一样的,都是从系统拿到时间状态,再转成 HTML 里的文本或 CSS 旋转角度。理解透彻之后,换一个场景也只是改显示形式,不需要重新学一遍语法。
4.3 滚动入场动画:不需要大屏特效也能有节奏
许多网站有“滚到这里卡片淡入”的动画。过去常用滚动事件监听 scroll 来判断元素进入可视区域,但这种写法在滚动频繁时会出现性能问题,需要自己加节流。现在更推荐 IntersectionObserver,它由浏览器原生检测,性能和写法都更舒服。
javascript复制const revealEls = document.querySelectorAll('[data-reveal]');
if ('IntersectionObserver' in window) {
const io = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
io.unobserve(entry.target);
}
});
});
revealEls.forEach(el => io.observe(el));
}
配合 CSS:
css复制[data-reveal] {
opacity: 0;
transform: translateY(24px);
transition: opacity 0.5s ease, transform 0.5s ease;
}
[data-reveal].is-visible {
opacity: 1;
transform: none;
}
给需要动画的区块加上 data-reveal 属性,等区块进入视口,浏览器自动加上 is-visible 类,元素便完成淡入上移的过渡。用 unobserve 取消观察则是为了防止元素再次离开视野时反复触发,浪费性能。这个交互在本案例里用得很克制,只给服务和作品区每张卡片加了一小段位移,视觉节奏刚刚好。
5. 放到手机前先过一遍:响应式与兼容性清单
网页设计案例如果只在电脑上看,很容易漏掉真实用户观感。我用这个案例做了两版移动端调整,一次是导航从横排变汉堡菜单,一次是卡片栅格从多列降单列。但除了这两个大动作,还有几个经常被忽略的角落值得单独拎出来说。
5.1 viewport 与 rem/px 的取舍
很多新手把页面做完后在手机上打开,发现字非常小、页面自动缩放,第一反应是加 CSS 去改字号,但真正原因往往是 <head> 里少了 viewport 设置。
html复制<meta name="viewport" content="width=device-width, initial-scale=1.0" />
没有这行,浏览器会默认按“近似桌面宽度”去渲染页面,再缩放成手机屏幕宽度,所以字才小得反常。只要加上这行,页面宽度就会跟随设备宽度,再配合 CSS 媒体查询去切换布局,多数基础问题就解决了。
字号单位我建议在组件间用 rem 保持比例,但在直接给某个标题调整尺寸时也会用 clamp()。clamp(1rem, 2vw, 2rem) 表示最小值 1rem、理想值随视口宽度变化、最大不超过 2rem,这种方式非常适合标题做流体缩放,省得在多个断点里反复调字号。
5.2 移动端真正容易踩的三个坑
以下三个问题是我实际改稿时遇到最多的:
第一,背景图固定。在桌面端喜欢把背景图设成 background-attachment: fixed 来制造视差感,但部分 iOS 浏览器对 fixed 背景支持不好,可能出现图片错位或整段白色闪屏。目前更稳妥的做法是,在移动端媒体查询里把 background-attachment 改为 scroll,宁可少一点视觉冲击,也不能让主要内容加载不出来。
第二,输入框的自动缩放。手机访问包含表单的页面时,聚焦输入框后部分 iOS 浏览器会自动放大页面。避免方法就是表单里的字号不小于 16px,如果字号小于 16px,iOS 会触发自动缩放。所以案例里的表单输入字段字号统一设成 16px,防患于未然。
第三,横向溢出。页面里某个元素宽度超出屏幕,就会出现整页能左右拖动。排查方式是在浏览器控制台里临时执行一条代码:
javascript复制document.querySelectorAll('*').forEach(el => {
if (el.scrollWidth > window.innerWidth) {
console.log(el);
}
});
把超宽的元素找出来,再针对性调整宽度。多数时候问题出在图片、长英文单词或 grid 布局里没有写 min-width: 0 的子容器上。这个排查技巧属于“留着不常用,用时很救命”的一类。
5.3 完整代码里的断点设计
文末完整代码使用的是 992px 和 768px 两档断点。 992px 以下作品区从三列降为两列,768px 以下服务区、作品区再降为单列,导航从横排切换为汉堡菜单。不同公司项目里断点可以自定义,但核心是不要给每一种手机尺寸都写一段媒体查询。移动优先还是桌面优先可以看团队喜好,关键是保证内容在中间尺寸过渡时不出现错位和重叠。
6. 完整代码:一个可直接保存运行的index.html
下面把整份网页设计案例完整贴出来。所有 CSS 和 JavaScript 都写在同一个 HTML 文件里,目的是让你复制后不会因为文件引用路径错误而白忙一场。你只需要新建一个文本文件,把代码粘贴进去,保存为 index.html,再用浏览器打开即可。
代码包含:固定导航、首屏问候、服务卡片、作品展示、动态时间、联系表单提示、锚点平滑滚动、移动端菜单、滚动淡入动画。图片位全部用渐变色块代替,不依赖外部图片资源,方便离线浏览。
html复制<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>明予设计工作室 - 网页设计案例</title>
<style>
:root {
--primary: #ff6b35;
--primary-soft: #fff1e8;
--text-main: #1f2937;
--text-sub: #6b7280;
--bg-page: #ffffff;
--bg-light: #f8fafc;
--shadow-soft: 0 4px 14px rgba(0, 0, 0, 0.06);
--shadow-hover: 0 14px 28px rgba(0, 0, 0, 0.10);
--radius-lg: 16px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
color: var(--text-main);
background: var(--bg-page);
line-height: 1.6;
}
a {
text-decoration: none;
color: inherit;
}
ul,
ol {
list-style: none;
}
.container {
width: min(1100px, 92%);
margin: 0 auto;
}
/* Header */
.site-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: rgba(255, 255, 255, 0.88);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
border-bottom: 1px solid transparent;
transition: background-color 0.3s ease, box-shadow 0.3s ease;
}
.site-header.is-scrolled {
background: rgba(255, 255, 255, 0.96);
border-bottom-color: rgba(0, 0, 0, 0.06);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.04);
}
.header-inner {
display: flex;
align-items: center;
justify-content: space-between;
height: 64px;
}
.logo {
font-weight: 700;
font-size: 1.25rem;
color: var(--text-main);
letter-spacing: 1px;
}
.logo span {
color: var(--primary);
}
.nav-menu {
display: flex;
align-items: center;
gap: 28px;
}
.nav-menu a {
font-size: 0.95rem;
font-weight: 500;
color: var(--text-main);
transition: color 0.2s ease;
}
.nav-menu a:hover {
color: var(--primary);
}
.nav-toggle {
display: none;
border: 1px solid rgba(0, 0, 0, 0.12);
background: transparent;
padding: 8px 16px;
border-radius: 999px;
font-size: 0.95rem;
line-height: 1.2;
cursor: pointer;
color: var(--text-main);
}
.nav-toggle:hover {
border-color: var(--primary);
color: var(--primary);
}
/* Hero */
.hero {
min-height: 100vh;
display: flex;
align-items: center;
padding-top: 64px;
background:
radial-gradient(circle at 15% 20%, #fff8f2 0%, transparent 36%),
radial-gradient(circle at 88% 15%, #ffeee2 0%, transparent 38%),
var(--bg-light);
}
.hero-content {
max-width: 760px;
}
.hero-greeting {
color: var(--primary);
font-weight: 600;
margin-bottom: 12px;
font-size: 1.05rem;
}
.hero-title {
font-size: clamp(2.2rem, 5vw, 4rem);
line-height: 1.15;
letter-spacing: -1px;
font-weight: 700;
margin-bottom: 20px;
}
.hero-desc {
font-size: 1.1rem;
color: var(--text-sub);
max-width: 560px;
margin-bottom: 32px;
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 14px;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 12px 24px;
border-radius: 999px;
font-weight: 600;
font-size: 1rem;
transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
cursor: pointer;
border: none;
}
.btn-primary {
background: var(--primary);
color: #fff;
}
.btn-primary:hover {
background: #e85820;
transform: translateY(-2px);
}
.btn-plain {
background: transparent;
color: var(--text-main);
border: 1px solid rgba(31, 41, 55, 0.2);
}
.btn-plain:hover {
border-color: var(--primary);
color: var(--primary);
}
/* Section */
.section {
padding: 90px 0;
}
.section-head {
margin-bottom: 40px;
}
.section-head span {
display: inline-block;
background: var(--primary-soft);
color: var(--primary);
font-size: 0.85rem;
font-weight: 700;
padding: 6px 14px;
border-radius: 999px;
margin-bottom: 12px;
}
.section-head h2 {
font-size: clamp(1.8rem, 3vw, 2.4rem);
line-height: 1.2;
font-weight: 700;
}
.section-head p {
color: var(--text-sub);
margin-top: 12px;
max-width: 520px;
}
/* Service Cards */
.service-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.service-card {
background: var(--bg-page);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: var(--radius-lg);
padding: 30px 26px;
box-shadow: var(--shadow-soft);
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.service-card:hover {
transform: translateY(-6px);
box-shadow: var(--shadow-hover);
}
.service-icon {
width: 46px;
height: 46px;
display: flex;
align-items: center;
justify-content: center;
background: var(--primary-soft);
color: var(--primary);
border-radius: 12px;
font-weight: 700;
margin-bottom: 18px;
}
.service-card h3 {
font-size: 1.2rem;
margin-bottom: 8px;
}
.service-card p {
color: var(--text-sub);
font-size: 0.95rem;
}
/* Works */
.work-section {
background: var(--bg-light);
}
.work-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.work-card {
background: #fff;
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow-soft);
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.work-card:hover {
transform: translateY(-6px);
box-shadow: var(--shadow-hover);
}
.work-cover {
height: 180px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.8);
font-size: 1.4rem;
font-weight: 700;
}
.cover-a {
background: linear-gradient(135deg, #ff9a76, #ff6b6b);
}
.cover-b {
background: linear-gradient(135deg, #4facfe, #667eea);
}
.cover-c {
background: linear-gradient(135deg, #43e97b, #38f9d7);
}
.work-info {
padding: 22px 20px 24px;
}
.work-info h3 {
margin-bottom: 6px;
font-size: 1.1rem;
}
.work-info p {
color: var(--text-sub);
font-size: 0.95rem;
margin-bottom: 14px;
}
.work-tag {
font-size: 0.85rem;
color: var(--primary);
background: var(--primary-soft);
display: inline-block;
padding: 4px 12px;
border-radius: 999px;
}
/* Clock Widget */
.clock-section {
background: var(--bg-page);
text-align: center;
}
.clock-card {
max-width: 560px;
margin: 0 auto;
background: linear-gradient(135deg, #1f2937, #2d3748);
color: #fff;
border-radius: 22px;
padding: 40px 32px;
}
.clock-card .today {
font-size: 0.95rem;
opacity: 0.7;
}
.clock-card .time {
font-size: clamp(2rem, 6vw, 3.2rem);
font-weight: 700;
letter-spacing: 2px;
margin: 10px 0;
font-variant-numeric: tabular-nums;
}
.clock-card .tip {
font-size: 0.9rem;
opacity: 0.7;
}
/* Contact */
.contact-section {
background: var(--bg-light);
}
.contact-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 40px;
align-items: start;
}
.contact-info h3 {
font-size: 1.4rem;
margin-bottom: 12px;
}
.contact-info p {
color: var(--text-sub);
max-width: 400px;
}
.contact-info-item {
margin-top: 18px;
display: flex;
align-items: center;
gap: 10px;
color: var(--text-sub);
}
.contact-form {
background: #fff;
border-radius: var(--radius-lg);
padding: 32px;
box-shadow: var(--shadow-soft);
display: grid;
gap: 16px;
}
.contact-form label {
font-size: 0.9rem;
font-weight: 600;
display: block;
margin-bottom: 6px;
}
.contact-form input,
.contact-form textarea {
width: 100%;
font-size: 1rem;
padding: 12px 14px;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 10px;
outline: none;
font-family: inherit;
background: var(--bg-page);
color: var(--text-main);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.contact-form input:focus,
.contact-form textarea:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(255, 107, 53, 0.12);
}
.contact-form textarea {
resize: vertical;
min-height: 110px;
}
/* Footer */
.site-footer {
padding: 24px 0;
text-align: center;
background: var(--bg-page);
color: var(--text-sub);
font-size: 0.9rem;
border-top: 1px solid rgba(0, 0, 0, 0.06);
}
/* Toast */
.toast {
position: fixed;
left: 50%;
bottom: 30px;
transform: translate(-50%, 20px);
background: #1f2937;
color: #fff;
padding: 12px 22px;
border-radius: 12px;
font-size: 0.95rem;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease, transform 0.3s ease;
z-index: 999;
}
.toast.show {
opacity: 1;
transform: translate(-50%, 0);
}
/* Responsive */
@media (max-width: 992px) {
.work-grid {
grid-template-columns: repeat(2, 1fr);
}
.contact-grid {
grid-template-columns: 1fr;
gap: 28px;
}
}
@media (max-width: 768px) {
.nav-menu {
display: none;
position: absolute;
top: 64px;
left: 0;
right: 0;
flex-direction: column;
align-items: stretch;
gap: 0;
background: #fff;
padding: 10px 6%;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.05);
border-top: 1px solid rgba(0, 0, 0, 0.04);
}
.nav-menu.open {
display: flex;
}
.nav-menu a {
display: block;
padding: 14px 4px;
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
}
.nav-toggle {
display: inline-flex;
}
.section {
padding: 64px 0;
}
.service-grid {
grid-template-columns: 1fr;
gap: 18px;
}
.work-grid {
grid-template-columns: 1fr;
gap: 18px;
}
}
</style>
</head>
<body>
<header class="site-header" id="siteHeader">
<div class="container header-inner">
<a href="#hero" class="logo">明予<span>.</span>Design</a>
<button class="nav-toggle" id="navToggle" aria-label="打开导航菜单" aria-expanded="false" aria-controls="navMenu">
菜单
</button>
<nav class="nav-menu" id="navMenu" aria-label="主导航">
<a href="#hero">首页</a>
<a href="#services">服务</a>
<a href="#works">作品</a>
<a href="#contact">联系</a>
</nav>
</div>
</header>
<main>
<section class="hero" id="hero">
<div class="container hero-content">
<p class="hero-greeting" id="greeting">你好</p>
<h1 class="hero-title">把每一个想法做成<br />清晰、有温度的作品</h1>
<p class="hero-desc">
这是一个用 HTML、CSS、JavaScript 完成的设计工作室落地页。所有文案与配色都可以替换,代码结构保持清晰,便于直接填充真实内容。
</p>
<div class="hero-actions">
<a href="#works" class="btn btn-primary">查看作品</a>
<a href="#contact" class="btn btn-plain">聊需求</a>
</div>
</div>
</section>
<section class="section" id="services">
<div class="container">
<div class="section-head" data-reveal>
<span>服务范围</span>
<h2>不只做页面,也解决展示逻辑</h2>
<p>设计不只是让页面好看,更要让信息在被打开的瞬间形成清晰层级。</p>
</div>
<div class="service-grid">
<article class="service-card" data-reveal>
<div class="service-icon">01</div>
<h3>品牌官网</h3>
<p>面向企业与个人的官网搭建,从文案结构到视觉落地一次完成。</p>
</article>
<article class="service-card" data-reveal>
<div class="service-icon">02</div>
<h3>活动落地页</h3>
<p>适合发布会、课程招生、活动报名等场景,以短平快页面快速完成信息传达。</p>
</article>
<article class="service-card" data-reveal>
<div class="service-icon">03</div>
<h3>网页代码修复</h3>
<p>排查响应式错位、交互失灵、页面加载卡顿等具体问题,适合已有网站二次优化。</p>
</article>
</div>
</div>
</section>
<section class="section work-section" id="works">
<div class="container">
<div class="section-head" data-reveal>
<span>近期作品</span>
<h2>一些为了解决问题而做的页面</h2>
<p>案例不以炫技为目的,而是希望你看到设计如何让信息更容易被理解。</p>
</div>
<div class="work-grid">
<article class="work-card" data-reveal>
<div class="work-cover cover-a">Plant App</div>
<div class="work-info">
<h3>植物养护小站</h3>
<p>面向植物新手的移动端界面,把浇水提醒与知识库做成轻量化工具。</p>
<span class="work-tag">移动端 UI</span>
</div>
</article>
<article class="work-card" data-reveal>
<div class="work-cover cover-b">City Guide</div>
<div class="work-info">
<h3>城市漫游指南</h3>
<p>以路线故事为核心的网页内容页,在图片与文字之间控制浏览节奏。</p>
<span class="work-tag">内容排版</span>
</div>
</article>
<article class="work-card" data-reveal>
<div class="work-cover cover-c">Market Data</div>
<div class="work-info">
<h3>数据看板样式</h3>
<p>把复杂报表转成卡片式信息看板,适合后台系统前端改造。</p>
<span class="work-tag">PC 端</span>
</div>
</article>
</div>
</div>
</section>
<section class="section clock-section" id="clock">
<div class="container">
<div class="section-head" data-reveal>
<span>时间模块</span>
<h2>不需要花哨,也能让页面“活”一点</h2>
<p>用 JavaScript 读取系统时间并动态渲染,这段代码在完整文件中可以随时移植到任何项目。</p>
</div>
<div class="clock-card" data-reveal>
<p class="today" id="todayText">今天</p>
<p class="time" id="timeText">--:--:--</p>
<p class="tip" id="periodText">欢迎光临</p>
</div>
</div>
</section>
<section class="section contact-section" id="contact">
<div class="container">
<div class="section-head" data-reveal>
<span>联系</span>
<h2>把需求说清楚,我们才知道怎么帮到你</h2>
<p>留个简单表单作为示例,实际部署时可以在页面里接入你的邮箱或后端服务。</p>
</div>
<div class="contact-grid">
<div class="contact-info" data-reveal>
<h3>直接找我们聊聊</h3>
<p>
网站不是越复杂越好,而是要找到最适合业务阶段的展示方式。如果你的项目刚好处于起步期,哪怕只有一个想法,也可以先约一次沟通。
</p>
<div class="contact-info-item">邮箱:hello@example.com</div>
<div class="contact-info-item">坐标:某城市(也接受远程)</div>
</div>
<form class="contact-form" id="contactForm" data-reveal>
<div>
<label for="name">称呼</label>
<input type="text" id="name" name="name" placeholder="怎么称呼你" autocomplete="name" />
</div>
<div>
<label for="email">邮箱</label>
<input type="email" id="email" name="email" placeholder="name@example.com" autocomplete="email" />
</div>
<div>
<label for="message">项目描述</label>
<textarea id="message" name="message" placeholder="简单描述一下现状或需求"></textarea>
</div>
<div>
<button type="submit" class="btn btn-primary">发送留言</button>
</div>
</form>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container">
<p>© 明予设计工作室 · 本页面可作为完整网页设计案例模板使用</p>
</div>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script>
// 1. 导航栏滚动状态
const header = document.getElementById('siteHeader');
window.addEventListener('scroll', function () {
if (window.scrollY > 16) {
header.classList.add('is-scrolled');
} else {
header.classList.remove('is-scrolled');
}
});
// 2. 移动端汉堡菜单切换
const navToggle = document.getElementById('navToggle');
const navMenu = document.getElementById('navMenu');
navToggle.addEventListener('click', function () {
const isOpen = navToggle.getAttribute('aria-expanded') === 'true';
navMenu.classList.toggle('open');
navToggle.setAttribute('aria-expanded
