综合作业:GitHub 个人主页
从零打造你的数字名片——综合运用 DOM、异步、OOP、Canvas 等核心技能
🎯 项目目标
完成本项目,你将能够:
- 综合运用 DOM 操作、异步编程、OOP 等 JS 核心技能构建完整应用
- 使用 GitHub API 获取真实数据并动态渲染页面
- 掌握 Canvas 动画与粒子系统的基本实现
- 独立完成从开发到 GitHub Pages 部署的完整工作流
这个作业做什么
在 GitHub Pages 上亲手打造一个能代表你技术实力、创意和个性的终极个人主页
学了一肚子 JS 知识,但还没有一个能"秀"出来的作品?这个作业就是你的机会。你将从零开始搭建一个真实可部署的个人主页——它不只是静态页面,而是带动态交互、异步数据、甚至 Canvas 粒子特效的完整作品。
整个作业分三个阶段,难度逐级递进:
Part 1 — 核心功能(必修)
先搭骨架:仓库 → 日夜切换 → 打字机动画 → GitHub API 拉数据 → OOP 卡片 → 筛选。做完这部分,你就有了一个能跑的网站。
Part 2 — 个性化进阶(选修 ≥2)
让网站不再千篇一律:数据可视化、叙事时间轴、互动地图、Markdown 博客。挑你感兴趣的方向深入。
Part 3 — 创意彩蛋(选修 ≥1)
给访客一个"哇"的瞬间:Canvas 粒子背景、Konami 秘籍、动态标题。小而精,但要有质感。
先搭地基
创建仓库、配置 Pages、规划页面结构
万丈高楼平地起。在写任何特效之前,先把仓库建好、文件结构规划清楚。这页做完,你就有了一个可以在浏览器里打开的空白页面。
在 GitHub 上新建一个名为 你的用户名.github.io 的仓库,建立三个文件:
你的用户名.github.io/
├── index.html ← 页面结构
├── style.css ← 样式与动画
└── script.js ← 交互逻辑

图:页面整体布局线框图
【AI 生图提示词】请生成一张中文网页线框图,风格简洁、配色柔和、适合教学: - 画面主体:一个个人主页的纵向布局线框图,从上到下依次为:导航栏(含 logo、导航链接、日夜切换按钮)、#hero 英雄区(大标题 + 打字机文字)、#about 关于我区域(技能条 + 雷达图占位)、#projects 项目展示区(卡片网格 + 顶部筛选按钮栏)、#hobbies 兴趣爱好区、页脚 - 各区域用浅色虚线框分隔,每个区域内部标注对应的 id 名称(如 #hero、#about、#projects) - 导航栏固定在顶部,用深色背景突出 - 关键标注:导航栏右侧标注"☀️/🌙 切换按钮"、#hero 区标注"打字机动画"、#projects 区标注"动态卡片 + 筛选" - 配色建议:浅灰背景,各区域用不同浅色填充(淡蓝、淡绿、淡黄交替),深色文字 - 画面比例:9:16(竖向,模拟移动端)或 4:3(横向,模拟桌面端),任选一种 - 避免:过度装饰、3D 效果、杂乱的颜色、具体文字内容(用占位线代替)
在 index.html 中规划好这些区域——后续每个任务都会往对应区域里填东西:
#hero— 首页英雄区(打字机动画会放在这里)#about— 关于我(选修 A 的技能条/雷达图)#projects— 我的项目(Part 1 的核心:动态卡片 + 筛选)#hobbies— 兴趣爱好<footer>— 页脚
用户名.github.io。这样 GitHub Pages 会自动部署到 https://用户名.github.io。其他命名需要在 Settings → Pages 里手动配置。
日夜模式切换
☀️ / 🌙 点击按钮,页面在浅色与深色之间平滑过渡
地基搭好了,现在给网站注入第一个"活"的效果。想象一下:晚上十一点,你打开自己的网站,白底黑字刺得眼睛疼。如果有个按钮让整站变成深色主题,是不是舒服多了?
要实现的效果:导航栏右上角放一个太阳 ☀️ 图标。点击后,背景平滑过渡到 #121212,文字变浅色,图标变成月亮 🌙。再点一次,切回浅色。整个过程不是瞬间切换,而是 0.3 秒的渐变。

图:日夜模式切换前后对比
【AI 生图提示词】请生成一张中文 UI 对比图,左右并排展示同一个网页的两种主题状态: - 画面主体:左右两栏,左栏标题"浅色模式 ☀️",右栏标题"深色模式 🌙" - 左栏:白色背景、深灰文字、橙色按钮、顶部导航栏白色底 - 右栏:#121212 深色背景、浅灰文字、橙色按钮(颜色不变,作为强调色)、顶部导航栏深色底 - 两栏之间用一个双向箭头连接,箭头上方标注"点击切换" - 关键标注:在右上角图标处标注"☀️ ↔ 🌙",在背景处标注"transition: 0.3s" - 配色建议:整体浅灰背景,两栏内容分别用实际主题色展示 - 画面比例:16:9 - 避免:过度装饰、3D 效果、复杂内容(用简化的页面线框即可)
思路拆解:
- 用 CSS 变量定义两套颜色,
body.dark-theme覆盖变量值 - 按钮点击时
classList.toggle('dark-theme')切换类名 - CSS
transition让颜色变化是渐变的,不是瞬间的 - 图标随主题一起切换(太阳 ↔ 月亮)
这个任务覆盖的 DOM 基础——选元素、监听事件、切换类名——后面的打字机动画还会用到。
日夜模式:代码
CSS 变量 + classList.toggle 的经典方案
核心思路:CSS 变量 + body 类名切换。JS 只需要一行 classList.toggle,剩下的全是 CSS 的事。
/* style.css — 用 CSS 变量定义主题 */
:root {
--bg-color: #ffffff;
--text-color: #333333;
--transition-speed: 0.3s;
}
body.dark-theme {
--bg-color: #121212;
--text-color: #e0e0e0;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color var(--transition-speed) ease,
color var(--transition-speed) ease;
}
// script.js — 切换逻辑
const themeBtn = document.querySelector('#theme-toggle');
const icon = themeBtn.querySelector('span');
themeBtn.addEventListener('click', () => {
document.body.classList.toggle('dark-theme');
icon.textContent = document.body.classList.contains('dark-theme')
? '🌙' : '☀️';
});
color: #333)而不是 var(--xxx)。只有用 CSS 变量声明的属性才会随主题变化。
打字机标题
逐字打出 → 停顿 → 逐字删除 → 循环切换
日夜模式搞定了——你已经掌握了选元素、监听事件、改 DOM 内容的基本功。接下来用同样的 DOM 基础,做一个更复杂的效果。
你一定见过这样的个人网站:页面加载后,标题一个字一个字地蹦出来,打完一句话停顿一下,然后一个字一个字地消失,换下一句。这种效果叫打字机动画,让访客觉得"这个网站是活的"。
要实现的效果:Hero 区域主标题逐字打出"你好,我是[你的名字]"。停顿 1.5 秒后逐字删除,再打出"一名前端开发爱好者"。无限循环。
思路拆解:
- 准备一个字符串数组,存储多句预设文案
- 用
setTimeout递归控制节奏——不是setInterval,因为打出和删除的速度不同 - 打完 → 停顿 → 逐字删除 → 切换到下一句
slice(0, i)截取前 i 个字符,更新textContent
打字机:代码
setTimeout 递归 + 字符串截取
const titles = [
"你好,我是张三",
"一名前端开发爱好者",
"热爱开源与创造"
];
const el = document.querySelector('#hero-title');
let titleIdx = 0, charIdx = 0, isDeleting = false;
function typeWriter() {
const current = titles[titleIdx];
if (!isDeleting) {
// 逐字打出
el.textContent = current.slice(0, charIdx + 1);
charIdx++;
if (charIdx === current.length) {
isDeleting = true; // 打完了,准备删除
setTimeout(typeWriter, 1500); // 停顿 1.5 秒
return;
}
setTimeout(typeWriter, 120); // 打字速度:每字 120ms
} else {
// 逐字删除
el.textContent = current.slice(0, charIdx - 1);
charIdx--;
if (charIdx === 0) {
isDeleting = false; // 删完了,换下一句
titleIdx = (titleIdx + 1) % titles.length;
setTimeout(typeWriter, 400); // 短暂停顿后开始下一句
return;
}
setTimeout(typeWriter, 60); // 删除比打出快一倍
}
}
typeWriter(); // 启动!
几个值得注意的细节:
- 打字速度
120ms,删除速度60ms——删除比打出快,这是打字机效果的"手感"秘诀 - 用
isDeleting标志位区分"打"和"删"两个阶段 (titleIdx + 1) % titles.length让数组循环
从 API 到卡片
用 fetch 拉取 GitHub 仓库数据,用 class 动态渲染项目卡片
两个动画效果搞定了,网站"活"起来了。但有个问题:项目列表还是你手动写在 HTML 里的。每次在 GitHub 上建新仓库,还得手动去改代码。能不能让项目列表自动和 GitHub 同步?
这就是模块二要做的事——你将第一次调用真实的 Web API,把拿到的数据用 OOP 封装成组件,再渲染到页面上。整个过程分两步:
- 异步请求:用
async/await+try...catch获取 GitHub API 数据 - 组件化渲染:创建
class ProjectCard,每个仓库一个实例,调用.render()生成卡片

图:从 API 到卡片的数据流
【AI 生图提示词】请生成一张中文数据流示意图,风格简洁、配色柔和、适合教学: - 画面主体:从左到右的数据流管道,包含以下节点: 1. "GitHub API"(云形图标,内含 api.github.com) 2. → 箭头标注 "fetch + async/await" → 3. "JSON 数据"(文档图标,显示示例字段:name, description, language, stargazers_count) 4. → 箭头标注 "new ProjectCard(repo)" → 5. "ProjectCard 实例"(卡片图标,显示:构造函数接收 repo,render() 返回 DOM 节点) 6. → 箭头标注 "appendChild" → 7. "页面上的卡片"(网页截图示意,3张项目卡片并排展示) - 关键标注:在 "JSON 数据"节点旁标注 "try...catch 包裹",在 "ProjectCard"节点旁标注 "OOP 封装" - 配色建议:浅蓝背景,节点用白色圆角矩形,箭头用橙色,标注用深灰文字 - 画面比例:16:9 - 避免:过度装饰、3D 效果、代码细节(只画流程不写代码)
API 地址:
GET https://api.github.com/users/{username}/repos
.then() 链式调用——这是培养现代异步编程习惯。
项目卡片类
用 class 封装"项目卡片"的数据与渲染逻辑
先设计好数据结构,再写请求。这样拿到数据后直接传给构造函数就行,不用到处拼字符串。
class ProjectCard {
constructor(repo) {
this.name = repo.name;
this.description = repo.description || '暂无描述';
this.url = repo.html_url;
this.language = repo.language || '未知';
this.stars = repo.stargazers_count;
}
render() {
const card = document.createElement('div');
card.className = 'project-card';
card.setAttribute('data-language', this.language);
card.innerHTML = `
<h3>
<a href="${this.url}" target="_blank">${this.name}</a>
</h3>
<p>${this.description}</p>
<span class="lang-tag">${this.language}</span>
<span class="stars">⭐ ${this.stars}</span>
`;
return card;
}
}
每个仓库对应一个 new ProjectCard(repo) 实例,调用 .render() 就拿到一个完整的 DOM 节点。数据存在属性里,渲染逻辑封装在方法里——这就是 OOP 的核心思想。后面的筛选功能也会用到 data-language 属性。
获取仓库数据
用 fetch + async/await 拉取 GitHub API
ProjectCard 类设计好了,现在去拿数据。注意 data-language 属性——下一步的筛选系统会用到它。
async function loadProjects() {
const container = document.querySelector('#projects-list');
const username = '你的GitHub用户名';
try {
const response = await fetch(
`https://api.github.com/users/${username}/repos`
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const repos = await response.json();
repos.forEach(repo => {
const card = new ProjectCard(repo);
container.appendChild(card.render());
});
} catch (error) {
container.innerHTML = '<p>加载项目失败,请稍后再试 🥲</p>';
console.error('获取仓库数据失败:', error);
}
}
loadProjects();
const response = await fetch(url, {
headers: { 'Authorization': 'token 你的Token' }
});
到 GitHub Settings → Developer settings → Personal access tokens 生成。Token 不要提交到公开仓库!
项目筛选系统
自动扫描语言标签,动态生成筛选按钮,平滑过滤卡片
卡片能自动生成了,但如果你的仓库有 20 个,访客可能只想看 JavaScript 的项目。你需要一个筛选系统——而且按钮本身也应该是动态生成的:仓库里有哪些语言,就自动出现哪些按钮。
还记得上一步给每个卡片设的 data-language 属性吗?现在它派上用场了。
要实现的效果:
- 从仓库数据中提取唯一的编程语言(用
Set去重) - 为每种语言动态创建一个筛选按钮,再加一个"全部"按钮
- 点击按钮时,非匹配卡片淡出隐藏,匹配卡片淡入显示
- 当前激活的按钮有视觉高亮
/* 提取唯一语言 */
const languages = [...new Set(repos.map(r => r.language).filter(Boolean))];
// 结果示例:["JavaScript", "HTML", "Python", "CSS"]
筛选:代码
动态生成按钮 + 事件委托 + CSS 过渡动画
function createFilterButtons(repos) {
const languages = [...new Set(repos.map(r => r.language).filter(Boolean))];
const bar = document.querySelector('#filter-bar');
// "全部"按钮
bar.innerHTML = '<button class="filter-btn active" data-lang="all">全部</button>';
// 每种语言一个按钮
languages.forEach(lang => {
bar.innerHTML += `<button class="filter-btn" data-lang="${lang}">${lang}</button>`;
});
// 事件委托:在 bar 上统一监听,不用给每个按钮绑定
bar.addEventListener('click', (e) => {
if (!e.target.classList.contains('filter-btn')) return;
bar.querySelectorAll('.filter-btn')
.forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
const lang = e.target.dataset.lang;
document.querySelectorAll('.project-card').forEach(card => {
const match = lang === 'all' || card.dataset.language === lang;
card.classList.toggle('hidden', !match);
});
});
}
/* CSS 配合过渡动画 */
.project-card {
transition: opacity 0.4s ease, transform 0.4s ease;
}
.project-card.hidden {
opacity: 0;
transform: scale(0.95);
pointer-events: none;
position: absolute;
}
opacity: 0 的元素依然占据空间。加上 position: absolute 让它脱离文档流。要动画用 absolute,要简洁用 display: none(但没过渡动画)。
个性化进阶
至少选修 2 项。三个方向,挑你感兴趣的开始
必修部分搞定了——你的网站现在能自动展示项目、有日夜切换、有打字机动画。接下来让它从"能用"变成"想多看两眼"。
三个方向,每个都需要你接触新的 API 或第三方库:
选修 A — 数据可视化
用 IntersectionObserver 做滚动触发的技能条动画,用 Chart.js 画技能雷达图。让"关于我"不再是枯燥的文字。
选修 B — 叙事与交互
用 CSS 伪元素画人生时间轴,用 Leaflet.js 标注旅行足迹地图。考验复杂 DOM 结构和第三方库配置。
选修 C — 内容创作
用 fetch 加载本地 Markdown 文件,用 Marked.js 解析成 HTML。无后端也能有一个博客。
动态技能条
滚动到可视区域时,技能条从 0% 平滑生长到目标百分比
大多数人写"关于我",就是一段自我介绍文字,一眼扫过就忘了。如果把技能用条形图展示出来,而且是在用户滚动到那个区域时才"长出来",效果就好得多。
要实现的效果:
- 初始状态所有技能条宽度为 0%
- 当技能条区域首次滚动进入视口时,动画生长到预设百分比
- 滚动离开再回来,动画不重复

图:滚动触发技能条动画效果
【AI 生图提示词】请生成一张中文 UI 效果示意图: - 画面主体:展示技能条动画的两个阶段——上方"初始状态",下方"动画完成后" - 上方"初始状态":3-4 条技能条,标签可见(如"JavaScript 85%"、"CSS 70%"、"Python 60%"),但进度条全部为 0%(空白) - 下方"动画完成后":同样的 3-4 条技能条,进度条已填充到对应百分比,颜色为渐变橙色 - 两个状态之间用一个向下箭头连接,标注"IntersectionObserver 触发" - 右侧画一个浏览器窗口边框的简笔画,底部有一条虚线标注"视口 50% 位置(threshold: 0.5)" - 配色建议:白色背景,技能条底色浅灰,填充色橙色渐变 - 画面比例:4:3 - 避免:过度装饰、3D 效果
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-in');
observer.unobserve(entry.target); // 只触发一次!
}
});
}, { threshold: 0.5 });
document.querySelectorAll('.skill-bar')
.forEach(bar => observer.observe(bar));
threshold: 0.5 = 元素有 50% 进入视口才触发。unobserve 确保只触发一次,不会来回滚动时反复播放。
练习知识点:IntersectionObserver、threshold、unobserve、CSS @keyframes
技能雷达图
用 Chart.js 绘制多维度技能雷达图
技能条只能一个维度一个维度地看。雷达图能同时展示多个维度,一眼看出你的"能力六边形"。Chart.js 是最流行的前端图表库之一,引入一个 CDN 脚本就能用。

图:技能雷达图效果预览
【AI 生图提示词】请生成一张中文雷达图效果示意图: - 画面主体:一个六边形雷达图,六个顶点分别标注"编程"、"设计"、"沟通"、"算法"、"团队协作"、"英语" - 数据区域为一个半透明的橙色六边形,各维度数值分别约为 85、70、80、75、90、65 - 图表标题:"我的技能"(位于雷达图上方) - 右下角放一个小标签"Chart.js 渲染" - 配色建议:白色背景,网格线浅灰色,数据区域橙色半透明填充(rgba(208, 101, 25, 0.2)),边线橙色 - 画面比例:1:1(正方形) - 避免:过度装饰、3D 效果、图例过多
<!-- 引入 Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<canvas id="skillRadar"></canvas>
new Chart(document.getElementById('skillRadar'), {
type: 'radar',
data: {
labels: ['编程', '设计', '沟通', '算法', '团队协作', '英语'],
datasets: [{
label: '我的技能',
data: [85, 70, 80, 75, 90, 65],
backgroundColor: 'rgba(208, 101, 25, 0.2)',
borderColor: 'rgba(208, 101, 25, 0.8)',
}]
},
options: {
scales: {
r: { beginAtZero: true, max: 100 }
}
}
});
把 labels 和 data 换成你自己的维度和分值就行。配色用了和教程主题一致的橙色。
互动式时间轴
垂直时间轴 + 交错节点 + 悬停展开详情
文字介绍看多了会累。如果你用一条时间轴把人生关键节点串起来,访客会更有代入感——他们不是在读简历,而是在翻你的故事。
要实现的效果:
- 页面中央一条垂直贯穿线(用 CSS 伪元素
::before绘制) - 轴线左右交错排列圆点节点(
nth-child(odd/even)) - 鼠标悬停:节点放大或发光 + 显示简短描述卡片
- 点击节点:展开详细内容区域,再次点击收起

图:垂直时间轴布局效果
【AI 生图提示词】请生成一张中文时间轴 UI 布局示意图: - 画面主体:一条垂直时间轴,从上到下贯穿画面中央 - 轴线用 2px 橙色实线绘制 - 轴线上有 4 个圆点节点,奇数节点在轴线左侧,偶数节点在轴线右侧(左右交错排列) - 每个节点旁有一张小卡片,内容示例: - 左侧第 1 个节点:"2019 · 开始学编程"(卡片在节点右侧,即轴线右侧) - 右侧第 2 个节点:"2020 · 第一个开源项目"(卡片在节点左侧,即轴线左侧) - 左侧第 3 个节点:"2022 · 实习经历"(卡片在节点右侧) - 右侧第 4 个节点:"2024 · 全栈工程师"(卡片在节点左侧) - 其中一个节点放大高亮显示,旁边标注"hover 效果:节点发光 + 卡片展开" - 配色建议:白色背景,轴线橙色,节点实心橙色圆点,卡片浅灰底圆角矩形 - 画面比例:9:16(竖向) - 避免:过度装饰、3D 效果
/* 时间轴中线 */
.timeline::before {
content: '';
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: 2px;
background: var(--primary-color);
}
练习知识点:CSS 伪元素(::before、::after)、nth-child 奇偶交错、事件委托
互动足迹地图
用 Leaflet.js 在地图上标记你去过的城市
如果你喜欢旅行,这个功能会很加分。Leaflet 是一个轻量级的地图库,配合 OpenStreetMap 的免费瓦片,不需要任何 API Key 就能跑起来。和时间轴一样,它让访客通过"探索"而非"阅读"来了解你。
<!-- 引入 Leaflet.js -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<div id="map" style="height: 400px;"></div>
const map = L.map('map').setView([35.86, 104.19], 4);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png')
.addTo(map);
const cities = [
{ name: '北京', lat: 39.90, lng: 116.40, story: '我在这里读的大学' },
{ name: '上海', lat: 31.23, lng: 121.47, story: '第一次实习的城市' },
];
cities.forEach(city => {
L.marker([city.lat, city.lng])
.addTo(map)
.bindPopup(`<b>${city.name}</b><br>${city.story}`);
});
坐标在 Google Maps 上右键就能获取。把城市和故事换成你自己的。
轻量级博客
用 Marked.js 解析 Markdown,实现无后端博客系统
你有一堆 Markdown 格式的笔记或文章,但每次展示给别人都要单独开仓库或用第三方平台。能不能直接在自己的主页上做一个博客?用 fetch 加载本地 .md 文件,用 Marked.js 解析成 HTML,就是一个完整的无后端博客。
要实现的效果:
- 主页"我的博客"区域只显示博文标题列表
- 标题从
posts文件夹动态读取(post1.md,post2.md...) - 点击标题:页面不跳转,在当前页动态获取并解析 Markdown
- 将格式化后的 HTML 渲染到阅读区域
async function loadPost(filename) {
try {
const res = await fetch(`posts/${filename}`);
const md = await res.text();
document.getElementById('post-content').innerHTML =
marked.parse(md); // Marked.js 解析
} catch (err) {
console.error('加载文章失败:', err);
}
}
posts/Post1.md 和 posts/post1.md 是两个不同的文件。另外 posts 文件夹必须和 index.html 同层,并且文件已 push 到仓库。
粒子星空背景
用 Canvas 实现星空粒子 + 鼠标引力效果
前面的选修让网站"有内容",接下来的彩蛋让网站"有灵魂"。这些小功能不需要大而全,但要有质感——给访客一个"哇"的瞬间。至少选做 1 项。
你见过那些背景是星空的网站吗?鼠标一滑,星星被"吸"过去,松开又散回来。这种效果用 <canvas> + requestAnimationFrame 就能实现。关键是理解动画循环:每帧清空画布 → 更新粒子位置 → 重新绘制。
要实现的效果:
- 整个页面背景为
<canvas>画布 - 画布上随机分布闪烁的"星星"粒子
- 鼠标移动时,周围产生更密集的"星云"
- 附近星星受引力影响,产生穿梭感

图:粒子星空背景效果
【AI 生图提示词】请生成一张粒子星空背景效果示意图: - 画面主体:深色(#0a0a2e 深蓝黑)背景上散布大量微小的白色和淡蓝色圆点,模拟星空 - 画面中央偏右有一个鼠标光标图标,周围 150px 范围内的粒子被"吸引"聚集,形成密集的星云效果 - 远离光标的粒子保持随机分布,大小不一(1px-3px),部分粒子带微弱的光晕效果 - 光标吸引区域用虚线圆圈标注"引力范围 150px" - 右下角放一个小标签"
/* 核心动画循环 */
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach(p => {
const dx = mouse.x - p.x;
const dy = mouse.y - p.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 150) {
p.x += dx * 0.01; // 被鼠标吸引
p.y += dy * 0.01;
}
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fillStyle = p.color;
ctx.fill();
});
requestAnimationFrame(animate);
}
练习知识点:Canvas API、坐标计算、requestAnimationFrame
隐藏彩蛋
↑↑↓↓←→←→BA —— 输入秘籍触发惊喜
Konami Code 是游戏史上最有名的秘籍。你的网站上也可以藏一个:页面上没有任何提示,只有"知道的人"才能触发。这个功能的精髓在于用数组模拟滑动窗口——只保留最近的 N 次按键,和秘钥序列做比较。
const konamiCode = [
'ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown',
'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight',
'KeyB', 'KeyA'
];
let inputBuffer = [];
window.addEventListener('keydown', (e) => {
inputBuffer.push(e.code);
// 滑动窗口:只保留最近 10 次按键
if (inputBuffer.length > konamiCode.length) {
inputBuffer.shift();
}
// 比较是否匹配
if (inputBuffer.join(',') === konamiCode.join(',')) {
triggerEasterEgg();
inputBuffer = [];
}
});
function triggerEasterEgg() {
document.body.classList.toggle('retro-theme');
// 或者弹出一个贪吃蛇小游戏...
}
练习知识点:keydown 事件、event.code、数组 push + shift 滑动窗口
细节魔王
动态 Title + 自定义光标,打磨极致细节体验
动态 Title
同时开了十几个标签页,某个标签的标题突然变成了"别走呀,快回来!QAQ"——你忍不住又点回去了。就是这种小细节让网站有了"灵魂"。
document.addEventListener(
'visibilitychange', () => {
if (document.hidden) {
document.title = '别走呀,快回来!QAQ';
} else {
document.title = '我的个人主页';
}
});
自定义光标
把系统默认箭头换成你设计的小圆点。悬停到按钮、链接等可点击元素上时,圆点放大并变形——"光标会呼吸"的效果,让每次交互都有触感。
/* 隐藏默认光标 */
* { cursor: none; }
.cursor {
width: 20px; height: 20px;
border-radius: 50%;
background: var(--primary-color);
position: fixed;
pointer-events: none;
transition: transform 0.15s ease;
}
.cursor.hover {
transform: scale(2);
opacity: 0.5;
}
练习知识点:visibilitychange、mousemove、CSS cursor、event.target.matches()
知识点总览
回顾你在这个作业中用到的每一项核心技能
| 阶段 | 模块 | 核心知识点 |
|---|---|---|
| 必修 | 日夜模式切换 | querySelector, addEventListener, classList.toggle, CSS transition |
| 必修 | 打字机标题 | setTimeout, 字符串 slice, 数组索引, textContent |
| 必修 | 项目动态渲染 | async/await, fetch, try...catch, class, constructor, this |
| 必修 | 项目筛选 | map, filter, Set, 事件委托, CSS transition |
| 选修 | 动态技能条 | IntersectionObserver, threshold, unobserve, CSS @keyframes |
| 选修 | 技能雷达图 | Chart.js, JSON 数据格式化 |
| 选修 | 时间轴 | CSS 伪元素, nth-child, 事件委托, 复杂 DOM 结构 |
| 选修 | 互动地图 | Leaflet.js, Popup 交互, 坐标系统 |
| 选修 | Markdown 博客 | Marked.js, fetch 加载文件, SPA 体验 |
| 彩蛋 | 粒子背景 | Canvas API, requestAnimationFrame, 坐标计算 |
| 彩蛋 | Konami Code | keydown 事件, 数组 push/shift 滑动窗口 |
| 彩蛋 | 细节魔王 | visibilitychange, mousemove, CSS cursor |
提交要求
确保你的作品完整、可访问、有文档
1. 在线作品链接
https://你的用户名.github.io
确保 GitHub Pages 已正确部署,页面可以正常访问。
2. 代码仓库链接
提供你的 GitHub 仓库地址
代码应结构清晰、有合理的文件组织。
3. README.md 文档
内容包括:
- 项目简介和设计理念
- 已实现功能列表
- 遇到的最大挑战及解决方案
动手开始吧!
完成这个综合作业后,你将拥有一个真正属于自己的线上作品。
下一章回到主线:错误处理与调试——让代码更健壮。
📝 项目总结
通过本项目你掌握了
DOM + 异步
动态渲染页面、fetch API 获取数据、事件驱动交互
OOP 设计
用 Class 组织代码,封装粒子系统、卡片组件等模块
Canvas 动画
粒子背景、动画循环、性能优化
部署上线
GitHub Pages 配置、自动化部署、域名绑定