☰
极客商店:CSS自定义属性与组件化商品卡片实战
2026/10/14 6:01:31 网站建设 项目流程

简介:「极客商店」是一份以 CSS 为核心的前端电商项目源码包,适合正在学习网页样式与布局的初中级前端开发者,也适合作为课程设计或练手项目的参考。压缩包共 58 个文件,约 9.98MB,包含 10 个 css 样式文件、11 个 js 脚本、12 个 py 后端文件、2 个 html 页面,以及 map、png、jpg 等资源与说明文档,覆盖样式、交互、模板与后端逻辑的完整结构。项目围绕 CSS 基础语法、盒模型、选择器优先级、Flexbox 与 Grid 布局、媒体查询响应式设计、过渡与关键帧动画、CSS 变量与模块化等知识点展开,并涉及 Sass/Less 预处理器与 PostCSS 后处理思路。已有 174 人学习,读者可借此理解样式组织方式、目录模块划分与前后端配合逻辑,积累可复用的布局与动画写法,提升页面美观度与可维护性。

1. 极客商店:一个用 CSS 把商品页做成“可拆解零件”的前端练习场

如果你写过电商列表页,大概率经历过这种局面:设计稿里卡片圆角、阴影、悬停位移都挺好看,真到代码里,一个box-shadow改三遍,hover效果在移动端又失效,最后只能靠!important硬压。极客商店这个资源,就是冲着这类问题来的——它把商品卡片、价格标签、徽章、评分条、购物车按钮这些高频模块,全部拆成独立的 CSS 组件,每个组件只依赖少量自定义属性,不绑框架、不绑构建工具,直接引入就能跑。

它适合两类人:一是刚学完 CSS 选择器和盒模型,想找一个完整页面练手的新手;二是已经在做后台系统或中台页面,需要一套轻量、可复制、不引入额外依赖的样式参考的熟手。资源本身不包含后端逻辑,也不涉及任何真实交易流程,纯粹是前端样式与布局的实战拆解。你拿到手之后,最直接的用法是把它当成一个“样式零件库”,按需摘取,而不是整站照搬。

2. 拆开极客商店的 CSS 结构:从自定义属性到组件边界

2.1 为什么用自定义属性而不是 Sass 变量

很多教程一上来就推 Sass,理由是变量、嵌套、混入方便。但极客商店这个资源走的是另一条路:它把颜色、间距、圆角、阴影全部收敛到:root下的自定义属性里,组件内部只引用var(--xxx)。这样做的好处是,你不需要任何预处理器就能在浏览器里直接改主题,调试时打开 DevTools 改一个值,整站相关样式立刻响应。

常见做法是给每个组件单独定义一套变量,但极客商店把变量分成两层:全局层(--color-primary、--space-unit、--radius-base)和组件层(--card-padding、--badge-bg)。组件层默认继承全局层,需要局部覆盖时只改组件层,不会污染其他模块。下面这段是资源里主题变量的典型写法:

:root { /* 全局层:整站基础尺度 */ --color-primary: #2f6fed; --color-text: #1f2329; --color-muted: #6b7280; --space-unit: 8px; --radius-base: 10px; --shadow-card: 0 2px 8px rgba(0, 0, 0, 0.06); /* 组件层:商品卡片专用,默认继承全局 */ --card-padding: calc(var(--space-unit) * 2); --card-radius: var(--radius-base); --card-shadow: var(--shadow-card); }

逻辑说明:--space-unit设为 8px 是为了让所有间距都是 8 的倍数,避免出现 7px、13px 这种随手写的值。calc()在这里不是炫技,而是让组件层变量能跟随全局层变化——你把--space-unit改成 10px,卡片内边距自动变成 20px。参数怎么改:如果项目设计稿用的是 4px 基准,直接把--space-unit改成 4px,所有依赖它的组件会整体缩放,不需要逐个文件搜索替换。

2.2 商品卡片的布局:Grid 还是 Flex

极客商店的商品卡片内部结构是:图片区、标题、价格行、评分行、操作按钮。资源里用的是 Grid 做主布局,Flex 做行内对齐。为什么不全用 Flex?因为卡片内部的行与行之间需要稳定的垂直节奏,Grid 的grid-template-rows能显式控制每一行的高度分配,而 Flex 在内容多少不一致时容易出现高度跳动。

.product-card { display: grid; grid-template-rows: auto 1fr auto auto; gap: calc(var(--space-unit) * 1.5); padding: var(--card-padding); border-radius: var(--card-radius); box-shadow: var(--card-shadow); background: #fff; transition: box-shadow 0.2s ease, transform 0.2s ease; } .product-card:hover { box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1); transform: translateY(-2px); }

逻辑说明:grid-template-rows: auto 1fr auto auto对应图片区、标题区、价格行、按钮行。标题区用1fr是为了让不同长度的标题把卡片撑到统一高度,避免同一行卡片参差不齐。gap用calc()而不是固定值,是为了跟随全局间距缩放。参数怎么改:如果卡片不需要悬停位移,把transform那行删掉即可;如果希望悬停更明显,把translateY(-2px)改成-4px,但要注意移动端没有 hover,资源里对@media (hover: hover)做了包裹,后面避坑章节会细说。

2.3 价格标签与徽章的层叠关系

价格标签和“新品”“热卖”徽章经常叠在图片右上角或左下角。极客商店的做法是给图片容器加position: relative,徽章用position: absolute定位,价格标签则放在正常文档流里,不参与绝对定位。这样做的原因是:徽章是装饰性信息,脱离文档流不影响布局;价格是核心信息,必须保证在任何屏幕宽度下都能被读到,不能因为绝对定位被裁切。

.card-media { position: relative; overflow: hidden; border-radius: calc(var(--card-radius) - 4px); } .card-badge { position: absolute; top: calc(var(--space-unit) * 1); left: calc(var(--space-unit) * 1); padding: 2px 8px; font-size: 12px; border-radius: 999px; background: var(--color-primary); color: #fff; } .card-price { font-size: 18px; font-weight: 600; color: var(--color-text); }

逻辑说明:overflow: hidden加在图片容器上,是为了让图片缩放动画或圆角不溢出。徽章用border-radius: 999px而不是固定值,是为了在文字长度变化时保持胶囊形状。参数怎么改:徽章位置想换到右上角,把left改成right即可;价格字号想跟随全局缩放,可以改成calc(var(--space-unit) * 2.25),但要注意可读性下限,不建议低于 14px。

2.4 评分条的两种实现:字符 vs 伪元素

评分条在资源里给了两套写法。一套是用 Unicode 字符直接拼,简单但不可控;另一套是用伪元素加width百分比,适合半星场景。极客商店默认用第二套,因为商品评分经常出现 4.5、3.8 这种非整数。

.rating { --rating-percent: 80%; /* 4 星 = 80% */ display: inline-block; position: relative; font-size: 14px; line-height: 1; color: #d1d5db; } .rating::before { content: "★★★★★"; letter-spacing: 2px; } .rating::after { content: "★★★★★"; letter-spacing: 2px; position: absolute; left: 0; top: 0; width: var(--rating-percent); overflow: hidden; color: #f59e0b; white-space: nowrap; }

逻辑说明:底层::before是灰色满星,上层::after是金色星,通过width控制显示比例。white-space: nowrap防止星星换行。参数怎么改:把--rating-percent改成45%就是 2.25 星的效果;如果设计稿要求半星对齐,可以把百分比取整到 10% 的倍数。注意这套写法依赖字体渲染,不同系统下星星间距可能略有差异,后面避坑章节会讲怎么处理。

3. 把极客商店跑起来:从文件引入到页面组装的完整步骤

3.1 目录结构与引入顺序

资源解压后,CSS 部分通常按base/、components/、utilities/三层组织。base/放重置和全局变量,components/放卡片、徽章、按钮、评分条,utilities/放间距、文字截断这类原子类。引入顺序必须是 base → components → utilities,否则组件样式会被原子类覆盖,或者变量还没定义就被引用。

<!-- 引入顺序不能乱:先变量,再组件,最后工具类 --> <link rel="stylesheet" href="./css/base/variables.css"> <link rel="stylesheet" href="./css/base/reset.css"> <link rel="stylesheet" href="./css/components/product-card.css"> <link rel="stylesheet" href="./css/components/badge.css"> <link rel="stylesheet" href="./css/components/rating.css"> <link rel="stylesheet" href="./css/utilities/spacing.css">

逻辑说明:variables.css必须在最前,因为后面所有文件都依赖var(--xxx)。reset.css放在变量之后、组件之前,是为了先清掉浏览器默认样式,再让组件在干净的基础上叠加。参数怎么改:如果你只想用卡片组件,可以只引入variables.css、reset.css和product-card.css,但要注意卡片里引用了--color-primary等变量,缺一个就会回退到浏览器默认值,颜色会变得很奇怪。

3.2 组装一个商品列表页

资源里没有提供完整的 HTML 页面,但给了组件的类名约定。下面是一个最小可用的列表页骨架,直接复制就能在浏览器里看到效果:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>极客商店 - 商品列表</title> <link rel="stylesheet" href="./css/base/variables.css"> <link rel="stylesheet" href="./css/base/reset.css"> <link rel="stylesheet" href="./css/components/product-card.css"> <link rel="stylesheet" href="./css/components/badge.css"> <link rel="stylesheet" href="./css/components/rating.css"> </head> <body> <main class="product-grid"> <article class="product-card"> <div class="card-media"> <img src="./images/demo-1.jpg" alt="商品图"> <span class="card-badge">新品</span> </div> <h3 class="card-title">模拟商品 A</h3> <div class="card-price">¥ 199.00</div> <div class="rating" style="--rating-percent: 80%;"></div> <button class="card-action">加入购物车</button> </article> </main> </body> </html>

逻辑说明:product-grid是外层容器,资源里用 Grid 做响应式列数控制,默认两列,窄屏一列。style="--rating-percent: 80%"是行内覆盖组件层变量,这是自定义属性的标准用法,不需要额外类名。参数怎么改:把--rating-percent改成60%就是 3 星;把card-badge的文本换成“热卖”即可复用同一套样式。

3.3 响应式断点与容器查询的取舍

极客商店默认用媒体查询做断点,断点值放在variables.css里统一管理。资源没有用容器查询,原因是容器查询在部分旧版浏览器里支持不完整,而商品列表页的布局变化主要跟视口宽度相关,媒体查询足够。如果你所在的项目已经全面转向现代浏览器,可以把卡片内部的布局改成容器查询,让卡片在侧边栏和主区域都能自适应。

/* 媒体查询:视口宽度决定列数 */ .product-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: calc(var(--space-unit) * 2); } @media (min-width: 768px) { .product-grid { grid-template-columns: repeat(3, 1fr); } } @media (min-width: 1200px) { .product-grid { grid-template-columns: repeat(4, 1fr); } }

逻辑说明:移动优先,默认两列,768px 以上三列,1200px 以上四列。gap用全局间距变量,保证列表间距和卡片内边距节奏一致。参数怎么改:如果设计稿要求 1024px 就切四列,把第二个断点改成 1024px 即可;如果希望列数更平滑,可以改用repeat(auto-fill, minmax(240px, 1fr)),但要注意这种写法在极端窄屏下可能出现单列过宽的问题。

4. 避坑与排查:极客商店样式落地时最容易翻车的五个点

4.1 悬停位移在触屏设备上“粘住”

现象:在手机上点一下商品卡片,卡片会往上跳一下然后卡住,再点其他地方才恢复。原因:transform: translateY(-2px)绑在:hover上,触屏浏览器会把第一次点击模拟成 hover,导致样式被激活后无法自动取消。解决:把悬停效果包在@media (hover: hover)里,只对真正支持悬停的设备生效。

@media (hover: hover) { .product-card:hover { transform: translateY(-2px); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1); } }

4.2 自定义属性在旧版浏览器里“失效”

现象:页面颜色、间距全部乱掉,打开 DevTools 看到var(--color-primary)被划掉。原因:自定义属性在部分旧版内核里不支持,或者你引入的 CSS 文件顺序不对,变量还没定义就被引用。解决:先确认variables.css在最前面;如果必须兼容旧版,可以在组件里写一个回退值,比如color: var(--color-primary, #2f6fed);,这样变量失效时还能用默认色。

4.3 评分条星星在不同系统下宽度不一致

现象:同一段评分条代码,在某个系统上显示 4 星,换一台机器变成 3.8 星。原因:::after的width是百分比,但星星字符的渲染宽度受字体影响,不同系统对★的字宽处理不同。解决:给评分条容器一个固定font-family,比如font-family: "Segoe UI Symbol", "Apple Symbols", sans-serif;,或者改用 SVG 图标替代字符。资源里默认用字符是为了零依赖,如果你对一致性要求高,换成内联 SVG 更稳。

4.4 卡片高度不齐导致“参差感”

现象:同一行卡片,有的高有的矮,底部按钮不在一条水平线上。原因:标题长度不一致,而卡片没有用 Grid 的1fr撑开标题区,或者外层列表用了 Flex 但没有设置align-items: stretch。解决:确认.product-card的grid-template-rows里标题行是1fr;如果外层是 Flex,给列表容器加align-items: stretch,并确保卡片本身height: 100%。

4.5 图片溢出圆角容器

现象:商品图四个角是直角,把卡片的圆角盖住了。原因:图片没有设置display: block,或者图片容器的overflow: hidden被其他样式覆盖。解决:给.card-media img加display: block; width: 100%; height: auto;,并确认.card-media的overflow: hidden没有被后续样式覆盖。如果图片本身有白边,可以在容器上加background: #f5f5f5;让留白看起来更自然。

5. 进阶用法:把极客商店的变量体系接到设计令牌上

资源本身是一套 CSS,但它的变量命名和分层方式,可以直接映射到设计令牌(Design Token)体系。我一般会做一件事:把variables.css里的全局层变量抽成一个 JSON 文件,交给设计侧维护,构建时再生成 CSS 自定义属性。这样设计改一个色值,前端不需要手动同步,构建产物自动更新。下面是一个最小转换脚本的思路:

// tokens.json -> variables.css 的简化转换 const tokens = { color: { primary: "#2f6fed", text: "#1f2329" }, space: { unit: "8px" }, radius: { base: "10px" } }; function flatten(obj, prefix = "") { return Object.entries(obj).flatMap(([key, value]) => { const name = prefix ? `${prefix}-${key}` : key; return typeof value === "object" ? flatten(value, name) : [` --${name}: ${value};`]; }); } const css = `:root {\n${flatten(tokens).join("\n")}\n}`; console.log(css);

逻辑说明:flatten递归把嵌套对象拍平成--color-primary这样的变量名,输出到:root里。参数怎么改:如果你的令牌命名用下划线而不是短横线,把模板字符串里的-换成_即可;如果设计侧用的是其他格式,只需要改tokens对象的读取方式,后面的生成逻辑不用动。

验证方法也很直接:改tokens.json里的color.primary,重新跑一遍脚本,刷新页面看按钮和徽章颜色是否同步变化。如果没变,先检查生成的 CSS 是否被正确引入,再检查浏览器 DevTools 里--color-primary的计算值是不是新值。我自己的习惯是,每次接入新项目前,先拿极客商店的卡片组件跑一遍这个流程,确认变量链路通了,再往里加业务样式。从那以后我每次改主题色都强制走一遍令牌生成,不再手动搜替换。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询