ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

纯CSS实现扑克牌扇形展开动画,实战掌握盒模型与Flex布局

2026/9/30 10:05:35 拓冰建站 浏览量
纯CSS实现扑克牌扇形展开动画,实战掌握盒模型与Flex布局 很多人学 HTML 和 CSS 最大的问题不是资料少而是缺少一个能把知识点串起来的练手项目。看一百遍盒模型不如亲手调一次 margin背十遍 flex 属性不如自己做一次三列布局。扑克牌这个选题我特别喜欢因为一张牌看起来简单但真要做到“像那么回事”宽度、间距、圆角、阴影、字体、定位、层级全都要照顾到做一遍下来比刷十个静态页面都有用。我这次做的是“小胡桃展开特教”主题牌组名字里的小胡桃不是某个平台专用的组件而是我给这个牌组设定的一个卡通形象你可以理解成一套带着小胡桃头像的定制扑克。整项目的核心有两个第一用纯 HTMLCSS 做出一组从牌面到牌背都完整的扑克牌第二鼠标移上去以后牌组从叠放状态慢慢扇形展开像手握着一副牌摊开给人看。整个过程不写一行 JavaScript全部靠 CSS 的 transform、transition 和 hover 来完成。这个项目适合两类人。一类是刚学完 HTML 和 CSS 基础语法、想做点有反馈感作品的前端入门者另一类是教网页开发的老师需要给学生找一个能覆盖大量知识点的小案例。跟着做完你不仅会得到一组能拿出手的牌组页面还会把文档骨架、盒模型、定位、渐变、伪元素、过渡动画这些零散概念一次性串起来。1. 项目拆解一张扑克牌里藏了哪些CSS硬知识1.1 一张牌拆开看四个区域与一个牌背在写代码之前我先说一下这张牌到底由哪些部分组成。一张常见的扑克牌正面上看其实可以拆成四个信息层左上角和右下角的点数标记、中间的图案区、整张牌的底色边界。左上角和右下角的标记是对角呼应的右上角的数字要旋转 180 度这样别人从对面看牌时方向才是正的。牌背就更直接了一整块矩形区域里填充花纹或重复图案。做牌背在 CSS 里很适合练 background 的重复渐变和背景定位一张对称的花纹可以通过多层 background 叠加实现不用切图。小胡桃展开这个交互可以拆分得更细牌组先重叠在一个支点上当鼠标悬停时每张牌围绕同一个中心点旋转不同角度同时往两侧散开形成扇形。散开以后每张牌的边缘要保留一点压叠关系这又涉及 z-index 的层级调整。所以这个项目表面上是“做几张牌”实际上覆盖了 HTML 结构、CSS 盒模型、背景、定位、层级、过渡、动画七个大块。1.2 为什么这个选题适合入门和进阶常见的 HTMLCSS 练习页无非是个人名片、企业官网、博客布局这类页面很容易做成“堆方块”做完以后对布局的理解有提升但对视觉细节的敏感度训练不足。扑克牌不一样它的尺寸是固定的信息密度是确定的你需要在一个很小的空间里把文字、图形、留白都安排明白这对细节的敏感度要求非常高。举个例子一张标准扑克牌的长宽比大约是 2.5:3.5也就是 5:7。你在页面上如果随便给一个 div 设 200px 宽、280px 高肉眼看起来会有点“胖”因为真实牌更窄长。这种比例感在文档流里很难察觉但一旦多张牌扇形展开比例不对立刻就会显得廉价。所以这个项目对新手友好但对细节的要求会让新手第一次意识到“原来 CSS 不是属性堆上去就完事”。对有经验的人来说纯 CSS 画出牌背花纹、用 transform-origin 控制旋转轴心这些技巧也能作为教案素材复用。1.3 方案选型全CSS图形还是切图素材做牌面图案有两种思路。一种是用图片素材比如直接下载一套扑克牌图标用 img 标签放进去另一种是用纯 CSS 绘制花色字符用文字背景用渐变小胡桃头像用圆角加伪元素拼出来。我强烈建议练手时选择纯 CSS 方案。原因有三点第一纯 CSS 方案不依赖外部资源文件随便拷到哪里都能打开不会被图片路径问题卡住第二绘制过程本身就是练习比如画红桃就会用到文字居中、字体大小、颜色画牌背就会用到 repeating-linear-gradient这些比图片素材带给你更多的练习量第三纯 CSS 方案加载更快也不会因为素材版权问题不好展示。用文字字符画花色有一个现成的优势HTML 里有 ♠ ♣ ♥ ♦ 这几个实体字符直接放进标签里就能显示不需要任何额外图片。虽然不同操作系统里字形渲染有差异但作为练习完全够用。小胡桃的头像我用的是一组嵌套 div 加伪元素来拼圆形的脸部、半圆的耳朵、两个点状眼睛所有角度都用 border-radius 控制这部分我后面会贴代码。2. 先把地基打好HTML5骨架与CSS的三种引用方式2.1 标准骨架逐行拆解不管做什么页面标准 HTML5 骨架都不能省。很多刚入门的朋友图省事直接把内容写在 body 里浏览器也能显示但后续加样式、加脚本、做调试就会到处踩坑。!DOCTYPE html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title小胡桃展开特教 - HTML CSS 扑克牌/title link relstylesheet hrefstyle.css /head body !-- 页面内容写在这里 -- /body /html这里每一行都有讲究。!DOCTYPE html的作用是告诉浏览器“我现在用的是 HTML5 标准模式解析”如果你漏掉这一行浏览器很可能进入怪异模式盒模型的宽高计算方式会变得和标准不一样同样的 CSS 在 Chrome 和 Firefox 里显示效果对不上。html langzh-cn声明了页面语言是简体中文这个属性对搜索引擎和辅助阅读工具友好更重要的是它影响一些浏览器默认行为比如中文页面的字体选择和换行策略。meta charsetutf-8必须放在 head 的最前面这个标签指定文档的字符编码。如果编码缺失中文注释和中文文本在部分环境下会出现乱码这对做项目的时候排查问题非常折腾。meta nameviewport contentwidthdevice-width, initial-scale1.0是移动端适配必需的。这个项目虽然没有重点适配移动端但加上它以后你在手机浏览器预览时不会出现整体缩得很小的情况。2.2 CSS 的三种引用方式怎么选CSS 样式有三种引入方式外部样式表、内部样式表、内联样式。这个项目里我选择的是外部样式表也就是单独建一个 style.css 文件然后用上面骨架里的link relstylesheet hrefstyle.css引入。外部样式表的好处是结构清晰HTML 文件只负责内容样式文件只负责表现修改样式不用在 HTML 里翻来翻去浏览器也能缓存 CSS 文件刷新更快。缺点就是初学者容易忘记写 link 标签或者路径写错导致样式死活不生效。内部样式表是写在style标签里的适合单页面小工具或者演示片段。如果你只是想在本地快速验证一个效果直接在 HTML 里写 style 标签比建外部文件更快。内联样式是写在标签 style 属性里的优先级最高但可维护性最差适合临时调试。实际开发中除非是邮件模板这类特殊场景不然不推荐。我给一个选择建议做一个几百行的练手项目用外部样式表最合适因为能真实模拟前端项目的工作方式。如果你只是临时试一个 CSS 属性直接在浏览器开发者工具里改就行连文件都不需要建。2.3 一句话说清 CSS 的层叠规则这个项目涉及多张牌共用的样式也有单张牌的特殊样式所以你自然会遇到一个问题多个规则同时命中同一个元素到底听谁的。规则其实很清楚同权重情况下后定义的覆盖先定义的权重不同时id 选择器大于类选择器类选择器大于标签选择器内联样式大于所有选择器!important又压过内联样式。这个优先级在实际调牌面布局时几乎一定会用到尤其是你会给普通状态和展开状态分别写规则一不小心就会互相覆盖。3. 从一张牌开始盒模型、宽高比例与定位布局3.1 给牌定一个合适的尺寸先做单张牌。我设定的牌宽是 140px高 196px比例接近真实扑克牌的 5:7。这个尺寸在页面上展开五张牌时不会太挤牌面上的数字和花色也有足够的空间呼吸。核心的牌面样式如下.card { width: 140px; height: 196px; background: #fff; border: 2px solid #333; border-radius: 12px; position: absolute; box-shadow: 2px 4px 10px rgba(0, 0, 0, 0.3); font-family: Georgia, serif; }这里用到了盒模型的两个细节。第一box-sizing默认值 content-box 时元素的宽高只包含内容区border 和 padding 会额外撑大。我设了 2px 边框实际占位是 144px 宽、200px 高需要心里有数。如果你希望宽高直接包含边框可以加上box-sizing: border-box;。第二position: absolute的意思是让牌脱离文档流后面将它定位到容器内部时会以最近的已定位祖先作为参考。这正是后面展开效果需要的因为多张牌必须重叠在同一个位置如果保持静态定位它们会一个一个往下排永远叠不到一起去。3.2 四角点数怎么排才不歪牌面内容我分了两块左上角和右下角各放一个由数字和花色组成的小区域中间区域放花色图形或小胡桃头像。小区域的 HTML 结构长这样div classcard div classcorner top-left span classrankA/span span classsuit♠/span /div div classcorner bottom-right span classrankA/span span classsuit♠/span /div /div.corner { position: absolute; display: flex; flex-direction: column; align-items: center; width: 24px; line-height: 1.1; } .top-left { top: 8px; left: 8px; } .bottom-right { bottom: 8px; right: 8px; transform: rotate(180deg); }这里有两个关键点。第一.corner用了 flex 加flex-direction: column数字和花色就能垂直排列且水平居中不需要反复调整 margin。第二右下角直接旋转 180 度这样从牌的另一头看点数方向是正的。牌面的数字字体可以根据花色选黑桃和梅花、方块和红桃我都用的同一个字体只是颜色不同。红桃和方块用红色黑桃和梅花用黑色这个用 color 控制就行。3.3 中间图案区绝对定位还是 flex中间区域的做法我经历过好几个版本。最早用绝对定位设 top: 50%、left: 50%再用 transform: translate(-50%, -50%) 让它居中这套方法兼容性好但是代码略长。后来用 flex 容器把牌本身变成纵向、横向都居中的结构写起来更直观。我的做法是给.card加一个display: flex; align-items: center; justify-content: center;然后在牌中间直接放.center-figure这个子元素。flex 一下就把垂直和水平居中都解决了不用算 top 和 left也不用考虑绝对定位脱离文档流带来的影响。中间的大花色或者小胡桃头像我建议不要做得太大占牌面宽度的 55% 左右视觉最佳。太大会让牌显得满太小又撑不住牌面重心这个比例是我反复调出来的你可以根据自己的喜好微调。4. 纯CSS画花色与小胡桃渐变、伪元素与字体属性4.1 花色字符一个字符搞定四个花色项目里的四个花色我直接用了 HTML 实体字符span♠/span span♥/span span♦/span span♣/span对应实体是spades;、hearts;、diams;、clubs;不过直接粘贴字符也能用。每个浏览器里这几个花色的字形不太一样chrome 的表现整体比较圆润Windows 自带字体里黑桃和梅花轮廓相对明显。做的时候我给花色单独设了一个类.suit { font-size: 20px; font-weight: bold; } .red { color: #d32f2f; } .black { color: #222; }你只需要给不同花色加 red 或 black 类就不用每个角重复写颜色了。4.2 牌背的重复渐变牌背是整个项目的加分项。用纯 CSS 做出像布料纹理一样的重复图案主要靠 repeating-linear-gradient。.card-back { background: repeating-linear-gradient( 45deg, #b71c1c 0px, #b71c1c 6px, #f44336 6px, #f44336 12px ); background-color: #d32f2f; border-radius: 12px; }这段代码的含义是在背景上沿 45 度方向重复绘制两种颜色每 12px 一个循环形成红白相间的斜条纹。如果你想让花纹更像是织物的经纬线可以叠加两层不同角度的渐变一层 45 度一层 -45 度中间用 background-blend-mode 控制混合方式。这个玩法我在调试时试过视觉层次一下子丰富很多。光有斜纹还不够我还在牌背外面叠了一个半透明的边框弧度让整个牌背看起来有厚度.card-back::after { content: ; position: absolute; inset: 8px; border: 2px solid rgba(255, 255, 255, 0.35); border-radius: 8px; }inset: 8px是 top/right/bottom/left 都为 8px 的缩写让这个半透明边框距离牌边缘 8px形成一个内嵌的装饰框。4.3 小胡桃头像用伪元素拼出来小胡桃是一个圆脸、带两片小胡桃壳耳朵的角色。我完全用 div 加伪元素来完成没有用任何图片。div classnut div classnut-face/div /div.nut { width: 80px; height: 80px; position: relative; } .nut-face { width: 70px; height: 70px; background: #e0a85c; border-radius: 50%; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); box-shadow: inset 0 -8px 0 rgba(0, 0, 0, 0.08); } .nut-face::before { content: ; position: absolute; width: 10px; height: 14px; background: #5d3a1a; border-radius: 50%; top: 24px; left: 14px; box-shadow: 28px 0 0 #5d3a1a; } .nut-face::after { content: ; position: absolute; width: 20px; height: 12px; background: #5d3a1a; border-radius: 0 0 20px 20px; bottom: 18px; left: 50%; transform: translateX(-50%); }这里用到的知识点主要是伪元素和 box-shadow 的多重投影。两个眼睛原本如果用两个 div 放会多写一个节点用box-shadow复制第二个投影就能让一个伪元素画出两个对称的黑点。嘴巴我用一个底部圆角矩形模拟微笑的弧度。你如果想让小胡桃更像卡通还可以再叠加一行腮红圆点同样用 box-shadow 实现。纯 CSS 画图形有一个练习价值在于你会不得不去理解坐标系。每个位置的 left、top 都是相对父元素算的转过头来再做普通网页布局时你对位移和层叠的理解会比以前清晰很多。5. 展开动画的核心transform、原点与过渡节奏5.1 扇形展开的几何原理现在来到整个项目的重头戏小胡桃展开。先看几何问题。五张牌叠在一起时它们都位于同一坐标原点。展开后每张牌要绕某个点旋转一个角度而且每张牌的角度要不同比如牌 1 旋转 -20 度牌 2 旋转 -10 度牌 3 不动牌 4 旋转 10 度牌 5 旋转 20 度这样视觉上就形成一个扇形。问题在于rotate 默认是绕元素自己的中心点旋转也就是每张牌会“原地转圈”而不是围绕一组牌的共同底部中心展开。要让它们像手握着牌时那样以底端某点为轴散开必须修改 transform-origin。5.2 transform-origin 决定旋转轴心我最终的样式是这样的.card { transform-origin: bottom center; transition: transform 0.4s ease, left 0.4s ease, z-index 0s; } .card:nth-child(1) { left: 200px; z-index: 1; transform: rotate(-20deg); } .card:nth-child(2) { left: 220px; z-index: 2; transform: rotate(-10deg); } .card:nth-child(3) { left: 240px; z-index: 3; transform: rotate(0deg); } .card:nth-child(4) { left: 260px; z-index: 4; transform: rotate(10deg); } .card:nth-child(5) { left: 280px; z-index: 5; transform: rotate(20deg); }这里transform-origin: bottom center的含义是旋转轴心在牌底边的中点。这句话至关重要如果写成默认值center center效果就会变成每张牌绕自己的中心转展开后像五张牌在转圈完全没有扇形的感觉。left属性也要跟着调。因为牌用的是绝对定位它们的 left 决定了水平位置展开时每张牌的 left 都不同配合 rotate 之后就形成了手风琴一样的错落感。这里我建议你把 left 值分别试一遍只改数字就能看到牌组整体的宽窄变化。5.3 鼠标移入事件其实是 hover前面我说过这个项目不写 JavaScript那么“鼠标移入事件”怎么处理CSS 没有事件系统它只有伪类:hover。严格来说鼠标移入、移出这些行为在浏览器底层会被收集成状态但 CSS 侧我们能利用的就是:hover这个匹配条件。我用的结构是给牌组套一层容器.fan鼠标移入容器时容器内部所有的牌一起进入展开状态.fan:hover .card { transform: rotate(0deg); }这行代码的意思是当鼠标悬停在.fan容器上时里面所有牌都回到旋转 0 度的状态。如果你希望展开后每张牌的角度更柔和可以给每张牌单独设置展开角度比如原来是 -20 度展开到 -8 度而不是直接到 0 度这种半展开效果在视觉上更自然。过渡节奏我用的是transition: transform 0.4s ease。0.4 秒是比较舒服的时长太快会显得突然太慢会拖沓。展开过程还可以加一点延迟让每张牌依次动作比如第二张比第一张晚 0.05 秒第三张再晚 0.05 秒形成波浪感。用 CSS 实现延迟很简单就是给每张牌单独写transition-delay.card:nth-child(1) { transition-delay: 0s; } .card:nth-child(2) { transition-delay: 0.05s; } .card:nth-child(3) { transition-delay: 0.1s; }这个细节很多人会忽略但加上以后整个展开过程从“几个牌一起动”变成“依次摊开”看起来很不一样。层级问题也要单独处理。牌组叠放的时候底下的牌会被上面的盖住鼠标移入后虽然角度变了但 z-index 如果没有跟着变部分牌还是会显示在错误的位置。我给每个子牌设置递增的 z-index移入时可以用z-index: 10统一提到最上层避免展开过程中互相遮挡。这个坑我在第一次做的时候踩过当时第三张牌一直被第一张盖住展开后缺了一块排查半天才意识到是 z-index 的问题。6. 实战遇到的问题与排查清单6.1 五个高频坑与解决思路先说最常见的一个问题样式完全没生效。新手遇到这个情况九成是 link 路径写错了要么少写一层目录要么 CSS 文件名和实际对不上。还有一个同样常见的原因就是骨架里的!DOCTYPE html漏掉了浏览器进入怪异模式后盒模型计算方式全变你设置的宽高会对不上预期。第二个高频问题是花色字符显示成方框。这通常是编码问题确认 HTML 文件保存成了 UTF-8 编码并且 head 里有meta charsetutf-8。如果你直接把字符复制到代码里文件本身又用了 GBK 保存浏览器解析就会乱。第三个问题是 hover 展开无效。先检查.fan容器是不是正确地包裹了所有牌再检查选择器是不是写成了.card:hover。如果写的是牌本身的 hover那么只有鼠标准确移到牌上时才触发鼠标放在牌组旁边的空隙里没有反应。放在容器上触发体验会好很多。第四个问题是展开时的旋转轴心不对。这个上面提到过一定要给.card设置transform-origin: bottom center。如果你发现牌在旋转时满屏跑几乎可以断定就是轴心设置没生效。第五个问题是过渡动画太生硬。过渡生硬通常是因为 transition 只写了 transform但展开时 left 和 z-index 也在变。left 的变化如果没加进过渡里牌会瞬间跳过去看起来非常卡。加上transition: transform 0.4s ease, left 0.4s ease;后就会顺滑很多z-index 不用过渡但要注意它的变化是否及时。6.2 排查速查表现象最可能原因处理方式样式完全不生效link 路径错误或缺少 DOCTYPE检查路径补上!DOCTYPE html中文字符乱码编码不一致文件保存为 UTF-8head 里加 charset花色显示为方框字体不支持或实体引用错误换用系统字体使用标准实体字符hover 无反应选择器写错位置或容器覆盖不对将 hover 放在父容器上检查嵌套旋转轴心不对transform-origin 被覆盖检查是否为bottom center优先级是否被其他规则覆盖展开时位移跳变transition 里漏了 left补充left 0.4s ease展开后层次错乱z-index 未设置给每张牌设置递增 z-index6.3 给这个项目再加点后续玩法做完整组牌之后你会发现这个项目还可以继续扩展。最简单的扩展是加一个洗牌按钮这个就得引入 JavaScript 了用随机数打乱牌组顺序再配合 CSS 过渡动画让牌组重新排列一瞬间交互感就很强。如果不引入 JS你也可以做纯 CSS 的自动播放展开效果用keyframes定义牌组的展开和收拢动画再设置 animation 的延时和循环次数。这样页面加载后即使没有人操作也能看到小胡桃牌组自动慢慢展开又收拢很适合做展示型页面。还有一个方向是做成交互问答比如页面展示一张牌面让用户猜牌背是什么点击牌面翻转过来看答案。翻转效果就是 rotateY 加 perspective把这些做进去以后你对 CSS 3D 变换的掌握也会更深入。我做完这个项目最大的体会是CSS 不是靠背属性学会的是靠“不断调试一个具体东西”学会的。调 rotate 的时候你会理解 transform 的参照系调渐变的时候你会理解色标和方向调过渡的时候你会理解缓动函数。等你把这些手感都找到再去看那些复杂组件库的源码思路会清晰很多。最后提醒一句纯 CSS 做图形有一个性价比问题像小胡桃这种简单头像是很合适的练习但如果你要做非常复杂的插画还是切成图片资源更高效。练习的意义在于理解原理而不是和图片素材较劲。