简单写一个个人作品集网站

516 阅读11分钟

“简历很多,但能动的,才值得停下来看。”

一、从“为什么要做作品集”开始

那天下午,楼下咖啡馆的爵士乐和浓郁的咖啡香,让我突然意识到:手里的简历虽然内容丰富,但却像一张生硬的纸片。要是能把我这些年折腾的项目、写过的代码、设计过的页面,一股脑儿地“搬”到一个精美、有动效、还支持手机和平板的地方,该有多好?想到这里,我合上笔记本,心里有了第一个明确的目标——自己动手做一个纯前端的个人作品集网站。至少,我得让它在打开的那一刻,就在用户脑海里留下“哇,这人还挺会做效果”的印象。

小记:一个人要做任何东西,首先要有一个清晰的“为什么”。对于我来说,“为什么要做个人作品集网站”不仅是找工作的需求,更是对自己过去经验的沉淀与包装。


二、把“做什么”画出来

简单的文字说明固然明了,但可视化思路更容易抓住要点。我画了下面这张流程图,标注了从首页到作品页,再到联系页的跳转逻辑,以及主题切换、懒加载等几个关键功能触发时机。

在这里插入图片描述

这张图对后续的逻辑理清帮助很大:哪些是静态渲染,哪些需要用 JavaScript 来交互;哪些靠 CSS 完成动画,哪些要用插件或原生 API(如 Intersection Observer)来实现。


三、搭建项目骨架——一切从静态文件说起

在决定不用任何打包工具(Vite、Webpack 等)之后,我的目录结构就显得格外“复古”——纯粹的静态文件夹,甚至连 package.json 都没有。它看起来很简单,但每个文件夹都是为了今后更清晰的维护和扩展而准备的。

my-portfolio/
├── index.html          # 首页
├── projects.html       # 作品页
├── contact.html        # 联系页
├── assets/
│   ├── css/
│   │   └── style.css   # 全站样式
│   ├── js/
│   │   └── main.js     # 全站脚本
│   └── images/         # 所有图片资源
└── README.md           # 项目说明
  • index.html、projects.html、contact.html:三大静态页面,负责各自的内容展示。
  • assets/css/style.css:所有 CSS 都在这里,后面会分区块用注释标明“Header 区样式”、“Hero 区样式”、“作品卡片样式”等。
  • assets/js/main.js:监听主题切换、懒加载、滚动视差等所有交互脚本都写在这里。

Tip:有时候“简单”反而最方便,特别是初学阶段,不用考虑打包配置,只要把 HTML、CSS、JS 写好,就能立刻在浏览器中查看效果。


四、打造“有温度”的首页

首页的核心,是一个 Hero 区块。它要求做到三个要点:全屏居中文字渐入动画背景视差滚动

4.1 HTML 结构

index.html<body> 中,我写下了这样一段结构——先放 Header,再是 Hero,最后可以留个 Footer:

<body data-theme="light">
  <header class="site-header">
    <nav class="site-nav">
      <ul>
        <li><a href="index.html">首页</a></li>
        <li><a href="projects.html">作品</a></li>
        <li><a href="contact.html">联系</a></li>
        <li><button id="toggle-theme">🌓</button></li>
      </ul>
    </nav>
  </header>

  <section class="hero">
    <div class="hero-content">
      <h1 class="fade-in">你好,我是<span class="highlight">小林</span></h1>
      <p class="fade-in delay">欢迎来到我的前端世界</p>
    </div>
  </section>

  <footer class="site-footer">
    <p>© 2025 小林 的个人作品集</p>
  </footer>
</body>
  • data-theme="light":我先在 <body> 上加了一个自定义属性,用来统一控制明暗主题。
  • fade-indelay:给文字加了两个 class,用来配合 CSS 动画。
  • <span class="highlight">:我想让名字「小林」很抢眼,所以给它单独一套样式。

4.2 CSS:响应式全屏 & 渐入效果

style.css 中,我先把页全局样式写好,再分区块注释。以下是 Hero 区核心部分:

/* =========== 全局重置 =========== */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  font-family: 'Segoe UI', Tahoma, sans-serif;
  line-height: 1.6;
  background-color: #fafafa;
  color: #333;
  transition: background-color 0.3s, color 0.3s;
}

/* =========== Hero 区 =========== */
.hero {
  position: relative;
  width: 100%;
  height: 100vh;             /* 全屏高度 */
  background: url('../images/hero-bg.jpg') center / cover fixed;  /* 视差:fixed 背景 */
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.hero-content {
  text-align: center;
  color: #fff;
  text-shadow: 0 2px 8px rgba(0,0,0,0.5);
}

.fade-in {
  opacity: 0;
  transform: translateY(20px);
  animation: fadeInUp 1s forwards;
}
.fade-in.delay {
  animation-delay: 0.5s;
}
@keyframes fadeInUp {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.highlight {
  color: #ffd700;
}

技巧分享

  • background-attachment: fixed(简写到背景属性里了)轻松实现背景视差效果。
  • transition 加到 body 上,让主题切换时过渡更自然。
  • 文本阴影(text-shadow)增强可读性,也更有层次感。

五、“出彩而不花哨”的响应式导航

在大屏上,导航是一行简单的文字链接;在小屏上,我要它收缩成一个汉堡菜单,但为了保持篇幅精炼,这里只演示使用 Flex+Media Query 在窄屏下让链接垂直排列。

/* =========== 导航区 =========== */
.site-nav ul {
  display: flex;
  justify-content: flex-end;
  list-style: none;
  padding: 1rem 2rem;
}
.site-nav li + li {
  margin-left: 1.5rem;
}
.site-nav a {
  text-decoration: none;
  color: inherit;
  font-weight: 500;
}
#toggle-theme {
  background: none;
  border: none;
  font-size: 1.2rem;
  cursor: pointer;
}

/* 窄屏下导航垂直排列 */
@media (max-width: 768px) {
  .site-nav ul {
    flex-direction: column;
    align-items: center;
  }
  .site-nav li + li {
    margin-left: 0;
    margin-top: 1rem;
  }
}

至于“汉堡菜单”+“侧边滑出”那套逻辑,未来版本我可以用原生 JS 或者 CSS Checkbox Hack 实现,也能借助小巧的组件库。但作为一个星级难度★☆☆ 的练手项目,Flex + 垂直排列就够了。


六、作品页:卡片式布局 + 图片懒加载

当用户从首页点击“作品”进入 projects.html 时,他会看到一排排卡片,每张卡片上有封面图、项目标题和简要描述。为了不让图片一次性全部加载,我引入了原生的 Intersection Observer 实现懒加载。

6.1 HTML 卡片模板

<main class="projects-container">
  <section class="project-card" tabindex="0">
    <div class="img-wrap">
      <img data-src="assets/images/project1.png" alt="项目一封面" class="lazy">
    </div>
    <div class="card-content">
      <h3>项目一:响应式企业官网</h3>
      <p>基于 Flex 和 Grid,实现移动端、平板和桌面端一致的布局体验,包含轮播图和表单验证。</p>
    </div>
  </section>
  <!-- 重复多个 project-card -->
</main>
  • tabindex="0":让键盘用户也能聚焦到卡片上,提升可访问性。
  • .img-wrap:给图片外层套一个容器,方便在加载前设置固定高度或占位样式,避免布局抖动。

6.2 CSS 卡片样式

/* =========== 作品卡片 =========== */
.projects-container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 2rem;
  padding: 2rem;
}

.project-card {
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.1);
  overflow: hidden;
  transition: transform 0.3s, box-shadow 0.3s;
}
.project-card:focus,
.project-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 4px 16px rgba(0,0,0,0.2);
}
.img-wrap {
  width: 100%;
  height: 180px;
  background: #eee;
}
.img-wrap img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.card-content {
  padding: 1rem;
}

思考

  • 用 Grid 的 auto-fit + minmax,让列数自动适配屏幕宽度,非常优雅。
  • 卡片的悬停升起效果(translateY),给人一种“可触摸”的感觉。
  • 外层 .img-wrap 背景色与占位高度,让图片懒加载期间也不会看到乱跳。

6.3 JavaScript:原生懒加载

// assets/js/main.js
document.addEventListener('DOMContentLoaded', () => {
  const lazyImages = document.querySelectorAll('img.lazy');
  if ('IntersectionObserver' in window) {
    const observer = new IntersectionObserver(entries => {
      entries.forEach(e => {
        if (e.isIntersecting) {
          const img = e.target;
          img.src = img.dataset.src;
          img.addEventListener('load', () => {
            img.classList.remove('lazy');
          });
          observer.unobserve(img);
        }
      });
    });
    lazyImages.forEach(img => observer.observe(img));
  } else {
    // 回退:没有 IntersectionObserver,就直接加载所有图片
    lazyImages.forEach(img => {
      img.src = img.dataset.src;
      img.classList.remove('lazy');
    });
  }
});

小贴士

  • 检测 IntersectionObserver 可用性,兼容老浏览器。
  • 图片加载完成后再移除 lazy 类,好做一些 CSS 动画(比如淡入)。

七、暗黑/亮色模式切换

多数前端框架在明暗主题切换上,都采用 CSS 变量或 data-theme 属性。这里我用后者最简单:在 <body> 标签上加 data-theme="light"data-theme="dark",CSS 根据其值渲染不同样式。

7.1 主题切换按钮逻辑

// assets/js/main.js
const toggleBtn = document.getElementById('toggle-theme');
toggleBtn.addEventListener('click', () => {
  const body = document.body;
  const current = body.getAttribute('data-theme');
  const next = current === 'dark' ? 'light' : 'dark';
  body.setAttribute('data-theme', next);
  localStorage.setItem('theme', next);
});

// 页面加载时恢复上次模式
document.addEventListener('DOMContentLoaded', () => {
  const saved = localStorage.getItem('theme');
  if (saved) {
    document.body.setAttribute('data-theme', saved);
  }
});

这里多加了一步:把用户的主题偏好存到 localStorage,下一次打开网站时就能记住。

7.2 CSS 主题变量

style.css 最顶部,我这样写:

:root[data-theme="light"] {
  --bg-color: #fafafa;
  --text-color: #333;
  --card-bg: #fff;
  --highlight-color: #ffd700;
  --link-color: #1a73e8;
}

:root[data-theme="dark"] {
  --bg-color: #121212;
  --text-color: #ddd;
  --card-bg: #1f1f1f;
  --highlight-color: #ffb900;
  --link-color: #8ab4f8;
}

body {
  background-color: var(--bg-color);
  color: var(--text-color);
}
a {
  color: var(--link-color);
}
.project-card {
  background: var(--card-bg);
}
.highlight {
  color: var(--highlight-color);
}

只要把所有可变色调用成 CSS 变量,就能一键切换全站配色。


八、联系页:简洁而不失精致

联系页(contact.html)的核心是一张渐变背景下居中的表单,让用户填写姓名、邮箱、留言内容,并点击发送。当然,真实环境下还要接后端 API;但本练手项目,我们先做前端验证和交交互效果即可。

8.1 HTML 结构

<section class="contact-hero">
  <h2>与我取得联系</h2>
  <form id="contact-form">
    <label>
      <span>你的名字</span>
      <input type="text" name="name" required>
    </label>
    <label>
      <span>你的邮箱</span>
      <input type="email" name="email" required>
    </label>
    <label>
      <span>你的留言</span>
      <textarea name="message" rows="5" required></textarea>
    </label>
    <button type="submit">发送</button>
    <p class="status"></p>
  </form>
</section>

8.2 CSS 样式

/* =========== 联系页 =========== */
.contact-hero {
  min-height: 80vh;
  padding: 4rem 2rem;
  background: linear-gradient(135deg, #6a11cb, #2575fc);
  color: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}
#contact-form {
  background: rgba(255,255,255,0.1);
  padding: 2rem;
  border-radius: 8px;
  max-width: 400px;
  width: 100%;
}
#contact-form label {
  display: block;
  margin-bottom: 1rem;
  text-align: left;
}
#contact-form input,
#contact-form textarea {
  width: 100%;
  padding: 0.5rem;
  border: none;
  border-radius: 4px;
  margin-top: 0.5rem;
}
#contact-form button {
  margin-top: 1rem;
  padding: 0.6rem 1.2rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  background: var(--highlight-color);
  color: #000;
  font-weight: bold;
  transition: transform 0.2s;
}
#contact-form button:hover {
  transform: scale(1.05);
}
.status {
  margin-top: 1rem;
  font-size: 0.9rem;
}

8.3 前端验证与模拟提交

// assets/js/main.js
document.getElementById('contact-form').addEventListener('submit', async e => {
  e.preventDefault();
  const statusEl = document.querySelector('.status');
  statusEl.textContent = '正在发送…';
  // 模拟网络延迟
  await new Promise(r => setTimeout(r, 1000));
  statusEl.textContent = '发送成功!感谢你的留言 😊';
  e.target.reset();
});

因为没有后端,我们用 setTimeout 模拟网络延迟,体验更真实一些。这样一来,整个联系流程看起来就很完整——从填写,到点击,再到收到“发送成功”提示。


九、小细节让人眼前一亮——滚动视差 & 微动效

回到首页,除了 background-attachment: fixed 的背景视差,我还想让部分元素在滚动时,有一点“轻微漂浮”的感觉。于是我继续在 main.js 中写了:

// 视差滚动微动效
window.addEventListener('scroll', () => {
  const scrolled = window.scrollY;
  const hero = document.querySelector('.hero');
  hero.style.backgroundPositionY = `${scrolled * 0.5}px`;
  // 可以再给 hero-content 加一点偏移
  document.querySelector('.hero-content')
    .style.transform = `translateY(${scrolled * 0.1}px)`;
});

这种“多层视差”配合背景固定,能营造出更有深度的视觉效果:

  1. 背景图按一定比例滚动
  2. 前景文字再以更小的比例偏移

让用户下拉时,既能看到背景动,也能看到文字轻微移动,仿佛页面有了生命。


十、可扩展功能:博客、懒加载、性能优化……

在完成了基础三页、懒加载、主题切换和滚动视差后,如果你想让项目更丰富,可尝试以下几个方向:

  1. 博客功能
    • 通过静态站点生成器(如 Eleventy、Hexo)或轻量 JS 实现一个简易博客列表与分页。
    • 使用 Markdown 或 YAML 管理文章,模板插件化。
  2. 图片懒加载占位动效
    • 利用 CSS 动画给加载中的图片添加渐变闪烁(Skeleton Loading)。
  3. 性能监测
    • 引入 Lighthouse 报告,优化图片体积、减少 DOM 节点、启用 HTTP/2。
  4. 离线缓存
    • 使用 Service Worker 实现离线访问,做一个小型 PWA。
  5. … more ….

这些扩展都不复杂,但能让你的作品集从“好看”提升到“好用、更专业”。


十一、部署到 GitHub Pages:人人都能驾驭

当一切准备就绪,把整个文件夹推到 GitHub,就可以在“设置 → Pages”里选择主分支 main 下的根目录作为发布源。片刻之后,你就能在 https://username.github.io/my-portfolio/ 看到你的作品集。

git init
git add .
git commit -m "chore: 首次提交个人作品集"
git branch -M main
git remote add origin https://github.com/yourname/my-portfolio.git
git push -u origin main

温馨提示

  • 仓库名建议和项目名一致,这样 Pages 地址更 neat。
  • 如果想用自定义域名,可在仓库根目录放一个 CNAME 文件,里面写上你的域名(不用 http://)。

十二、全篇回顾:一场关于“设计”与“实现”的对话

从最初的“为什么要做作品集”,到用图像把整体思路可视化,再到三大页面的 HTML+CSS 核心布局、细腻的 CSS 动画、原生 Intersection Observer 懒加载、主题切换、滚动视差……每一步,都离不开“先想清楚,再动手”的原则。更重要的是,用“最简单的前端三件套”完成所有交互,让我深刻体会到——有时候,不用引入过多依赖和框架,也能做出令人眼前一亮的效果。

如果你已经跟着我,把这篇博客中的代码一行一行敲下来,那么恭喜你:一个现代、响应式、动画丰富、还支持暗黑模式和懒加载的个人作品集网站,就这样诞生了。它不但能展现你的工作和项目,更能体现你对前端技术细节的打磨与追求。

期待每一位读到这里的前端朋友,也能用自己的创意和代码,把个人作品集“动”起来、“亮”起来,让更多人看到你的技术和品味。祝开发愉快,我们下次再见!

在这里插入图片描述