响应式企业官网:零框架综合实战

前置:HTML 语义化页面Flexbox 布局Grid 布局响应式设计Font Awesome 基础

目标:不依赖任何框架和构建工具,用 HTML + CSS + 原生 JavaScript 完成一个可直接部署的企业官网单页。这是检验 01-基础是否扎实的试金石。


1. 项目概览

1.1 需求清单

模块功能点涉及技术
导航栏锚点平滑滚动、滚动后变色、移动端汉堡菜单CSS scroll-behavior、checkbox 技巧
Hero 区全屏首屏、标语与行动按钮Flex 布局、渐变背景
特性区三列特性卡片Flex 换行布局
服务区六格服务展示Grid 网格
联系区表单校验与提交反馈表单语义、JS 校验
动效各区块进入视口时淡入上移IntersectionObserver
图标全站图标Font Awesome CDN
SEOmeta 描述、Open Graph 分享标签head 元信息

1.2 页面结构

flowchart TD
    A[header 固定导航] --> B[section#home Hero 首屏]
    B --> C[section#features 特性卡片]
    C --> D[section#services 服务展示]
    D --> E[section#about 关于我们]
    E --> F[section#contact 联系表单]
    F --> G[footer 页脚]

单页锚点导航的关键是每个 section 的 id 与导航链接的 href 一一对应,浏览器原生支持 #id 跳转,我们要做的是让它”平滑”且考虑固定导航的遮挡。

2. 项目骨架与 SEO 头部

<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
  <!-- SEO 基础三件套 -->
  <title>云舟科技 - 企业数字化转型服务商</title>
  <meta name="description" content="云舟科技提供企业级软件定制、数据中台与云计算服务,已服务 500+ 企业客户。">
  <meta name="keywords" content="软件开发,数据中台,云计算,数字化转型">
 
  <!-- Open Graph:分享到微信/微博/社交平台的卡片信息 -->
  <meta property="og:type" content="website">
  <meta property="og:title" content="云舟科技 - 企业数字化转型服务商">
  <meta property="og:description" content="云舟科技提供企业级软件定制、数据中台与云计算服务。">
  <meta property="og:image" content="https://www.example.com/assets/og-cover.jpg">
  <meta property="og:url" content="https://www.example.com">
 
  <!-- 移动端主题色(部分浏览器地址栏会着色) -->
  <meta name="theme-color" content="#1a5fb4">
 
  <!-- Font Awesome 图标库 -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
 
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <!-- 内容见后续模块 -->
  <script src="js/main.js" defer></script>
</body>
</html>

要点:

  • lang="zh-CN" 影响屏幕阅读器发音与搜索引擎语言判断;
  • viewport 缺失则移动端会把页面按 980px 宽缩放显示,响应式无从谈起;
  • OG 标签决定链接分享出去时显示的标题、描述与配图,做官网必配;
  • <script defer> 保证 DOM 解析完再执行脚本,不必把 script 挪到 body 底部。

3. 导航栏:锚点平滑滚动与汉堡菜单

3.1 HTML 结构

<header class="site-header" id="siteHeader">
  <nav class="container nav">
    <a href="#home" class="nav-logo">
      <i class="fa-solid fa-water"></i> 云舟科技
    </a>
 
    <!-- checkbox 技巧核心:input 放在 label 之前,利用 :checked 兄弟选择器控制菜单 -->
    <input type="checkbox" id="navToggle" class="nav-toggle" aria-label="打开菜单">
    <label for="navToggle" class="nav-burger">
      <i class="fa-solid fa-bars"></i>
    </label>
 
    <ul class="nav-menu">
      <li><a href="#home">首页</a></li>
      <li><a href="#features">产品特性</a></li>
      <li><a href="#services">服务体系</a></li>
      <li><a href="#about">关于我们</a></li>
      <li><a href="#contact" class="nav-cta">联系我们</a></li>
    </ul>
  </nav>
</header>

3.2 平滑滚动:一行 CSS 加一个偏移修正

html {
  scroll-behavior: smooth;
}
/* 锚点跳转时给目标留出固定导航的高度,避免标题被遮住 */
section[id] {
  scroll-margin-top: 72px;
}

scroll-behavior: smooth 让所有锚点跳转自带平滑动画;scroll-margin-top 是比 JS 计算偏移优雅得多的原生方案。

3.3 汉堡菜单:纯 CSS checkbox 技巧

原理:input[type=checkbox] 在视觉上隐藏但保留可勾选状态,label 充当汉堡按钮;勾选后通过兄弟选择器展开菜单——全程无需 JS。

.nav-toggle {
  display: none;           /* 隐藏 input 本体,功能保留 */
}
.nav-burger {
  display: none;
  font-size: 1.5rem;
  cursor: pointer;
}
 
@media (max-width: 768px) {
  .nav-burger { display: block; }
 
  .nav-menu {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: #fff;
    flex-direction: column;
    padding: 1rem 0;
    box-shadow: 0 8px 16px rgba(0,0,0,.08);
    /* 收起态:最大高度为 0 配合过渡实现展开动画 */
    max-height: 0;
    overflow: hidden;
    transition: max-height .3s ease;
  }
  /* 勾选 checkbox 时展开紧邻其后的菜单 */
  .nav-toggle:checked ~ .nav-menu {
    max-height: 400px;
  }
}

这个技巧的价值在于理解选择器只能向后找兄弟,所以 input 必须写在菜单元素之前——CSS 没有父选择器和前向选择器,DOM 结构要为样式服务让路。

3.4 点击菜单项后自动收起(少量 JS)

// js/main.js
document.querySelectorAll('.nav-menu a').forEach(link => {
  link.addEventListener('click', () => {
    document.getElementById('navToggle').checked = false;
  });
});

3.5 导航栏滚动态

const header = document.getElementById('siteHeader');
window.addEventListener('scroll', () => {
  // 滚过首屏一半后给导航加白底阴影
  header.classList.toggle('scrolled', window.scrollY > 60);
}, { passive: true });
.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  height: 72px;
  background: transparent;
  transition: background .3s, box-shadow .3s;
  z-index: 100;
}
.site-header.scrolled {
  background: #fff;
  box-shadow: 0 2px 12px rgba(0,0,0,.08);
}

passive: true 告诉浏览器本监听器不会调用 preventDefault,滚动事件处理不再阻塞渲染主线。

4. Hero 区与特性卡(Flex)

4.1 Hero 首屏

<section id="home" class="hero">
  <div class="container hero-inner">
    <h1>让每一家企业<br>都拥有自己的数字引擎</h1>
    <p>从咨询、开发到运维的一站式数字化服务</p>
    <div class="hero-actions">
      <a href="#contact" class="btn btn-primary">免费咨询</a>
      <a href="#services" class="btn btn-ghost">了解服务</a>
    </div>
  </div>
</section>
.hero {
  min-height: 100vh;
  display: flex;
  align-items: center;
  background:
    linear-gradient(135deg, rgba(26,95,180,.92), rgba(10,50,120,.88)),
    url('../assets/hero-bg.jpg') center / cover no-repeat;
  color: #fff;
  text-align: center;
}
.hero-inner { max-width: 720px; }
.hero h1 { font-size: clamp(2rem, 5vw, 3.5rem); line-height: 1.25; }
.hero p  { font-size: 1.25rem; margin: 1rem 0 2rem; opacity: .9; }

两个细节值得记住:渐变叠加图片保证文字在任何底图上都有对比度;clamp() 让标题字号在移动端与桌面端之间平滑伸缩,替代多断点媒体查询。

4.2 按钮体系

.btn {
  display: inline-block;
  padding: .75rem 2rem;
  border-radius: 999px;
  font-weight: 600;
  text-decoration: none;
  transition: transform .2s, box-shadow .2s;
}
.btn:hover { transform: translateY(-2px); }
.btn-primary {
  background: #1a5fb4;
  color: #fff;
  box-shadow: 0 4px 14px rgba(26,95,180,.4);
}
.btn-ghost {
  border: 1px solid rgba(255,255,255,.7);
  color: #fff;
}

4.3 特性卡片区

<section id="features" class="section">
  <div class="container">
    <h2 class="section-title">为什么选择云舟</h2>
    <p class="section-sub">三个理由,让你放心把系统交给我们</p>
 
    <div class="feature-row">
      <div class="feature-card reveal">
        <i class="fa-solid fa-shield-halved feature-icon"></i>
        <h3>安全合规</h3>
        <p>等保三级认证,数据加密存储与传输。</p>
      </div>
      <div class="feature-card reveal">
        <i class="fa-solid fa-rocket feature-icon"></i>
        <h3>极速交付</h3>
        <p>标准化组件库加成熟方法论,最快两周上线。</p>
      </div>
      <div class="feature-card reveal">
        <i class="fa-solid fa-headset feature-icon"></i>
        <h3>全天候支持</h3>
        <p>7x24 小时响应,重大故障 30 分钟介入。</p>
      </div>
    </div>
  </div>
</section>
.feature-row {
  display: flex;
  gap: 2rem;
  flex-wrap: wrap;              /* 空间不足自动换行 */
}
.feature-card {
  flex: 1 1 260px;              /* 可伸可缩,基准宽 260px */
  text-align: center;
  padding: 2.5rem 1.5rem;
  border-radius: 12px;
  background: #f7f9fc;
  transition: transform .3s, box-shadow .3s;
}
.feature-card:hover {
  transform: translateY(-6px);
  box-shadow: 0 12px 24px rgba(0,0,0,.1);
}
.feature-icon {
  font-size: 2.5rem;
  color: #1a5fb4;
  margin-bottom: 1rem;
}

flex: 1 1 260px 是响应式卡片的惯用写法:不需要媒体查询,容器放得下就并排、放不下就换行,配合基准宽度天然自适应。

5. 服务展示(Grid)与关于我们

<section id="services" class="section section-alt">
  <div class="container">
    <h2 class="section-title">服务体系</h2>
    <div class="service-grid">
      <article class="service-item reveal">
        <i class="fa-solid fa-code"></i>
        <h3>软件定制开发</h3>
        <p>Web / 小程序 / App 全端覆盖</p>
      </article>
      <article class="service-item reveal">
        <i class="fa-solid fa-database"></i>
        <h3>数据中台建设</h3>
        <p>数据汇聚、治理与资产化</p>
      </article>
      <article class="service-item reveal">
        <i class="fa-solid fa-cloud"></i>
        <h3>云原生改造</h3>
        <p>容器化与微服务架构落地</p>
      </article>
      <article class="service-item reveal">
        <i class="fa-solid fa-chart-line"></i>
        <h3>BI 数据看板</h3>
        <p>经营数据一屏尽览</p>
      </article>
      <article class="service-item reveal">
        <i class="fa-solid fa-screwdriver-wrench"></i>
        <h3>系统运维托管</h3>
        <p>监控告警与应急响应</p>
      </article>
      <article class="service-item reveal">
        <i class="fa-solid fa-graduation-cap"></i>
        <h3>技术团队培训</h3>
        <p>体系化课程与实战陪跑</p>
      </article>
    </div>
  </div>
</section>
.service-grid {
  display: grid;
  /* 自动填充:每个格子最小 240px,能塞几列塞几列 */
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 1.5rem;
}
.service-item {
  padding: 2rem 1.5rem;
  border: 1px solid #e5eaf1;
  border-radius: 12px;
  transition: border-color .3s, transform .3s;
}
.service-item:hover {
  border-color: #1a5fb4;
  transform: translateY(-4px);
}
.service-item i {
  font-size: 1.75rem;
  color: #1a5fb4;
}

对比第 4 节的 Flex 卡片:Flex 适合”一行内容流式排布”,Grid 适合”规整的多行多列矩阵”,两者都靠 auto-fill/minmaxflex-basis 实现无断点响应式。更多网格细节见 Grid 布局

6. 联系表单与验证

<section id="contact" class="section">
  <div class="container contact-wrap">
    <form id="contactForm" class="contact-form" novalidate>
      <div class="form-field">
        <label for="name">姓名</label>
        <input type="text" id="name" name="name" required minlength="2"
               placeholder="您的称呼">
        <p class="error-msg" data-error-for="name"></p>
      </div>
      <div class="form-field">
        <label for="phone">手机号</label>
        <input type="tel" id="phone" name="phone" required
               pattern="^1[3-9]\d{9}$" placeholder="方便联系的手机号">
        <p class="error-msg" data-error-for="phone"></p>
      </div>
      <div class="form-field">
        <label for="message">需求描述</label>
        <textarea id="message" name="message" rows="4" required
                  placeholder="简单描述您的需求"></textarea>
        <p class="error-msg" data-error-for="message"></p>
      </div>
      <button type="submit" class="btn btn-primary btn-block">提交需求</button>
      <p id="formResult" class="form-result" aria-live="polite"></p>
    </form>
  </div>
</section>
// js/main.js —— 表单校验
const form = document.getElementById('contactForm');
const messages = {
  name: '请填写至少 2 个字的姓名',
  phone: '请输入正确的 11 位手机号',
  message: '请简单描述您的需求'
};
 
function showError(input, text) {
  const tip = form.querySelector(`[data-error-for="${input.id}"]`);
  tip.textContent = text;
  input.classList.add('invalid');
}
 
function clearError(input) {
  const tip = form.querySelector(`[data-error-for="${input.id}"]`);
  tip.textContent = '';
  input.classList.remove('invalid');
}
 
form.addEventListener('submit', e => {
  e.preventDefault();                 // 拦截默认提交,改用自定义逻辑
  let valid = true;
 
  for (const field of ['name', 'phone', 'message']) {
    const input = form.elements[field];
    if (!input.value.trim()) {
      showError(input, messages[field]); valid = false;
    } else if (!input.checkValidity()) {
      // checkValidity 按 HTML 属性(pattern/minlength 等)判定
      showError(input, messages[field]); valid = false;
    } else {
      clearError(input);
    }
  }
 
  if (valid) {
    // 官网场景通常没有后端,这里演示两种去向:
    // 1) 对接后端:fetch('/api/contact', { method: 'POST', body: ... })
    //    参见 [[前端开发/04-DOM与交互/AJAX/02-Fetch-API]]
    // 2) 纯静态兜底:mailto 或第三方表单服务
    document.getElementById('formResult').textContent =
      '提交成功,我们会在 1 个工作日内与您联系。';
    form.reset();
  }
});
.form-field { margin-bottom: 1.25rem; }
.form-field label { display: block; margin-bottom: .5rem; font-weight: 600; }
.form-field input,
.form-field textarea {
  width: 100%;
  padding: .75rem 1rem;
  border: 1px solid #d6dee9;
  border-radius: 8px;
  font-size: 1rem;
  outline: none;
  transition: border-color .2s;
}
.form-field input:focus,
.form-field textarea:focus { border-color: #1a5fb4; }
.invalid { border-color: #d93025 !important; }
.error-msg { color: #d93025; font-size: .85rem; min-height: 1.2em; margin-top: .35rem; }

设计决策说明:novalidate 关掉浏览器默认气泡提示是为了统一错误样式;aria-live="polite" 让提交结果对屏幕阅读器可见。更完整的表单专题见 HTML 表单与语义化

7. 滚动显现动画(IntersectionObserver)

.reveal {
  opacity: 0;
  transform: translateY(30px);
  transition: opacity .6s ease, transform .6s ease;
}
.reveal.visible {
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
}
const observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add('visible');
      observer.unobserve(entry.target);   // 只播一次,播完即停观察
    }
  });
}, { threshold: 0.15 });                   // 元素露出 15% 即触发
 
document.querySelectorAll('.reveal').forEach(el => observer.observe(el));

相比监听 scroll 事件手动算坐标,IntersectionObserver 由浏览器异步调度回调,不阻塞主线程,也不需要节流。想进阶观察 DOM 变化可继续读 MutationObserverprefers-reduced-motion 则照顾了晕动症用户与低性能设备。

8. 设计要点清单

零框架做页面,代码之外更重要的是设计纪律:

要点说明
留白区块上下 padding 至少 96px(.section { padding: 6rem 0 }),内容不要顶满边缘,容器统一 max-width: 1100px 居中
字体层级页面只用三级字号体系:标题 clamp(2rem,4vw,2.75rem)、副标题 1.25rem、正文 1rem,层级靠大小与字重表达而非颜色花样
一屏一主题每个 viewport 高度只传达一个核心信息:Hero 只讲定位、特性区只讲三个卖点,禁止在一屏内堆砌多组内容
色彩克制主色一个(#1a5fb4)、中性灰阶若干、语义红绿各一,全站不超过五个色值
统一圆角与阴影圆角统一 12px,阴影只用两档(hover 深、常态浅或无)
图片有 alt所有 img 必须带 alt,装饰图 alt 留空

9. 部署

纯静态产物,任意静态托管皆可:

# 方案一:GitHub Pages —— 推送仓库后在设置里开启 Pages
git init && git add . && git commit -m "feat: corporate site"
git remote add origin https://github.com/yourname/site.git && git push -u origin main
 
# 方案二:自有服务器 Nginx
scp -r index.html css assets user@server:/var/www/corp-site/
# Nginx server 块:
# root /var/www/corp-site;
# location / { try_files $uri $uri/ =404; }
 
# 方案三:本地快速预览
npx serve .

上线前自查清单:OG 图片链接必须是绝对 URL 且可公网访问;og:url 与最终域名一致;Favicon 已放置;手机真机走一遍汉堡菜单与表单。


小结

本章把 01-基础的 HTML/CSS/JS 核心知识压缩进了一个真实交付物:语义化结构、Flex 与 Grid 双布局、checkbox 纯 CSS 交互、IntersectionObserver 动效、表单校验。它证明了不用框架也能做出合格的生产页面。当你需要组件复用、状态管理和服务端对接时,就是进入 后台管理系统(Vue3 实战) 的时机。