简介:这是一套基于HTML5、CSS与JavaScript构建的二十四节气主题网站模板,定位为前端学习者的大作业或毕业设计参考,也可用于传统文化科普站点。模板采用单页式结构,包含首页、节气概述、分类、时间轴、图鉴、文化、在线留言等模块,并集成了页面加载动画、粒子背景、轮播图、3D悬停卡片、视频播放、快速导航、标签页切换、回到顶部等交互效果,能直观展示节气文化与前端常见动效。压缩包共44个文件,以39张jpg图片素材为主,辅以2个js、1个css、1个html和1个mp4视频,包体约24.44MB;样式与脚本独立存放,页面适配手机端,附有部分中文注释,适合初学者边看边改、逐步理解页面结构与动效实现。目前已有111人学习,对于需要完成课程作业或毕业设计的学生而言,是一份可以直接套版、快速出效果的实用参考。
1. 一份HTML5二十四节气网站的zip,装的不只是网页
拿到一个“HTML5二十四节气网站.zip”,第一反应往往是解压、双击index.html,然后发现浏览器里要么空白、要么样式错乱。问题多半不在代码本身,而在于这个项目的运行前提:它是一套不依赖构建工具的纯静态站点,HTML、CSS、JavaScript、SVG资源全部本地化,任何一处用了绝对路径或跨域请求,直接双击打开就会失效。二十四节气本身是一条线性历法,适合用信息展示型页面呈现,而HTML5恰好提供了语义化结构、原生交互API和SVG动画三样地基。这篇文章要拆的,就是把这类节气主题站点从数据层做到视觉层、交互层,最后封进一个能正常解压运行的zip的完整路径。适合想独立完成一版前端作品、或者要交付一个不需要安装依赖的静态站的人。
2. 先用语义化HTML5把二十四节气的信息骨架立住
很多人在做节气网站时,第一步就是写24个div,然后再回头补样式。这样做的直接后果是:后续要加详情、做季节过滤、按节气名搜索,全都得改HTML。更稳妥的做法是先想清楚页面分成哪几个区域,再把节气内容从HTML中剥离到JavaScript数据结构里。zip包里跑的是一套“数据 + 渲染脚本”,而不是一张把24个节气写死的静态页。
2.1 首页分区:header、aside、figure 在节气页面里的实际用途
一个常规节气展示页面的信息层级大致是:全局头部(站名、当前节气)、主体区(24节气卡片网格)、侧边栏(季节导航)、页脚(数据来源说明)。HTML5语义标签在这里不是装饰,而是让页面结构可读、可维护的基础。
<!doctype html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>二十四节气 · HTML5</title> <link rel="stylesheet" href="assets/css/style.css"> </head> <body> <header class="site-header"> <h1>二十四节气</h1> <p class="current-term" id="currentTerm">今日节气:计算中…</p> </header> <main id="termGrid" class="term-grid" aria-label="二十四节气列表"> <!-- 卡片由JS生成 --> </main> <aside class="season-sidebar" aria-label="季节导航"> <button type="button">// assets/js/terms-data.js window.SOLAR_TERMS = [ { id: 1, name: "立春", season: "spring", date: "2025-02-03", time: "22:10:13", description: "春气始而建立,万物随阳气上升而萌芽。", customs: ["咬春", "打春牛"], foods: ["春饼", "萝卜", "五辛盘"], poem: "东风带雨逐西风,大地阳和暖气生。", icon: "licun" } // 其余23个节气按相同结构补齐 ];逻辑说明:挂到window.SOLAR_TERMS而不是用const声明,是为了让main.js在另一个脚本文件里直接读取;若改用ES Module的import,在file://协议下同样会被CORS拦下,这是zip场景与传统前端工程最大的差异点。date和time分开存,既支持按日期判断“今天属于哪个节气”,又保留了精确到时刻的扩展能力。season字段把24个节气映射到四季,切换主题时只需filter(term => term.season === current)。icon对应SVG雪碧图中的symbol id,渲染时用<use href="#licun">引入。
参数说明:日期统一用YYYY-MM-DD字符串,字典序即时间序,判断节气区间时可以直接用字符串比较;如果混入2025/02/03这类格式,比较结果会错乱。id从1到24保持稳定,后续做URL定位#term-5和localStorage存储都依赖它。
下面的字段规划表,是写数据文件前建议先列好的:
| 字段 | 类型 | 用途 | 注意事项 |
|---|---|---|---|
| id | number | 唯一标识、URL定位 | 固定1-24,不建议用数组下标代替 |
| name | string | 节气名称 | 保持标准写法:立春、雨水… |
| season | string | 四季主题映射 | 只取 spring/summer/autumn/winter |
| date | string | 公历日期 | YYYY-MM-DD,不要带星期 |
| time | string | 交节时刻 | 格式HH:mm:ss,仅展示用 |
| icon | string | SVG symbol引用 | 与sprite中id严格一致 |
2.2.1 当前节气判断:区间比较而非日期等值比较
用户打开页面时,页面顶部要显示“今日节气:大暑”。这个判断不能写成“当前日期等于数据里的日期”,因为节气之间隔了约15天,立春日期是2月3日,那么2月4日到2月18日之间仍然属于立春区间。正确做法是找到“当前日期所在的那一段节气区间”:
function getCurrentTermIndex(now, terms) { const nowDate = new Date(now); const nowKey = dateToKey(nowDate); // 输出 2025-07-20 for (let i = 0; i < terms.length; i++) { const startKey = terms[i].date; const endKey = i < terms.length - 1 ? terms[i + 1].date : `${nowDate.getFullYear()}-12-31`; if (nowKey >= startKey && nowKey < endKey) { return i; } } return 0; // 兜底:数据缺失时默认立春 } function dateToKey(d) { const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${d.getFullYear()}-${m}-${day}`; }参数说明:判断区间用的是右开[start, end),即当前日期等于某个节气的date时算作进入该节气,而等于下一个节气的date时进入下一个。这个边界如果不处理,立春和雨水交界的当天会同时命中两条。endKey在最后一个节气时落入12-31哨兵值,跨年时记得更新数据表,否则冬至之后到12月31日之间会找不到所属节气。
骨架层到这里已经具备:页面有语义分区,数据表可驱动渲染。接下来进入视觉层,让节气主题从“一堆文字卡片”变成“有季节氛围的页面”。
3. 视觉层:用CSS3与SVG把节气氛围做成可切换主题
节气网站最忌做成信息瀑布流:24张卡片排得整整齐齐,每张上面一张图下面一段字,整体毫无记忆点。要让页面有“季节感”,核心方案是设计一套可切换的四季主题变量,再用SVG动画让节气图标活起来。整个过程不引入任何框架,浏览器原生就够。
3.1 CSS变量与四季主题:改一处属性,全站换肤
在:root里定义春季节气的基准色,其余季节通过body[data-season]覆盖变量值。这样切换季节时,JavaScript只需要改body上的>:root { --bg: #f7f3e8; --card-bg: #fffdf7; --text: #3b342c; --accent: #4a7c59; --card-radius: 16px; } body[data-season="summer"] { --bg: #e8f2e6; --accent: #2f6b4f; } body[data-season="autumn"] { --bg: #f8efe3; --accent: #a4583c; } body[data-season="winter"] { --bg: #eef1f5; --accent: #3e5a7a; } body { background: var(--bg); color: var(--text); transition: background-color .4s ease, color .4s ease; }
逻辑说明:CSS变量在这里的价值不是炫技,而是省掉“24张卡片 + 3种季节”的排列组合选择器。卡片文字、边框、图标描边统一引用var(--accent),换季节只改一个变量。用CSS变量而非Sass的原因很实际:zip包要求解压即用,不能有“安装依赖 → 编译 → 输出”的中间链路。transition只写在background-color和color上,避免其他属性参与变换导致的重绘开销。
参数说明:--card-radius这类非颜色变量也可以放入主题区,统一管理圆角与间距。若想让“当前节气卡片”比普通卡片更醒目,可直接在.term-card.is-current上覆盖--accent,视觉上会与同季节其他卡片形成层次差异。
3.2 内联SVG图标:symbol雪碧图与CSS动画配合
24个节气图标如果全部用PNG,包体膨胀且风格难统一。更合理的方案是做一个SVG雪碧图,里面放24个<symbol>,页面中通过<use>引用。这样做的好处是图标整体只有一份文件,CSS能直接选中symbol内的path做动画。
<svg xmlns="http://www.w3.org/2000/svg" style="display:none"> <symbol id="rain" viewBox="0 0 48 48"> <path id="rain-cloud" d="M14 34a7 7 0 0 1 0-14 9 9 0 0 1 17-4 8 8 0 0 1 3 15H14z" fill="#8aa6b3" /> <g id="rain-drops" stroke="#4A90D9" stroke-width="2" stroke-linecap="round" fill="none"> <line x1="18" y1="36" x2="16" y2="42" /> <line x1="25" y1="36" x2="23" y2="42" /> <line x1="32" y1="36" x2="30" y2="42" /> </g> </symbol> </svg>渲染节气卡时,图标节点只需要一行:
<svg class="term-icon">.term-card:hover .term-icon[data-icon="rain"] { animation: rainfall 1.2s ease-in-out infinite; } @keyframes rainfall { 0% { transform: translateY(0); opacity: .9; } 50% { transform: translateY(4px); opacity: .6; } 100% { transform: translateY(0); opacity: .9; } }逻辑说明:整张雪碧图用display:none隐藏,但<use>引用到的内容仍会正常渲染,这是SVG文档内嵌引用的标准写法。>const canvas = document.getElementById('fx'); const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (canvas && !reduceMotion) { const ctx = canvas.getContext('2d'); const particles = []; const COUNT = window.innerWidth < 768 ? 24 : 48; for (let i = 0; i < COUNT; i++) { particles.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, r: Math.random() * 2 + 1, vy: Math.random() * 0.6 + 0.2, vx: (Math.random() - 0.5) * 0.2 }); } function tick() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'rgba(255,255,255,0.75)'; particles.forEach(p => { p.y += p.vy; p.x += p.vx; if (p.y > canvas.height) p.y = 0; ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); ctx.fill(); }); requestAnimationFrame(tick); } tick(); }
逻辑说明:prefers-reduced-motion是浏览器暴露的用户偏好,系统开启“减弱动态效果”时直接跳过canvas初始化,一帧都不会跑。粒子数量按屏幕宽度切到24或48,是移动端与桌面端的直接取舍。白色半透明圆点适合表现雪或柳絮;要做雨丝,把fillStyle换成细线、vy提到2~4即可。
参数说明:canvas元素的CSS尺寸要和绘制像素尺寸一致,否则粒子会发虚或被拉伸。可用以下方式兜底:
canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight;设置时放在粒子初始化之前,并监听resize重新赋值。还有一种更常见的坑:canvas默认有内联宽度300x150,如果CSS设了width: 100%; height: 100%但没同步给canvas.width,粒子只会出现在左上角一小块区域。
视觉层做完后,页面已经具备“季节感”。下一层把交互补上:详情展开、浏览位置记忆、滚动动画、季节筛选。
4. 交互层:不引依赖,用HTML5自带能力做详情加载与状态记忆
一个zip包内的节气网站,不应该要求用户联网加载CDN脚本。所有交互尽量走浏览器原生API,像<details>、localStorage、IntersectionObserver这些都是现成的能力,写起来反而比引库更稳。
4.1 用 details/summary 做无JS的节气详情展开
节气卡片上通常有“习俗、饮食、诗词”三类信息,默认全部展开会让页面变得很长,折叠起来又需要JavaScript帮忙。原生<details>恰好解决这个问题:
<details class="term-detail"> <summary>习俗与饮食</summary> <ul> <li>咬春</li> <li>打春牛</li> <li>吃春饼、萝卜</li> </ul> </details>逻辑说明:浏览器原生支持折叠/展开,键盘Tab聚焦、回车切换都不需要额外写事件。默认<details>是关闭的;要对“当前节气”默认展开,在渲染时判定命中的卡片加open属性。
参数说明:Chrome里summary自带黑色三角标记,通常要隐藏掉再替换成自定义加号:
.summary::-webkit-details-marker { display: none; } .term-detail summary { cursor: pointer; list-style: none; position: relative; padding-left: 22px; } .term-detail summary::before { content: "+"; position: absolute; left: 0; }注意list-style: none是Firefox隐藏三角的写法,::-webkit-details-marker是WebKit内核的写法,两者都要保留。如果希望每次只展开一个详情,可以监听toggle事件,关闭其他卡片的open属性;这个手风琴行为不是原生的,但多数用户期望是这样。
4.2 localStorage记录浏览位置与主题偏好,刷新不迷路
用户找了半天才点开“大暑”,刷新页面后又回到顶部。把当前节气id和季节主题存进localStorage,下次回访时直接恢复。这里的坑比功能本身更值得注意:
const STORE_KEY = 'solar-site-pref-v1'; function savePref() { const data = { lastTermId: currentTermId, season: document.body.dataset.season || "spring", ts: Date.now() }; try { localStorage.setItem(STORE_KEY, JSON.stringify(data)); } catch (err) { console.warn("localStorage不可用,忽略本次保存", err); } } function loadPref() { let raw = null; try { raw = localStorage.getItem(STORE_KEY); } catch (err) { return null; } if (!raw) return null; try { return JSON.parse(raw); } catch (err) { console.warn("preference解析失败,忽略恢复", err); return null; } }逻辑说明:setItem在浏览器隐私模式、存储配额满、部分企业安全策略下会抛异常,不包try/catch会中断整段脚本。JSON.parse同样要保护,因为localStorage里的内容是可被用户手动修改的,坏了不能影响页面正常显示。ts字段先存着,后续若做“超过7天重置偏好”的功能,直接用这个时间戳判断即可。
参数说明:存储key建议带版本号v1,以后数据结构变化时,旧key可以直接废弃,不用写迁移逻辑。存储内容只放“偏好”,不放节气原始数据;数据仍从terms-data.js读取,状态与数据分离,避免用户清理浏览器后页面变成空白。
4.3 IntersectionObserver做滚动进入动画,替代scroll监听
节气卡滚入视口时做一个淡入上移的动画。传统做法是监听scroll,每次滚动遍历所有卡片计算位置;这个页面有24张卡,虽然没那么夸张,但移动端滚动监听的开销仍然明显。IntersectionObserver由浏览器在合适的时机回调,不算性能负担:
const io = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add("is-visible"); io.unobserve(entry.target); } }); }, { root: null, threshold: 0.15, rootMargin: "0px 0px -30px 0px" }); document.querySelectorAll(".term-card").forEach(card => io.observe(card));逻辑说明:unobserve是性能要点。动画只播一次,观察完的卡片从观察列表里移除,后续滚动不再重复触发。卡片初始样式设为opacity: 0; transform: translateY(16px);,加上.is-visible后过渡到可见状态。首屏卡片在页面加载后立即触发,视觉上相当于直接显示,不会有明显的延迟闪现。
参数说明:threshold: 0.15表示卡片有15%进入视口才触发,这个值对横向卡片比较友好;rootMargin: "0px 0px -30px 0px"表示底部边界提前30px触发,适合让动画发生在“用户刚看到卡片边缘”的时机。调试时若发现卡片停在透明状态,优先检查IntersectionObserver是否在旧浏览器里不存在:
if (!("IntersectionObserver" in window)) { document.querySelectorAll(".term-card").forEach(card => { card.classList.add("is-visible"); }); }这段降级代码在所有卡片加载后执行,直接显示全部卡片,避免不支持该API的浏览器出现“内容永远看不见”的问题。
4.4 事件委托、节气高亮与URL状态同步
24张卡逐个绑定click事件既冗余又难维护,常见做法是把监听绑在同一个父容器上,再用closest找到目标卡片:
const grid = document.getElementById("termGrid"); grid.addEventListener("click", (e) => { const card = e.target.closest("[data-term-id]"); if (!card) return; const id = Number(card.dataset.termId); const term = SOLAR_TERMS.find(t => t.id === id); if (!term) return; document.querySelectorAll(".term-card").forEach(c => c.classList.remove("is-current")); card.classList.add("is-current"); document.body.dataset.season = term.season; window.location.hash = `term-${id}`; savePref(); });逻辑说明:closest("[data-term-id]")会沿DOM向上找带该属性的祖先,所以点击卡片里的文字、SVG图标都能命中。用dataset.termId与数据表匹配,而不是比中文节气名,避免全角半角、空格差异导致匹配失败。window.location.hash写入#term-5,刷新后可以从location.hash恢复高亮,这个hash可以分享,别人打开同一链接能定位到同一张卡。
参数说明:Number(card.dataset.termId)必须转换类型,dataset取出的值都是字符串,而数据表里id是数字,直接用===比较会永远为false。savePref()放在hash更新之后,确保恢复页面时能还原到刚选中的节气。
5. 收尾:把HTML5二十四节气网站封装成可复现的zip
5.1 一个最小可发布的目录结构与瘦身清单
打包之前先做一次目录收拢,将源码与发布目录分离。常见交付结构如下:
dist/ ├── index.html ├── assets/ │ ├── css/style.css │ ├── js/terms-data.js │ ├── js/main.js │ ├── icons/sprite.svg │ ├── img/ # 未被SVG替代的位图 │ └── fonts/ # 可选:中文字体子集 └── README.txt发布前三项必做:检查所有资源都使用相对路径,任何/assets/...绝对路径在解压到子目录后都会404;图片转WebP后保留同名的PNG回退,用<picture>标签做兼容;SVG文件删除编辑器导出的元数据、空分组和未使用的滤镜。这个步骤能直接把zip体积砍掉三到五成。
5.2 用命令行压缩,处理中文文件名与目录层次
Windows下最稳妥的打包方式是7-Zip命令行,而不是PowerShell的Compress-Archive。后者的中文文件名在部分老旧解压工具里会出现乱码,目录嵌套也容易出错。固定参数写成一个bat脚本即可:
7z a -tzip -scsUTF-8 -r ..\release\24-solar-terms.zip .\dist\*翻译成PowerShell同样适用:
7z a -tzip -scsUTF-8 -r ..\release\24-solar-terms.zip .\dist\*参数含义:-tzip指定zip格式;-scsUTF-8让文件名按UTF-8编码写入,这是中文文件名不乱码的关键;-r递归包含子目录;.\dist\*只打包dist目录内部内容,避免外层目录被套进zip造成“解开后还要再进一层文件夹才能看到index.html”。输出到..\release\是为了防止压缩包出现在dist内部,把自己套进包里。
macOS/Linux 上的zip命令同样需要排除系统文件:
cd dist && zip -r ../24-solar-terms.zip . -x "*.DS_Store" -x "*__MACOSX*"打包后务必原地解压一次,确认根目录直接出现index.html。如果解压后第一层是个文件夹,说明打包时多套了一层目录。
5.3 解压后的验收清单
以下五条检查项可以在本地直接完成,不用上传服务器:
| 检查项 | 预期结果 | 失败常见原因 |
|---|---|---|
| 双击打开index.html | 24张节气卡全部渲染 | main.js或terms-data.js路径错误 |
| 浏览器控制台无报错 | 无404、无红色报错 | sprite.svg路径或use引用id错误 |
| 点击节气卡片 | 卡片高亮、hash变为#term-x | >cd dist python -m http.server 8080 浏览器访问 本文还有配套的精品资源,点击获取
版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设
2026/9/14 23:49:26
基于Hadoop的商品推荐系统:MapReduce协同过滤实现简介:基于Hadoop的商品推荐系统课程设计项目以zip压缩包形式发布,适合大数据、分布式计算方向的高校学生以及推荐系统入门开发者学习使用,主要解决海量用户行为数据场景下商品推荐系统的工程搭建问题。压缩包共含19个文件,包括10个…
网站建设
2026/9/14 23:48:18
SpringBoot+SSM构建高校成绩分析系统实践/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …
网站建设
2026/9/14 23:46:09
基于SpringBoot+Vue的社区医院管理系统设计与实战接手“基于SpringBootVue的社区医院管理系统”这类项目时,很多人会先入为主地觉得:无非就是 Java 后端加点页面,把增删改查写完就交差。但真把这个系统从零搭起来,你会发现要理顺的远不止 CRUD,挂号、收费、药房库存和…
网站建设
2026/9/14 23:46:08
读懂GitHub Trending日榜:从热榜趋势到开源项目落地实践指南每天夜里刷一遍 GitHub Trending,已经是很多开发者戒不掉的习惯。2026-09-08 的日榜我也认真过了一遍,说句实话,日榜比周榜、月榜都更有“现场感”,它反映的是过去 24 小时里技术社区正在为什么东西兴奋。这篇文章不打算报菜名式地…
网站建设
2026/9/14 23:44:50
PowerShell函数参数详解与最佳实践1. PowerShell函数参数基础解析在PowerShell脚本开发中,函数参数是实现功能复用的关键要素。参数允许我们向函数传递数据,使函数能够根据不同的输入产生不同的输出。PowerShell提供了多种参数处理方式,包括位置参数、命名参数、开关参数等&am…
网站建设
2026/9/14 23:44:48
Python编程入门:第一次作业全指南与实战技巧1. Python第一次作业:从零开始的编程初体验作为一名Python教学经验超过10年的开发者,我见过太多初学者在第一次作业时手足无措的样子。第一次Python作业往往决定了学生对编程的第一印象——它应该像学习骑自行车一样,在适当的指导下获得"… |