“简历很多,但能动的,才值得停下来看。”
一、从“为什么要做作品集”开始
那天下午,楼下咖啡馆的爵士乐和浓郁的咖啡香,让我突然意识到:手里的简历虽然内容丰富,但却像一张生硬的纸片。要是能把我这些年折腾的项目、写过的代码、设计过的页面,一股脑儿地“搬”到一个精美、有动效、还支持手机和平板的地方,该有多好?想到这里,我合上笔记本,心里有了第一个明确的目标——自己动手做一个纯前端的个人作品集网站。至少,我得让它在打开的那一刻,就在用户脑海里留下“哇,这人还挺会做效果”的印象。
小记:一个人要做任何东西,首先要有一个清晰的“为什么”。对于我来说,“为什么要做个人作品集网站”不仅是找工作的需求,更是对自己过去经验的沉淀与包装。
二、把“做什么”画出来
简单的文字说明固然明了,但可视化思路更容易抓住要点。我画了下面这张流程图,标注了从首页到作品页,再到联系页的跳转逻辑,以及主题切换、懒加载等几个关键功能触发时机。
这张图对后续的逻辑理清帮助很大:哪些是静态渲染,哪些需要用 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-in、delay:给文字加了两个 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)`;
});
这种“多层视差”配合背景固定,能营造出更有深度的视觉效果:
- 背景图按一定比例滚动
- 前景文字再以更小的比例偏移
让用户下拉时,既能看到背景动,也能看到文字轻微移动,仿佛页面有了生命。
十、可扩展功能:博客、懒加载、性能优化……
在完成了基础三页、懒加载、主题切换和滚动视差后,如果你想让项目更丰富,可尝试以下几个方向:
- 博客功能
- 通过静态站点生成器(如 Eleventy、Hexo)或轻量 JS 实现一个简易博客列表与分页。
- 使用 Markdown 或 YAML 管理文章,模板插件化。
- 图片懒加载占位动效
- 利用 CSS 动画给加载中的图片添加渐变闪烁(Skeleton Loading)。
- 性能监测
- 引入 Lighthouse 报告,优化图片体积、减少 DOM 节点、启用 HTTP/2。
- 离线缓存
- 使用 Service Worker 实现离线访问,做一个小型 PWA。
- … 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 懒加载、主题切换、滚动视差……每一步,都离不开“先想清楚,再动手”的原则。更重要的是,用“最简单的前端三件套”完成所有交互,让我深刻体会到——有时候,不用引入过多依赖和框架,也能做出令人眼前一亮的效果。
如果你已经跟着我,把这篇博客中的代码一行一行敲下来,那么恭喜你:一个现代、响应式、动画丰富、还支持暗黑模式和懒加载的个人作品集网站,就这样诞生了。它不但能展现你的工作和项目,更能体现你对前端技术细节的打磨与追求。
期待每一位读到这里的前端朋友,也能用自己的创意和代码,把个人作品集“动”起来、“亮”起来,让更多人看到你的技术和品味。祝开发愉快,我们下次再见!