尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

用HTML、CSS与JavaScript打造三只宠物的互动展示小站

用HTML、CSS与JavaScript打造三只宠物的互动展示小站 看到这个标题很多读者可能会好奇文章是打算晒三只小动物的日常吗其实不是。这是我在整理前端练习素材时冒出来的一个灵感——与其拿千篇一律的“Todo List”或“登录表单”练手不如做一个充满童趣的宠物展示小网站让三只性格迥异的“小家伙”作为主角串起 HTML、CSS、JavaScript 的核心知识点。这篇实战教程会从一个纯静态页面开始逐步实现宠物卡片展示、互动按钮投喂、抚摸、玩耍、心情与精力状态管理、本地存储、夜间模式切换等常见功能。整个过程不依赖任何框架不需要安装 Node.js不涉及后端服务只要有一台电脑、一个浏览器和任意一个文本编辑器就能完成。读完并动手实践后你将掌握HTML 语义化标签的合理组织方式CSS Flex / Grid 布局、动画和主题切换的写法JavaScript 对象数组、事件监听、DOM 渲染和 localStorage 持久化一个完整小项目从设计到运行的思路闭环。如果你刚学完前端基础正愁没有合适的项目练手或者想快速做一个可爱风格的展示页面送给朋友这篇文章可以直接照着抄。1. 项目背景与功能规划1.1 这个项目解决什么问题很多初学前端的人会遇到一个尴尬阶段语法都看懂了但一打开编辑器不知道写什么。做计算器太枯燥做后台管理模板又涉及太多框架知识。而“宠物展示页”这类项目刚好处于“简单但不简陋”的区间——它足够轻量能让初学者把注意力集中在三件核心事情上用语义化 HTML 搭建页面骨架用 CSS 控制视觉表现和布局用 JavaScript 让页面“动”起来。同时“三只小宠物”的设定天然具备叙事性写代码的过程更像是给三只虚拟角色做成长记录页。看着页面逐渐成型、按钮产生响应、状态被保存这种成就感是单纯的语法练习无法带来的。1.2 功能模块拆解在动手写代码之前先明确页面需要具备哪些能力。我不建议一上来就写先花两分钟做拆解后面代码组织会清晰很多。模块功能说明涉及技术点宠物信息展示展示三只宠物的头像、名字、年龄、性格标签和简介HTML 结构、CSS 卡片布局状态面板每只宠物拥有心情值、精力值两个数值属性JavaScript 对象、动态渲染互动按钮投喂、抚摸、玩耍三种操作影响数值变化事件监听、函数封装数据持久化刷新页面后状态不被重置localStorage主题切换白天/夜间模式一键切换CSS 变量、class 切换响应式布局在手机和电脑上都能正常浏览Flex / Grid 媒体查询1.3 三只“小家伙”的人设为了让项目有辨识度我给三只宠物设定了简单的人设。你在复制代码时可以随意改名、换性格标签。橘子一只喜欢晒太阳的小橘猫性格慵懒最爱小鱼干心情值掉得最慢布丁一只精力旺盛的小柯基热爱奔跑玩耍时精力消耗最快棉花糖一只毛茸茸的小白兔胆子小被抚摸时心情值涨得最快。这三个人设不仅让页面有温度还会影响后续 JavaScript 参数设计。比如每只宠物的数值增减幅度可以不同这正好引出“配置数据驱动渲染”的思想。2. 环境准备与项目结构2.1 开发环境说明本项目介绍的是纯前端静态页面对运行环境的要求非常低。你可以选择任意一种方式创建项目使用 VS Code 等编辑器新建文件夹手动创建index.html、style.css、script.js三个文件直接复制下面的完整代码在本地保存后双击index.html用浏览器打开如果你更习惯在线编辑也可以使用 CodePen、JSFiddle 等在线工具直接粘贴运行。版本方面不需要过度纠结。示例代码使用的是 ES6 语法const、let、模板字符串、箭头函数只要浏览器版本不是特别老旧即可。建议使用 Chrome、Edge 或 Firefox 最新版测试。2.2 项目目录结构推荐创建如下目录cute-three-pets/ ├── index.html ├── style.css └── script.js其中index.html负责页面结构是项目的入口style.css负责全部样式包括布局、颜色、动画和响应式规则script.js负责宠物数据、互动逻辑、存储读写和主题切换。把结构、样式、行为分离是前端开发的基本规范。即使以后项目变大这种分层也会让你的代码更容易维护。3. 核心设计思路拆解3.1 用 JavaScript 对象描述宠物数据整个项目的核心是“宠物数据”。如果直接把三只宠物写死在 HTML 里那么互动逻辑和渲染逻辑都会变得很僵硬。更合理的做法是先用 JavaScript 对象数组保存宠物信息然后动态生成卡片。const pets [ { id: cat, name: 橘子, avatar: , type: 小橘猫, age: 2岁, desc: 喜欢晒太阳最爱小鱼干。, tags: [慵懒, 黏人], mood: 80, energy: 70, moodChange: { feed: 5, stroke: 8, play: -10 }, energyChange: { feed: 6, stroke: 2, play: -15 } }, { id: dog, name: 布丁, avatar: , type: 小柯基, age: 1岁半, desc: 精力旺盛每天都要出门跑三圈。, tags: [活泼, 贪玩], mood: 70, energy: 90, moodChange: { feed: 4, stroke: 5, play: 12 }, energyChange: { feed: 5, stroke: 1, play: -20 } }, { id: rabbit, name: 棉花糖, avatar: , type: 小白兔, age: 8个月, desc: 胆子很小但熟悉之后特别粘人。, tags: [温柔, 胆小], mood: 60, energy: 55, moodChange: { feed: 5, stroke: 12, play: -6 }, energyChange: { feed: 4, stroke: 3, play: -8 } } ];这段代码中mood和energy是每只宠物的当前状态值范围建议控制在 0 到 100moodChange和energyChange是三种互动操作对数值的影响幅度feed表示投喂stroke表示抚摸play表示玩耍。把参数设计成这样后续新增一只宠物只需要往数组里追加一个对象不需要修改渲染逻辑这其实就是“数据驱动视图”的雏形。3.2 渲染函数让数据和 UI 保持同步修改数据之后必须让页面重新更新这个操作我们称之为“渲染”。常规做法是写一个renderCards函数循环遍历pets数组生成对应的卡片 HTML 字符串然后插入到页面容器中。function renderCards() { const container document.getElementById(pet-list); container.innerHTML pets.map(pet { return article classpet-card>document.getElementById(pet-list).addEventListener(click, function (e) { const btn e.target.closest(.btn); if (!btn) return; const card btn.closest(.pet-card); const petId card.dataset.id; const action btn.dataset.action; handleAction(petId, action); });e.target.closest(.btn)会向上查找最近的.btn元素。如果点击位置不在按钮上closest返回null这时直接return即可。这种写法在列表项很多、需要频繁增删元素的场景中非常实用。3.4 数值变化与边界处理handleAction函数负责更新宠物数据。这里要注意一个问题心情值和精力值不能无限上涨或下跌应该被限制在 0 到 100 之间。function handleAction(petId, action) { const pet pets.find(p p.id petId); if (!pet) return; pet.mood pet.moodChange[action]; pet.energy pet.energyChange[action]; pet.mood Math.max(0, Math.min(100, pet.mood)); pet.energy Math.max(0, Math.min(100, pet.energy)); saveData(); renderCards(); }Math.max(0, Math.min(100, value))是一种非常常见的数值裁剪写法先限制上限 100再限制下限 0。如果你不这样做投喂多次后数值可能变成 120精力也有可能变成负数。saveData()负责把宠物数组写入 localStorage。这里需要先把 JavaScript 对象转换成 JSON 字符串因为 localStorage 只能存储字符串。function saveData() { localStorage.setItem(cute-three-pets, JSON.stringify(pets)); } function loadData() { const saved localStorage.getItem(cute-three-pets); if (saved) { const parsed JSON.parse(saved); if (Array.isArray(parsed) parsed.length) { parsed.forEach((item, index) { pets[index].mood item.mood; pets[index].energy item.energy; }); } } }loadData中做了一层防御性判断因为 localStorage 里的内容可能被用户手动修改或删除不校验就直接使用可能让页面崩溃。这个习惯在真实项目中很重要。4. 完整实战案例三只宠物的互动小站4.1 创建项目结构第一步新建一个文件夹命名为cute-three-pets。在文件夹内创建三个文件index.htmlstyle.cssscript.js如果你使用的是 VS Code可以直接在编辑器里创建这些文件然后安装一个名为Live Server的插件右键index.html选择“Open with Live Server”就能在浏览器中实时预览效果。如果没有插件直接双击index.html也能运行只是修改代码后需要手动刷新浏览器。4.2 编写 HTML 骨架index.html是所有内容的载体。页面整体分为三块区域顶部导航、宠物卡片主舞台、底部页脚。我把首页展示区与相册趣味区都放在了主容器内让页面内容更丰富。!-- 文件路径index.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title可爱的三小只~/title link relstylesheet hrefstyle.css /head body header classsite-header h1 可爱的三小只~/h1 p这是一座由 HTML、CSS 和 JavaScript 搭建的宠物小站/p button idtheme-toggle classtheme-btn 夜间模式/button /header main classcontainer section classintro h2欢迎来到萌宠之家/h2 p点击每只宠物卡片上的按钮投喂、抚摸或陪它玩耍看看它们的情绪和精力会发生什么变化。所有状态都会保存在你的浏览器中刷新页面也不会消失。/p /section section idpet-list classpet-list !-- 宠物卡片由 JavaScript 动态生成 -- /section section classgallery h2生活时光/h2 div classgallery-grid div classgallery-item 午后晒太阳/div div classgallery-item 草地狂奔/div div classgallery-item 偷吃胡萝卜/div /div /section /main footer classsite-footer pMade with ❤️ by HTML / CSS / JavaScript/p /footer script srcscript.js/script /body /html这段 HTML 中有几个值得注意的细节meta nameviewport ...是移动端适配的基础配置没有它手机浏览器会自动缩放页面script标签放在body末尾是为了保证脚本执行时页面中的 DOM 元素已经全部加载完成section idpet-list classpet-list是一个空容器里面的卡片内容将由 JavaScript 动态插入。4.3 编写 CSS 样式CSS 是整个页面“可爱感”的来源。我会通过 CSS 变量定义配色方便后续切换夜间模式。核心样式包括卡片布局使用 Grid在宽屏下三列排列窄屏下自动降为单列状态条采用两层嵌套结构外层是灰色背景内层宽度由 JavaScript 控制按钮使用圆角与 hover 效果夜间模式通过给body添加night类实现。/* 文件路径style.css */ :root { --bg-color: #fff8f0; --text-color: #4a3f35; --card-bg: #ffffff; --card-shadow: rgba(255, 180, 130, 0.25); --primary: #ff9a76; --primary-light: #ffd4c2; --bar-bg: #f0e6dd; --tag-bg: #fff0e8; } body.night { --bg-color: #2b2b36; --text-color: #ece5dd; --card-bg: #3a3a48; --card-shadow: rgba(0, 0, 0, 0.3); --bar-bg: #55556a; --tag-bg: #494958; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: PingFang SC, Microsoft YaHei, sans-serif; background-color: var(--bg-color); color: var(--text-color); transition: background-color 0.4s ease, color 0.4s ease; line-height: 1.6; } .site-header { text-align: center; padding: 40px 20px 20px; } .site-header h1 { font-size: 32px; } .theme-btn { margin-top: 16px; padding: 8px 18px; border: none; border-radius: 20px; background-color: var(--primary); color: #fff; font-size: 14px; cursor: pointer; transition: opacity 0.3s; } .theme-btn:hover { opacity: 0.9; } .container { max-width: 1080px; margin: 0 auto; padding: 20px; } .intro { text-align: center; margin-bottom: 32px; } .intro h2 { font-size: 24px; margin-bottom: 8px; } .intro p { max-width: 600px; margin: 0 auto; color: var(--text-color); opacity: 0.8; } .pet-list { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; margin-bottom: 40px; } .pet-card { background-color: var(--card-bg); border-radius: 20px; padding: 24px; box-shadow: 0 10px 24px var(--card-shadow); text-align: center; transition: transform 0.3s ease, box-shadow 0.3s ease; } .pet-card:hover { transform: translateY(-4px); } .pet-avatar { font-size: 72px; line-height: 1; margin-bottom: 12px; } .pet-type { display: block; margin: 4px 0 10px; font-size: 14px; opacity: 0.7; } .pet-tags { margin-bottom: 12px; } .tag { display: inline-block; background-color: var(--tag-bg); color: var(--primary); border-radius: 12px; padding: 4px 12px; font-size: 12px; margin: 0 4px 6px; } .pet-desc { font-size: 14px; opacity: 0.85; margin-bottom: 16px; min-height: 40px; } .status-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; font-size: 13px; } .bar { flex: 1; height: 10px; background-color: var(--bar-bg); border-radius: 6px; overflow: hidden; } .bar-inner { height: 100%; border-radius: 6px; transition: width 0.4s ease; } .bar-inner.mood { background: linear-gradient(90deg, #ffb199, #ff8259); } .bar-inner.energy { background: linear-gradient(90deg, #fddb92, #f5a64c); } .actions { display: flex; justify-content: center; gap: 10px; margin-top: 16px; } .btn { padding: 8px 14px; border: none; border-radius: 16px; background-color: var(--primary-light); color: var(--text-color); font-size: 13px; cursor: pointer; transition: background-color 0.3s, transform 0.2s; } .btn:hover { background-color: var(--primary); color: #fff; transform: scale(1.05); } .gallery { margin: 40px 0; } .gallery h2 { text-align: center; margin-bottom: 20px; } .gallery-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; } .gallery-item { background-color: var(--card-bg); border-radius: 16px; padding: 28px 12px; text-align: center; font-size: 18px; box-shadow: 0 6px 16px var(--card-shadow); } .site-footer { text-align: center; padding: 30px 20px; opacity: 0.7; font-size: 14px; } media (max-width: 768px) { .pet-list { grid-template-columns: 1fr; } .gallery-grid { grid-template-columns: 1fr; } }这段 CSS 中值得学习的地方是 CSS 变量Custom Properties的使用。:root中定义的变量在全局可用body.night中重新定义同名变量后所有引用var(--bg-color)、var(--text-color)的地方都会自动使用新值。这种机制比“写死颜色再逐个覆盖”要干净得多。4.4 编写 JavaScript 逻辑script.js承担三类职责维护数据、处理交互、保存状态。完整代码如下// 文件路径script.js const pets [ { id: cat, name: 橘子, avatar: , type: 小橘猫, age: 2岁, desc: 喜欢晒太阳最爱小鱼干。, tags: [慵懒, 黏人], mood: 80, energy: 70, moodChange: { feed: 5, stroke: 8, play: -10 }, energyChange: { feed: 6, stroke: 2, play: -15 } }, { id: dog, name: 布丁, avatar: , type: 小柯基, age: 1岁半, desc: 精力旺盛每天都要出门跑三圈。, tags: [活泼, 贪玩], mood: 70, energy: 90, moodChange: { feed: 4, stroke: 5, play: 12 }, energyChange: { feed: 5, stroke: 1, play: -20 } }, { id: rabbit, name: 棉花糖, avatar: , type: 小白兔, age: 8个月, desc: 胆子很小但熟悉之后特别粘人。, tags: [温柔, 胆小], mood: 60, energy: 55, moodChange: { feed: 5, stroke: 12, play: -6 }, energyChange: { feed: 4, stroke: 3, play: -8 } } ]; function saveData() { localStorage.setItem(cute-three-pets, JSON.stringify(pets)); } function loadData() { const saved localStorage.getItem(cute-three-pets); if (saved) { try { const parsed JSON.parse(saved); if (Array.isArray(parsed) parsed.length pets.length) { parsed.forEach((item, index) { pets[index].mood item.mood; pets[index].energy item.energy; }); } } catch (error) { console.warn(本地数据解析失败使用默认数据, error); } } } function renderCards() { const container document.getElementById(pet-list); container.innerHTML pets.map(pet { return article classpet-card>const MAX_VALUE 100; const MIN_VALUE 0;把最大最小值抽成常量比在代码里直接写100和0更容易维护。当业务规则变化时只需要修改常量定义。6.3 交互反馈要即时按钮点击后数值的变动不应该只在数字文本上体现还要通过颜色、动画、提示文案等方式强化反馈。比如心情值增加时状态条颜色由浅变深数值变化时数字增加一个小幅缩放的动画投喂操作后在卡片角落弹出“小鱼干 1”的小提示。这些细节会让页面更有生命力但实现成本并不高。6.4 善用浏览器存储但要注意容量和权限localStorage 的容量一般是 5MB 左右适合保存小体积的配置项和状态数据不适合存储图片、大文本。同时localStorage 只能存储字符串对象的读写必须经过JSON.stringify和JSON.parse。另外用户如果开启隐私模式或禁用站点数据localStorage 可能不可用所以需要提前用try...catch包住读取操作。6.5 CSS 命名规范示例中使用了类似.pet-card、.status-bar、.bar-inner的命名方式。如果你接手更大的项目可以尝试 BEM 命名法Block Element Modifier例如.pet-card__avatar、.pet-card__action。命名规范的主要价值是让样式结构更清晰方便团队协作和维护。6.6 兼容性与前缀现代浏览器对 CSS 变量、Flex、Grid 的支持度已经很高但如果你需要兼容老旧浏览器可能需要考虑为display: grid提供 flex 回退布局为渐变、border-radius等属性添加-webkit-前缀使用 autoprefixer 等构建工具自动处理。不过在本项目中面向现代浏览器即可不需要过度处理兼容性。7. 扩展方向与总结到这里一个完整的“可爱的三小只~”互动页面已经可以做出来了。它包含了一个前端页面从“设计数据结构”到“渲染视图”再到“处理交互”和“持久化存储”的完整链条。通过这个项目你会更深刻地理解下面几个核心概念数据驱动渲染页面内容是数据的映射事件委托动态元素的统一事件处理思路状态管理数值变化后如何同步视图并持久化主题切换CSS 变量在换肤场景中的实际应用。如果你希望继续深入这里还有几个明确的扩展方向。第一个方向是接入后端。宠物数据不再硬编码在pets数组中而是通过fetch(/api/pets)从后端接口获取。这一步会让你接触到异步编程、接口设计、错误处理等内容对巩固 JavaScript 基础很有帮助。第二个方向是引入前端框架。把这个静态页面迁移到 Vue 或 React 中你会发现之前手动renderCards、手动维护innerHTML的工作框架都帮你完成了。对比起来你会更容易理解“响应式”和“组件化”的意义。第三个方向是给项目增加更多玩法。例如为每只宠物增加“喂食记录”和“成长值”连续喂养 7 天解锁新头像增加宠物对话气泡不同心情状态输出不同文案使用 Canvas 或第三方动画库让宠物做出跑步、飞翔等动作。技术学习的路径从来不是靠背语法而是靠一个又一个可运行、可展示、可分享的小作品往前走。“可爱的三小只~”就是一个很好的起点。如果你在实际搭建过程中遇到了其他问题欢迎对照上面的排查表逐项检查也可以重新阅读对应章节理解每一步为什么这样设计。代码和笔记都放在手边动手多敲几遍收获会更大。
返回列表