CSS 3D 硬核解析:四个属性手写旋转立方体

📅 2026/6/17 0:31:48
CSS 3D 硬核解析:四个属性手写旋转立方体
不需要 WebGL不需要 Three.js纯 CSS 就能搞定 3D。本文把perspective、transform-style、transform、animation这四个核心属性彻底拆开每一步都解释为什么这么写。 先看成品一个边长 200px 的立方体六面不同色绕 Y 轴匀速旋转。核心代码不到 50 行。效果一个彩色立方体绕 Y 轴旋转下面从零开始搭每个 CSS 属性都掰开揉碎讲清楚。 目录️ 第一步搭骨架 —— HTML 三层结构️ 第二步perspective —— 打开 3D 的眼睛 第三步transform-style —— 让空间变立体 第四步六个面的 3D 定位 —— 构建立方体 第五步animation —— 让立方体转起来 附赠GPU 加速 —— 3D 的赠品 完整代码 总结️ 第一步搭骨架 —— HTML 三层结构结构非常简单三层 divdivclassbox-wrap!-- 外层负责 3D 视距 --divclassbox!-- 中层负责 3D 空间 旋转动画 --divclassface front前/div!-- 内层立方体的六个面 --divclassface back后/divdivclassface left左/divdivclassface right右/divdivclassface top上/divdivclassface bottom下/div/div/div为什么分三层因为 3D 的两个核心属性必须写在不同元素上——perspective写在外层不动的地方transform-style写在旋转的元素上。后面会解释为什么。开工前先把页面铺满并居中——两行 Flex 搞定不展开了*{margin:0;padding:0;}html, body{height:100vh;display:flex;justify-content:center;align-items:center;}100vh是视口高度的 100%无论手机还是电脑body都刚好铺满一屏。 前置知识inline vs blockdisplay 的三种面孔在深入 3D 之前花五分钟搞清楚 HTML 元素的两种天性——不然后面看到display: flex、position: absolute这些属性会懵。块级元素 vs 行内元素HTML 元素天生分两类浏览器给每个标签预设了一个出厂类型类型代表标签能设宽高独占一行块级 blockdiv、p、h1、ul、li✅ 可以✅ 会把兄弟挤到下一行行内 inlinespan、a、em、strong❌ 不能❌ 不会和兄弟排在同一行块级元素一人占一整行┌──────────────────────────────┐ │ div块级 width: 200px │ │ 宽高生效独占一行 │ └──────────────────────────────┘ ┌──────────────────────────────┐ │ div块级 │ │ 被挤到下一行不管上面多窄 │ └──────────────────────────────┘块级元素像一本书的章节标题——不管标题文字多短它后面一定是另起一行。行内元素像水流一样排列┌─────┐ ┌──────────┐ ┌────┐ ┌────────┐ │span │ │span │ │a链接│ │em强调 │ → 同一行 └─────┘ └──────────┘ └────┘ └────────┘ 宽高无效内容多宽就多宽排不下才换行行内元素像句子里的文字——一个接一个往后排排满了才换行。width和height对它无效大小完全由内容撑开。代码对照!-- 块级宽高生效但各占一行 --divstylewidth:200px;height:60px;background:red;块级 div/divdivstylebackground:blue;我被挤到下一行了/div!-- 行内并排显示但宽高无效 --spanstylewidth:200px;height:60px;background:red;宽高写了也没用/spanspanstylebackground:blue;我跟在上面同一行/span这就是浏览器默认的行为规则。但你可以用display属性手动改写。display 的三条路线浏览器出厂设定 │ │ 块级 div独占一行能设宽高 │ 行内 span并排流动宽高无效 │ ▼ display: block / inline ← 路线一在两种基础类型之间切换 │ │ display: inline-block ← 路线二行内块级能设宽高 不换行 │ display: flex ← 路线三弹性容器子元素自动弹性分配 │ display: grid ← 路线三网格容器 │ ▼ 开启新的格式化上下文三条路线的视觉效果路线一基础切换 ┌──────────┐ ┌──────────┐ display: inline 把块级变行内 │div→inline│ │div→inline│ → 能并排但宽高又无效了 └──────────┘ └──────────┘ 路线二inline-block ┌──────────┐ ┌──────────┐ 能设宽高 ✅ 不换行 ✅ │ 50% │ │ 50% │ → 但中间有个空格坑… └──────────┘ └──────────┘ ↑ 换行符变成的空格几像素但足以撑破 100% 路线三flex ┌─────────────────────────────┐ │ ┌────┐ ┌────┐ ┌────┐ ┌────┐│ 父容器 display: flex │ │ 1 │ │ 2 │ │ 3 │ │ 4 ││ 子元素 flex: 1 自动等分 │ └────┘ └────┘ └────┘ └────┘│ 无空格坑自动响应 └─────────────────────────────┘① inline-block —— 行内块级.box{display:inline-block;/* 行内块级能设宽高不独占一行 */width:50%;background:red;}inline-block取了块级和行内的优点既能设宽高又不会把兄弟挤下去。两个width: 50%的 div 可以并排。divclassbox1/divdivclassbox2/div!-- 两个块级 div 并排了 --⚠️inline-block 的经典天坑HTML 源码中标签之间的换行符会被浏览器渲染成一个可见的空格字符。两个width: 50%加上这个空格总宽度超过 100%第二个 div 就被挤到下一行。这是新手必踩的坑。解决方案① 父元素设font-size: 0把空格缩成零② 标签之间不留换行全写在一行③ 直接用 Flex 布局替代——完全不存在这个问题。② flex —— 弹性布局.box{display:flex;/* 开启弹性格式化上下文 */}.item{flex:1;/* 所有子元素等分父容器空间 */}flex: 1是flex-grow: 1; flex-shrink: 1; flex-basis: 0%的简写。意思就是所有子元素按相同比例瓜分父容器的剩余空间——4 个子元素各 25%3 个各 33.33%不需要手动算百分比。divclassboxdivclassitem1/divdivclassitem2/divdivclassitem3/divdivclassitem4/div/divFlex 是移动端最常用的布局方式。视口尺寸千变万化Flex 天生弹性适应。本文的立方体居中用的就是 Flex。③ block / inline —— 基础切换div{display:inline;}/* 让块级 div 变成行内行为 */span{display:block;}/* 让行内 span 变成块级行为 */这个用得少但要知道——display可以彻底改变一个元素的天性。回到我们的立方体搞清楚这些之后立方体 CSS 里的display: flex和position: absolute就不再神秘了body设display: flex→ 变成弹性容器子元素.box-wrap自动居中。.face设position: absolute→ 六个面脱离文档流叠在同一个原点。这和display无关——absolute让元素不再参与文档流排版后面会细讲。️ 第二步perspective —— 打开 3D 的眼睛.box-wrap{width:200px;height:200px;perspective:600px;} 这个属性到底在干什么perspective定义的是你的眼睛到屏幕z0 平面的距离。 你观察者 │ │ ← perspective: 600px │ ═══╪═══ z0 平面屏幕表面 │ │ ← translateZ(100px) 物体往屏幕外凸 ▼ 立方体没有 perspectiveZ 轴上的一切变化你都看不见。因为人眼是靠近大远小感知深度的而透视正是这个规则的名字。关掉perspectivetranslateZ(100px)和translateZ(-100px)看起来一模一样——都是那个平面。打开它前面的面大、后面的面小深度感瞬间就出来了。 值越小3D 感越强这是最关键的一条直觉很多人用了很久 3D 都没搞明白perspective相当于效果200px凑到脸上看透视变形剧烈广角镜头感600px正常观看距离适中的 3D 感最常用1200px远远地看几乎不变形接近平面你可以打开开发者工具把perspective从 200 拖到 2000亲眼见证立方体从夸张变形到几乎就是一个正方形在转的整个过程。⚠️ 一个容易犯的错perspective不能写在旋转的元素上。如果把它写在.box旋转的元素上透视原点会跟着旋转一起动——效果鬼畜。正确的做法perspective写在不动的外层容器上这样整个 3D 场景共享同一个透视空间旋转时六个面的透视关系始终一致。 第三步transform-style —— 让空间变立体.box{width:100%;height:100%;position:relative;transform-style:preserve-3d;} flat vs preserve-3d天差地别transform-style决定的是当父元素发生 3D 变换时子元素是活在 3D 空间里还是被拍扁在 2D 平面上。❌flat默认值子元素全部压平。无论你怎么设子元素的translateZ都看不到深度——整个东西转起来就是一张纸片。✅preserve-3d保留子元素的 3D 位置。六个面真实地分布在 Z 轴不同深度处——转起来是有体积的立方体。 你现在就可以验证在开发者工具里把.box的transform-style: preserve-3d勾掉立方体瞬间变卡片。勾回来立体感恢复。这一勾一改之间就是flat和preserve-3d的全部区别。 两个核心的定位perspective → 管你怎么看 → 写在外层容器 transform-style → 管空间长什么样 → 写在旋转的元素上两者缺一不可perspectivepreserve-3d你看到的东西✅✅真正的 3D 立方体✅❌一张会转的纸片❌✅没有透视的立方体像等距视图很假❌❌一个静止的 2D 正方形 第四步六个面的 3D 定位 —— 构建立方体立方体边长 200px所以中心到每个面的距离是100px。 4.1 先把六个面叠在一起.face{width:200px;height:200px;position:absolute;/* 全部脱离文档流 */display:flex;align-items:center;justify-content:center;/* 文字在面内居中 */font-size:30px;color:#fff;opacity:0.8;/* 半透明能看到后面的面 */}父元素.box设了position: relative所以六个position: absolute的face全部以.box的左上角为原点精确重叠在同一个位置。这就是俗称的父相子绝。 4.2 最重要的规则transform 从右往左执行写具体的 transform 之前先死记这条规则transform:translateX(100px)rotateY(90deg);/* ② 后执行 ① 先执行 */CSS 的 transform 函数列表最右边的先执行。translateX(100px) rotateY(90deg) 先旋转再平移。为什么这个顺序至关重要因为旋转会改变元素的局部坐标系。rotateY(90deg)一执行元素的 X 轴就被扭到了原来的 Z 轴方向——后面的translateX(100px)实际上是沿着新方向移动。 想象手里拿着一张纸✅先转向左再向前走 100px→ 纸在你左边 100px纸面朝左。这是立方体左面的正确位置。❌先向左走 100px再转向左→ 纸在你左边 100px纸面朝向你然后才转向左——位置和朝向都不对。 4.3 CSS 3D 坐标系速查动手之前瞄一眼坐标轴Y- ↑ │ │ X- ←───────┼────────→ X │ │ Y ↓ Z 从屏幕指向你的眼睛 Z- 指向屏幕深处三个关键点 Y 轴向下为正屏幕坐标系不是数学坐标系所以translateY(-100px)是向上移。rotateX()是绕 X 轴翻转rotateY()是绕 Y 轴水平旋转rotateZ()是绕 Z 轴 2D 旋转。 旋转方向用右手定则大拇指指向轴的正方向四指弯曲的方向就是正角度方向。 4.4 逐个摆好六个面前面默认朝前不需要旋转直接沿 Z 轴正方向推 100px。.front{background:#4299e1;transform:translateZ(100px);}后面绕 Y 轴转 180° 让面朝后再沿 Z 轴负方向推 100px。为什么要转 180°不转的话从背面看文字是镜像的——后字左右颠倒。.back{background:#f5656f;transform:translateZ(-100px)rotateY(180deg);/* ② 后执行 ① 先执行 */}左面绕 Y 轴逆时针转 90° 面朝左再沿旋转后的X 轴向左推 100px。.left{background:#48bb78;transform:translateX(-100px)rotateY(-90deg);}右面绕 Y 轴顺时针转 90° 面朝右再沿 X 轴向右推 100px。.right{background:#48bb78;transform:translateX(100px)rotateY(90deg);}上面绕 X 轴转 90° 面朝上再沿 Y 轴向上推 100px。Y 负 上。.top{background:#9f7aea;transform:translateY(-100px)rotateX(90deg);}下面绕 X 轴逆时针转 90° 面朝下再沿 Y 轴向下推 100px。.bottom{background:#ecc94b;transform:translateY(100px)rotateX(-90deg);}️ 4.5 一个直观的图解----- ← .top紫色 /| /| / | / | ----- | ← .right绿色 | --|-- | / | / ← .front蓝色 ----- ← .bottom黄色 .left绿色 .back红色在背面看不到 第五步animation —— 让立方体转起来立方体摆好了加一行让它绕 Y 轴旋转。.box{/* 前面的属性不动 */animation:rotate/* 动画名称 */5s/* 一轮 5 秒 */linear/* 匀速 */infinite;/* 无限循环 */}keyframesrotate{0%{transform:rotateY(0deg);}100%{transform:rotateY(360deg);}}️ keyframes 在做什么0%是起点100%是终点。浏览器在这两个状态之间自动算中间值这叫插值从 0° 平滑过渡到 360°视觉上就是完整转一圈。你可以插入任意中间关键帧来编排更复杂的动作keyframeswiggle{0%{transform:rotateY(0deg);}25%{transform:rotateY(45deg);}50%{transform:rotateY(0deg);}75%{transform:rotateY(-45deg);}100%{transform:rotateY(0deg);}} 旋转动画为什么必须用 linearanimation-timing-function控制速度曲线。旋转这件事永远选linear。值表现用在旋转上linear全程匀速✅ 自然像地球自转ease默认慢→快→慢❌ 转一圈中间突快、两头慢节奏诡异ease-in-out慢→快→慢❌ 同上自然界中匀速旋转的东西到处都是——地球、陀螺、风扇。旋转中用ease忽快忽慢直觉上就是卡顿。 附赠GPU 加速 —— 3D 的赠品这不是 3D 本身但太实用了必须提一嘴。当你给元素加上 3D transform浏览器会自动把它提升到独立的合成层Compositor Layer渲染丢给 GPU/* 纯 2D 页面也能蹭 GPU 加速 */.smooth-element{transform:translateZ(0);/* 或 translate3d(0, 0, 0)效果一样 */}好处 动画期间不触发重排reflow和重绘repaint只做 GPU 合成。 帧率更稳在低端手机上差异尤其明显。⚠️ 别滥用——每个合成层都占 GPU 显存几百个元素全加translateZ(0)反而可能掉帧。 完整代码/* 全局重置 */*{margin:0;padding:0;}/* 页面居中 */html, body{height:100vh;display:flex;justify-content:center;align-items:center;}/* 外层设置 3D 视距 */.box-wrap{width:200px;height:200px;perspective:600px;/* 核心 ①视距 */}/* 中层3D 空间 旋转动画 */.box{width:100%;height:100%;position:relative;transform-style:preserve-3d;/* 核心 ②3D 空间 */animation:rotate 5s linear infinite;}keyframesrotate{0%{transform:rotateY(0deg);}100%{transform:rotateY(360deg);}}/* 内层六个面的公共样式 */.face{width:200px;height:200px;position:absolute;display:flex;align-items:center;justify-content:center;font-size:30px;color:#fff;opacity:0.8;}/* 六个面各自的 3D 定位 *//* 核心 ③transform 从右往左执行——先旋转朝向再平移到目标位置 */.front{background:#4299e1;transform:translateZ(100px);}/* 前朝前是默认方向直接向前推 */.back{background:#f5656f;transform:translateZ(-100px)rotateY(180deg);}/* 后转 180° 朝后文字从背面看才不是反的再向后推 */.left{background:#48bb78;transform:translateX(-100px)rotateY(-90deg);}/* 左逆时针转 90° 朝左再向左推 */.right{background:#48bb78;transform:translateX(100px)rotateY(90deg);}/* 右顺时针转 90° 朝右再向右推 */.top{background:#9f7aea;transform:translateY(-100px)rotateX(90deg);}/* 上绕 X 轴转 90° 朝上再向上推Y 负 上 */.bottom{background:#ecc94b;transform:translateY(100px)rotateX(-90deg);}/* 下绕 X 轴逆时针转 90° 朝下再向下推 */!DOCTYPEhtmlhtmllangzh-CNheadmetacharsetUTF-8titleCSS 3D 旋转立方体/titlelinkrelstylesheethrefstyle.css/headbodydivclassbox-wrapdivclassboxdivclassface front前/divdivclassface back后/divdivclassface left左/divdivclassface right右/divdivclassface top上/divdivclassface bottom下/div/div/div/body/html两个文件放同一目录浏览器打开 HTML 就能看到效果。 总结整个立方体就是四个属性的协作perspective: 600px → ① 打开透视让 Z 轴有深度 transform-style: preserve-3d → ② 声明 3D 空间六个面不被拍平 transform: translate rotate → ③ 把六个面推到立方体的六个位置 animation keyframes → ④ 绕 Y 轴匀速旋转 背下来这几条就够了perspective 和 preserve-3d 缺一不可—— 一个管能不能看到深度一个管空间是不是 3D 的。perspective 值越小3D 感越强—— 200 广角镜头1200 接近平面。transform 从右往左执行——translateX(100px) rotateY(90deg) 先旋转再平移。perspective 写在外层preserve-3d 写在旋转层—— 放错位置效果完全不对。旋转动画用 linear—— 匀速最自然ease 忽快忽慢在旋转中很违和。3D 自动触发 GPU 加速—— 2D 页面加translateZ(0)也能白嫖。 最好的学习方式复制代码 → 打开开发者工具 → 逐属性修改 → 亲眼看到变化。改perspective看变形程度改transform-style看立体感开关改各面的translate值看面的位置怎么变。改一遍比读十遍记得牢。