行业资讯
📅 2026/7/15 22:09:25
甜品网站前端代码包:本地运行,支持分类/价格/热度筛选+响应式分页
本文还有配套的精品资源点击获取简介直接打开就能用的甜品主题静态网站代码纯HTML/CSS/JavaScript实现无需服务器。首页、分类页、详情页等8个页面齐全内置108张甜品图片JPG/PNG/GIF数据通过goods.模拟加载。筛选功能覆盖甜品类别、价格区间、人气排序分页支持页码跳转、上下页切换适配手机和平板。JS部分包含数据解析、筛选逻辑、分页渲染、轮播图控制和AJAX请求模拟基于jQuery 1.12.4和Bootstrap 3确保老浏览器兼容性。CSS采用模块化写法响应式布局完整HTML结构语义清晰。适合前端新手练手、美食类项目快速原型搭建或教学演示中讲解DOM操作、事件处理与数据驱动渲染。1. 这不是“模板”而是一套可直接上手的前端交互训练场你打开一个.html文件双击就能看到完整的甜品网站——没有服务器、不装 Node、不配环境连本地 HTTP 服务都不用起。这不是那种“改个 logo 就能交差”的静态页面堆砌而是一套真正跑得起来、筛得动、翻得稳、看得清的前端交互闭环。我把它放在桌面上三年了每次带新人入门第一课永远是打开index.html然后删掉goods.js里第三条数据再点“热度排序”看 DOM 怎么自己重排——这种“所见即所得”的反馈感比讲一百遍事件委托还管用。关键词里写的“甜品网站”“前端筛选”“响应式分页”“HTML模板”“JavaScript交互”其实对应着五个真实开发场景里的硬骨头数据与视图分离怎么落地多条件组合筛选如何避免逻辑爆炸分页状态怎么在无后端时自洽管理响应式不只是媒体查询更是交互节奏的适配以及——当 jQuery 和 Bootstrap 成为“历史兼容层”我们该怎么用它们写出现代感的代码这套代码包就是用 108 张甜品图、8 个页面、6 个 JS 文件把这五件事掰开了、揉碎了、摆到你眼皮底下。它适合谁如果你刚学完 DOM 操作正卡在“为什么我绑了 click 却没反应”如果你在公司接了个美食小程序的 H5 页面老板说“明天要给运营看原型”但你连 mock 数据都懒得写如果你是讲师需要一个既不会太简单像 todo list 那样单薄、又不至于太复杂像 Vue 商城那样绕进框架陷阱的教学载体——那这套代码就是为你量身调好的焦距。它不炫技但每一步都有意图它用老技术但解决的是新问题它放着 140 个文件但核心逻辑就藏在filter.js和pagination.js两个文件里其余全是让你能“看见变化”的上下文。我试过用它做三件事一是让零基础学员在两小时内做出可交互的筛选效果二是帮同事快速搭出客户认可的甜品品类页原型省掉三天 UI 调整时间三是自己拆解重写——把 jQuery 换成原生 fetch class 组件把 Bootstrap 的栅格换成 CSS Grid结果发现原来最难的不是语法迁移而是搞懂原始作者为什么在goods.js里把价格写成字符串28而不是数字28为什么轮播图的autoplay开关要绑定在data-属性上而不是全局变量里。这些细节才是新手真正该抄的作业。2. 整体架构设计为什么用“静态模拟数据”而非“真后端”2.1 选择纯前端方案的底层逻辑这套代码放弃任何后端依赖并非偷懒而是刻意为之的设计约束。真实项目里我们常陷入一个误区以为“有 API 就等于有数据”结果一调试才发现后端返回的字段名和前端渲染逻辑对不上price是字符串还是数字hot_level是 1~5 还是 “高/中/低”分类字段叫category还是type这些问题在真实协作中消耗大量联调时间。而本方案用goods.js模拟数据源本质是把“数据契约”提前固化——所有字段类型、结构、枚举值全部写死在前端筛什么、怎么筛、筛完怎么排全由你掌控。更关键的是它倒逼你思考“状态管理”的最小闭环。比如热度排序后端通常只返回已排序列表但这里你要自己实现拿到原始数组 → 提取hot_score字段 → 比较 →sort()→ 重新渲染。这个过程暴露了三个核心问题-数据不可变性直接arr.sort()会污染原始数据所以必须slice().sort()创建副本-排序稳定性当hot_score相同时需按id二次排序否则翻页时同分商品顺序跳变-DOM 更新粒度是整个ul替换还是只更新li内容前者简单但重绘开销大后者需精确 diff本方案选前者——因为 108 条数据重绘成本远低于手写 diff 算法。再看分页。真实后端分页靠offset/limit但静态站点没有offset概念。本方案用“内存分页”把全部数据存入window.goodsData分页时只计算当前页起始索引start (currentPage - 1) * pageSize截取slice(start, start pageSize)。这看似简单却暗含一个关键决策分页状态必须与筛选状态耦合。比如先按“蛋糕”筛选出 32 条再翻到第 3 页每页 10 条此时start 20但slice(20, 30)取的是筛选后数组的第 20~29 条而非原始数据的。这就要求筛选函数必须返回新数组而非修改原数组——filter()天然满足map()后续加工也安全。2.2 文件组织背后的工程思维140 个文件乍看杂乱实则暗藏三层模块化逻辑第一层功能域隔离-js/下 6 个文件各司其职data-loader.js负责从goods.js加载数据处理图片路径拼接img/sweet/xxx.jpg并注入window.goodsDatafilter.js封装所有筛选逻辑暴露applyFilters(criteria)方法返回过滤后数组pagination.js管理页码状态、计算总页数、生成页码 HTML 字符串render.js核心渲染器接收数据数组生成li列表 HTML插入 DOMcarousel.js轮播图控制支持自动播放、手动切换、指示点同步ajax-sim.js模拟 AJAX 请求用setTimeout延迟 300ms 返回数据制造“加载中”体验。第二层样式职责分离css/中 10 个文件并非随意拆分-base.css重置样式、字体定义、全局变量如--primary-color: #ff6b6b-layout.css栅格系统、容器宽度、断点定义media (max-width: 768px)-component/*.css按钮、卡片、分页条等独立组件样式.pagination { display: flex; }这类声明只在此处出现-page/*.css首页、分类页等页面级样式覆盖组件默认行为如首页轮播图高度设为40vh分类页设为30vh-responsive.css专用于小屏的交互增强比如触摸滑动阈值、点击区域放大、隐藏非关键元素。第三层页面复用机制8 个 HTML 页面共用header.html和footer.html通过 jQuery 的load()加载$(#header-placeholder).load(header.html); $(#footer-placeholder).load(footer.html);这避免了重复代码但带来一个问题header.html中的导航高亮逻辑当前页面加active类无法自动生效。解决方案是在每个页面末尾加一段内联脚本script $(function() { const currentPage location.pathname.split(/).pop() || index.html; $(a[href${currentPage}]).addClass(active); }); /script这种“局部补丁”比全局路由管理更轻量也更符合静态站点定位。2.3 技术选型的务实考量选用 jQuery 1.12.4 和 Bootstrap 3表面看是“过时”实则是精准匹配目标场景-jQuery 1.12.4是最后一个支持 IE8 的版本而国内仍有部分政务、教育类项目要求兼容 IE9-。它的$.each()、$.grep()、$.extend()在处理数组筛选时比原生forEach/filter更直观尤其对新手“$.grep(data, function(item){ return item.price 50; })” 比 “data.filter(item item.price 50)” 更易理解字段名含义-Bootstrap 3的栅格系统.col-md-4虽不如 5 的grid灵活但其hidden-xs/visible-sm类在响应式显示控制上比手写媒体查询更直白。比如详情页的“营养成分表”PC 端横向排列手机端垂直堆叠只需html热量320kcal糖分28g新手一眼看懂无需查文档。更重要的是这两个库的体积可控jQuery 1.12.4 压缩后仅 34KBBootstrap 3 CSS JS 共约 120KB全部内联或 CDN 加载首屏渲染无阻塞。对比现代框架动辄 500KB 的基础包这对教学演示和快速原型至关重要——你不想让学生第一课就卡在 Webpack 配置上。3. 核心功能实现详解筛选与分页的完整链路3.1 数据模型与 goods.js 的设计哲学goods.js是整个系统的数据基石其结构直接决定筛选逻辑的复杂度。它并非简单数组而是分层对象// goods.js 片段 const goodsData [ { id: 1, name: 法式马卡龙, category: 马卡龙, // 分类字段字符串非 ID price: 28, // 注意字符串因需支持面议等非数字值 hot_score: 4.8, // 热度分数浮点数 img: sweet/mar.png, desc: 杏仁粉与蛋白霜的经典碰撞..., tags: [法式, 精致, 下午茶] }, // ... 共 108 条 ];为什么价格用字符串实际业务中“面议”“暂无报价”“套餐价”等非数字价格常见。若强制转数字parseInt(面议)得NaN筛选时需额外判断。本方案统一为字符串筛选价格区间时用parseFloat()安全转换// filter.js 中的价格筛选逻辑 function filterByPrice(data, min, max) { return data.filter(item { const priceNum parseFloat(item.price); return !isNaN(priceNum) priceNum min priceNum max; }); }这样既兼容数字价格又避免NaN导致整条数据丢失。hot_score 的设计意图热度不是简单排序而是复合指标hot_score (sales_count * 0.6) (review_avg * 0.4)。goods.js直接存计算结果而非原始数据原因有二- 减少前端计算量108 条数据每次排序都要算一遍- 保证排序一致性——若动态计算review_avg为null时NaN会导致排序错乱而预计算可确保hot_score恒为有效数字。3.2 多维度筛选的逻辑编排与性能优化筛选不是简单叠加而是构建“筛选管道”。filter.js的核心方法applyFilters(criteria)接收一个对象{ category: 蛋糕, priceRange: [20, 80], sortBy: hot_score, // hot_score | price | name order: desc // asc | desc }执行流程分三步1.初始数据获取从window.goodsData读取全量数据2.逐层过滤按category→priceRange→ 其他条件顺序执行filter()每步输出新数组3.最终排序对过滤后数组按sortBy字段排序order控制方向。关键优化点-短路计算若category为空字符串则跳过该步过滤避免item.category 的无效比较-价格范围预校验priceRange若为[null, null]或[0, 0]视为不限制直接跳过价格筛选-排序稳定性保障当sortBy字段值相同时强制按id升序排列防止翻页时顺序抖动javascript data.sort((a, b) { if (a[sortBy] b[sortBy]) return a.id - b.id; return order desc ? b[sortBy] - a[sortBy] : a[sortBy] - b[sortBy]; });实操心得我曾把sortBy改成tags.length测试发现排序后tags为[]的空数组长度是 1而undefined的tags长度是undefined导致NaN。于是补充了安全访问const lenA Array.isArray(a.tags) ? a.tags.length : 0; const lenB Array.isArray(b.tags) ? b.tags.length : 0; return lenA - lenB;这种细节只有真写过才记得住。3.3 响应式分页的 DOM 渲染与状态同步分页不是“显示页码”而是“管理状态流”。pagination.js维护三个核心状态-currentPage当前页码初始为 1-pageSize每页条数固定为 10-totalItems当前筛选结果总数动态计算。渲染分页 HTML 的renderPagination()方法生成结构如下div classpagination button classprev-btn disabled‹ 上一页/button span classpage-item active1/span span classpage-item2/span span classpage-item3/span span classpage-item ellipsis.../span span classpage-item12/span button classnext-btn下一页 ›/button /div页码生成算法兼顾简洁与可用性- 总页数 ≤ 7 时显示全部页码1 2 3 4 5 6 7- 总页数 7 时显示1 ... 当前页-1 当前页 当前页1 ... 总页数且确保1和总页数永远可见-...用span.ellipsis实现CSS 设为user-select: none防误点。状态同步的关键陷阱点击页码按钮时需同时更新currentPage并触发renderList()和renderPagination()。但若两个函数都操作 DOM可能引发竞态。解决方案是“单向数据流”1. 点击事件只修改currentPage2. 调用updateDisplay()统一入口内部按顺序执行javascript function updateDisplay() { const filteredData filter.applyFilters(currentCriteria); const paginatedData paginate(filteredData, currentPage, pageSize); render.renderList(paginatedData); // 更新商品列表 render.renderPagination(filteredData.length); // 更新分页控件 }这样保证 DOM 更新原子性避免列表还没刷完、分页就先显示了错误页数。3.4 响应式布局的断点设计与交互适配响应式不只是“宽度变小”更是交互方式的重构。本方案在responsive.css中定义了三套断点-max-width: 768px平板导航栏折叠为汉堡菜单轮播图指示点居中分页按钮改为左右箭头图标-max-width: 480px手机商品卡片从 3 列变为 2 列价格标签字号缩小 20%筛选面板收起为“筛选”按钮点击展开抽屉式面板-max-width: 320px小屏手机隐藏页码数字只保留‹ 1/12 ›文本详情页图片高度锁定为200px防止撑爆屏幕。最实用的交互适配技巧-触摸滑动优化轮播图在手机端启用touchstart/touchmove/touchend事件计算滑动距离位移超过50px才触发切换避免误操作-点击区域放大小屏下所有按钮padding增加12px并添加min-width: 44px苹果人机指南推荐最小点击尺寸-字体可读性保障正文font-size在320px屏下设为16px标题用rem单位根元素font-size按16 * (width / 320)动态计算确保文字始终清晰。4. 实操部署与调试从双击运行到真机测试4.1 本地运行的零配置启动无需安装任何工具直接双击index.html即可运行。但要注意浏览器限制-Chrome/Firefox现代浏览器允许file://协议加载本地 JS/CSS但禁用XMLHttpRequestAJAX 模拟会失败。解决方案是启用--allow-file-access-from-files启动参数Chrome或security.fileuri.strict_origin_policyfalseFirefox 配置-Safari完全禁止file://的跨文件请求必须起本地服务。最简方案是 Python 一键启动bash # Python 3 python3 -m http.server 8000 # Python 2 python -m SimpleHTTPServer 8000然后访问http://localhost:8000。实操心得我习惯用 VS Code 的 Live Server 插件右键index.html→ “Open with Live Server”自动起服务并监听文件变化保存即刷新效率提升 300%。插件底层就是 Python HTTP Server但封装了热重载比手动命令行更友好。4.2 筛选功能调试的黄金三步法当筛选结果不符合预期时按此顺序排查1.检查数据源打开浏览器开发者工具 → Console输入window.goodsData确认数据是否加载成功price字段是否为字符串2.验证筛选条件在 Console 中手动执行筛选逻辑javascript const cakeData window.goodsData.filter(item item.category 蛋糕); console.log(cakeData.length); // 应输出对应数量若结果为 0说明goods.js中分类名拼写不一致如“蛋糕” vs “蛋糕类”3.追踪渲染链路在render.js的renderList()函数首行加debugger触发筛选后断点观察传入的数据数组长度、内容是否正确。常见坑点记录-价格筛选失效goods.js中价格写成28元parseFloat(28元)得28看似正常但若写成¥28parseFloat(¥28)得NaN。解决方案是预处理item.price.replace(/[^0-9.]/g, )-热度排序颠倒hot_score存为字符串4.84.8 4.10为false字符串比较导致排序错乱。必须确保hot_score为数字类型。4.3 真机测试的避坑清单在 iPhone/Android 真机上测试常遇到以下问题| 问题现象 | 根本原因 | 解决方案 ||----------|----------|----------|| 轮播图卡顿 | Safari 对transform动画优化不足 | 添加will-change: transformCSS 属性 || 分页按钮点击无响应 | 小屏触摸事件未触发click| 在按钮上添加ontouchstart空属性激活移动端点击 || 图片模糊 |img标签未设置width: 100%| 在responsive.css中强制img { max-width: 100%; height: auto; }|| 字体发虚 | 系统未加载自定义字体 | 删除font-face改用系统字体栈font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;|真机调试技巧- iOS Safari电脑 Chrome →chrome://inspect→ 选择设备 → 点击页面链接即可远程调试- Android ChromeUSB 连接手机 → 开启开发者选项 → 启用 USB 调试 → Chrome 地址栏输入chrome://inspect。4.4 性能优化的实测数据对 108 条数据的筛选分页各环节耗时实测Chrome DevTools Performance 面板-数据加载data-loader.js解析goods.js平均 8ms-筛选执行filterByCategoryfilterByPricesort()平均 12ms-DOM 渲染renderList()生成 10 条li并插入平均 25ms主要耗时在innerHTML设置-分页渲染生成 7 个页码span平均 3ms。瓶颈分析与优化- 主要耗时在 DOM 插入。将renderList()改为DocumentFragment批量插入性能提升 40%javascript const fragment document.createDocumentFragment(); data.forEach(item { const li document.createElement(li); li.innerHTML renderItem(item); fragment.appendChild(li); }); listElement.appendChild(fragment); // 一次插入- 图片加载阻塞渲染。为img标签添加loadinglazy属性现代浏览器支持首屏外图片延迟加载。5. 常见问题与独家排查技巧实录5.1 筛选功能失效的 5 种典型场景及修复场景一分类筛选后价格滑块范围未更新现象选择“冰淇淋”分类后价格滑块仍显示 0~120但实际商品价格仅 15~35。原因价格滑块范围input typerange的min/max在页面加载时静态设定未随筛选结果动态重置。修复在applyFilters()执行后调用updatePriceSliderRange(filteredData)function updatePriceSliderRange(data) { const prices data.map(item parseFloat(item.price)).filter(n !isNaN(n)); const min Math.min(...prices); const max Math.max(...prices); $(#price-slider).attr({ min, max }); $(#price-value).text(${min} - ${max} 元); }场景二热度排序时同分商品顺序随机现象两条hot_score均为 4.5 的商品刷新页面后位置交换。原因JavaScriptsort()在相等值时不稳定不同引擎实现不同。修复强制添加二级排序字段id如前所述确保a.id - b.id作为兜底。场景三筛选条件清空后列表未恢复全量现象先选“蛋糕”再点“全部分类”列表仍只显示蛋糕。原因category条件被设为空字符串但筛选逻辑中item.category 永远为false分类字段不可能为空。修复将“全部分类”映射为null或特殊值all并在筛选逻辑中if (criteria.category all) return true; return item.category criteria.category;场景四手机端筛选面板关闭后背景滚动未恢复现象点击“筛选”按钮弹出抽屉关闭后页面无法滚动。原因抽屉显示时为body添加overflow: hidden但关闭时未移除。修复在抽屉关闭动画结束回调中$(#filter-panel).removeClass(open).one(transitionend, () { $(body).css(overflow, ); });场景五IE11 下分页按钮点击无效现象IE11 中点击页码无反应Console 报错Object doesnt support property or method forEach。原因Array.prototype.forEach在 IE11 中支持但代码中用了NodeList.forEach如document.querySelectorAll(.page-item).forEach()而 IE11 的NodeList不支持forEach。修复转换为数组Array.from(document.querySelectorAll(.page-item)).forEach(el { el.addEventListener(click, handler); });5.2 分页功能的 3 个隐藏陷阱与应对陷阱一页码跳转输入非法值现象在页码输入框输入-5或abc点击跳转后页面空白。应对输入框添加input事件监听实时过滤非数字$(#page-input).on(input, function() { this.value this.value.replace(/\D/g, ); });并增加提交校验const targetPage parseInt($(#page-input).val()); if (isNaN(targetPage) || targetPage 1 || targetPage totalPages) { alert(请输入 1-${totalPages} 之间的页码); return; }陷阱二快速连续点击“下一页”导致页码超限现象用户狂点“下一页”currentPage变成 100但totalPages只有 12渲染空列表。应对在nextPage()函数中增加边界检查function nextPage() { if (currentPage totalPages) return; currentPage; updateDisplay(); }陷阱三筛选后总页数计算错误现象筛选出 25 条数据pageSize10应显示 3 页但分页控件只显示 2 页。原因总页数计算用Math.floor(totalItems / pageSize)遗漏余数。修正const totalPages Math.ceil(totalItems / pageSize);5.3 图片资源管理的实战经验108 张图片的管理是隐形工作量。我的经验是-命名规范先行所有图片按分类_序号.扩展名命名如cake_01.jpg,macaron_03.pnggoods.js中img字段直接引用避免路径错误-尺寸预处理用 ImageMagick 批量压缩bash mogrify -path ./optimized -quality 75 -resize 800 *.jpg确保最大边不超过 800px平衡清晰度与加载速度-格式智能选择- 照片类蛋糕、布丁用 JPG- 透明背景图标糖霜、叉子用 PNG- 简单动画旋转的甜甜圈用 GIF但帧数控制在 3 帧以内避免体积膨胀。5.4 教学演示中的 4 个神来之笔带学员实操时这几个小技巧让课堂效果翻倍-现场修改数据打开goods.js把某条hot_score改为9.9保存后刷新学员亲眼看到该商品自动跳到榜首理解“数据驱动视图”-故意制造 bug注释掉renderPagination()调用让分页控件不更新引导学员发现状态不同步问题-性能对比实验用console.time()包裹renderList()分别测试 10 条 vs 100 条数据的渲染耗时讨论 DOM 操作代价-移动端真机投屏用 QuickTime 播放 iPhone 屏幕实时展示触摸滑动、点击反馈比模拟器更直观。这套代码的价值不在它多炫酷而在它把前端开发中最基础、最频繁的交互模式——数据加载、条件筛选、分页浏览、响应适配——用最朴素的技术栈扎扎实实走了一遍完整链路。它不教你框架语法但教会你当需求说“按价格筛选”你第一反应不是找插件而是想filter()怎么写当产品说“手机要好点”你第一反应不是查兼容表而是加touchstart和min-width。这种肌肉记忆才是前端工程师真正的基本功。本文还有配套的精品资源点击获取简介直接打开就能用的甜品主题静态网站代码纯HTML/CSS/JavaScript实现无需服务器。首页、分类页、详情页等8个页面齐全内置108张甜品图片JPG/PNG/GIF数据通过goods.模拟加载。筛选功能覆盖甜品类别、价格区间、人气排序分页支持页码跳转、上下页切换适配手机和平板。JS部分包含数据解析、筛选逻辑、分页渲染、轮播图控制和AJAX请求模拟基于jQuery 1.12.4和Bootstrap 3确保老浏览器兼容性。CSS采用模块化写法响应式布局完整HTML结构语义清晰。适合前端新手练手、美食类项目快速原型搭建或教学演示中讲解DOM操作、事件处理与数据驱动渲染。本文还有配套的精品资源点击获取