从零掌握 CSS 3D:用前端技术构建一个旋转立方体

文章目录

  • [一、Canvas --- HTML5 的"画布"](#一、Canvas — HTML5 的"画布")
  • [二、CSS 3D --- 用 CSS 属性触发 GPU 加速](#二、CSS 3D — 用 CSS 属性触发 GPU 加速)
    • [2.1 不止是"做 3D"](#2.1 不止是"做 3D")
    • [2.2 核心 CSS 属性速览](#2.2 核心 CSS 属性速览)
  • [三、布局 Layout --- 3D 效果的前置基础](#三、布局 Layout — 3D 效果的前置基础)
    • [3.1 外层盒子 ------ 3D 的"舞台"](#3.1 外层盒子 —— 3D 的"舞台")
    • [3.2 内层盒子 ------ 3D 的"演员"](#3.2 内层盒子 —— 3D 的"演员")
  • [四、水平垂直居中 --- 移动端适配的基石](#四、水平垂直居中 — 移动端适配的基石)
    • [4.1 认识视口单位](#4.1 认识视口单位)
    • [4.2 Flexbox 一步到位居中](#4.2 Flexbox 一步到位居中)
  • [五、行内元素与块级元素 --- HTML 布局的基石](#五、行内元素与块级元素 — HTML 布局的基石)
    • [5.1 两大类元素](#5.1 两大类元素)
    • [5.2 块级元素 --- 独占一行的盒子](#5.2 块级元素 — 独占一行的盒子)
    • [5.3 行内元素 --- 排排坐的文本流](#5.3 行内元素 — 排排坐的文本流)
    • [5.4 `display` 属性 --- 手动切换元素类型](#5.4 display 属性 — 手动切换元素类型)
    • [5.5 `inline-block` --- 鱼和熊掌兼得](#5.5 inline-block — 鱼和熊掌兼得)
  • [六、定位 --- 摆放 3D 面片的核心机制](#六、定位 — 摆放 3D 面片的核心机制)
    • [6.1 `position: relative` --- 相对定位](#6.1 position: relative — 相对定位)
    • [6.2 `position: absolute` --- 绝对定位](#6.2 position: absolute — 绝对定位)
    • [6.3 定位在 3D 立方体中的应用](#6.3 定位在 3D 立方体中的应用)
  • [七、实战:完整的 3D 旋转立方体](#七、实战:完整的 3D 旋转立方体)
    • [7.1 HTML 结构](#7.1 HTML 结构)
    • [7.2 CSS 完整代码(带逐行注释)](#7.2 CSS 完整代码(带逐行注释))
    • [7.3 立方体 6 个面的空间定位原理](#7.3 立方体 6 个面的空间定位原理)
    • [7.4 效果预览](#7.4 效果预览)
  • [八、Flexbox 布局实战补充](#八、Flexbox 布局实战补充)

本文将带你从 HTML5 Canvas 出发,深入到 CSS 3D 的核心原理与实战,手把手实现一个纯 CSS 驱动的 3D 旋转立方体。同时系统梳理行内/块级元素、弹性布局、定位等前端布局基石,适合正在夯实 CSS 基础、对 3D 效果感兴趣的开发者。


一、Canvas --- HTML5 的"画布"

在聊 CSS 3D 之前,有必要提一下 HTML5 另一个做图形的方向:Canvas

<canvas> 是 HTML5 新增的标签,它本身只是一块"画布",真正绘制图形需要靠 JavaScript API。你可以用 JS 在 Canvas 上画 2D 图形,也能结合 WebGL 实现复杂的 3D 渲染(比如 Three.js 就是建立在 Canvas/WebGL 之上的)。

Canvas 和 CSS 3D 的本质区别:

对比维度 Canvas CSS 3D
驱动方式 JavaScript API 逐像素绘制 CSS 属性声明式触发
性能消耗 大量 JS 计算,CPU 密集 浏览器底层/GPU 加速
交互能力 需要通过坐标计算 原生 DOM 事件(点击、hover)
学习成本 高(需要掌握绘制 API) 中(熟悉 CSS 属性即可)
适用场景 游戏、数据可视化、图像处理 页面 UI 动效、产品展示、Banner

一句话总结:Canvas 适合"画"复杂图形,CSS 3D 适合"摆"界面元素。


二、CSS 3D --- 用 CSS 属性触发 GPU 加速

2.1 不止是"做 3D"

CSS 3D 的核心能力远不止"搞个立体效果"这么简单。当你使用 transform 相关属性时,浏览器会把该元素提升到独立的合成层(Composite Layer) ,交给 GPU 负责渲染,而非走传统的 CPU 重绘流程。

这意味着:

  • ✅ 动画更流畅(60fps)
  • ✅ 不触发重排(reflow),只触发合成(composite)
  • ✅ 主线程压力小,页面交互不卡顿

实战技巧 :哪怕你做的只是一个 2D 的淡入淡出,有时我们也会手动加一句 transform: translateZ(0) ------ 这就是俗称的 "手动 3D 化",目的就是骗浏览器开启 GPU 加速。

css 复制代码
/* 2D 元素也可以偷偷吃 GPU 加速 */
.smooth-card {
    transform: translateZ(0);  /* 提升到合成层,动画更丝滑 */
    transition: opacity 0.3s;
}

2.2 核心 CSS 属性速览

在动手写 3D 效果之前,先把关键属性认全:

属性 作用 常见值
perspective 视距(3D 空间的"深度感"来源) 600px~1200px,值越小透视感越强
transform-style 是否保留子元素的 3D 空间 preserve-3d(保留)/ flat(默认,拍扁)
transform 执行变换:位移、旋转、缩放等 translateZ() rotateX() rotateY()
backface-visibility 元素背面是否可见 visible / hidden
perspective-origin 视点位置(观察者站哪看) center center(默认)

三、布局 Layout --- 3D 效果的前置基础

在做 3D 之前,你必须先把元素"摆对位置"。布局不稳,3D 全是歪的。

3.1 外层盒子 ------ 3D 的"舞台"

3D 效果需要一个视距容器来提供深度感。我们通常用一个外层 wrapper 包裹 3D 内容:

css 复制代码
.box-wrap {
    width: 200px;
    height: 200px;
    perspective: 600px;  /* 👈 3D 核心!定义观察者与屏幕的距离 */
}

perspective 的作用直观理解:

想象你站在一条马路上看远方------路越远越小,两条平行的路边最终"汇聚"成一个点。perspective 就是你站的位置与"屏幕"的距离:

  • perspective: 300px → 你离屏幕很近 → 透视感极强,元素变形剧烈
  • perspective: 1200px → 你离屏幕很远 → 透视感弱,接近正交投影
  • perspective: 600px常用中间值,效果自然
css 复制代码
/* 对比效果 */
.box-wrap-near  { perspective: 300px; }  /* 强烈透视 */
.box-wrap-far   { perspective: 1200px; } /* 微弱透视 */

3.2 内层盒子 ------ 3D 的"演员"

内层盒子需要两个关键属性来配合:

css 复制代码
.box {
    width: 100%;
    height: 100%;
    position: relative;            /* 为子元素的绝对定位提供锚点 */
    transform-style: preserve-3d;  /* 👈 保留子元素的 3D 空间 */
}

transform-style: preserve-3d 有多重要?

  • 默认值 flat 会把所有子元素"拍扁"在父元素的平面上------你做的 3D 变换看起来还是 2D
  • 设为 preserve-3d 后,子元素的 Z 轴位置才真正生效,立体感马上就出来了

四、水平垂直居中 --- 移动端适配的基石

在做 3D 效果之前,我们通常需要把内容放到屏幕正中央。这涉及到一个前端面试高频考点:水平垂直居中

4.1 认识视口单位

CSS3 引入了四个与视口(viewport)相关的单位,特别适合移动端适配:

单位 含义 示例
vh viewport-height,视口高度的 1% 100vh = 满屏高度
vw viewport-width,视口宽度的 1% 100vw = 满屏宽度
vmin vhvw 中较小的那个 竖屏时 = vw
vmax vhvw 中较大的那个 竖屏时 = vh
css 复制代码
/* 移动端适配利器 */
html, body {
    height: 100vh;  /* 占满整个屏幕高度,100 等份中的 100 份 */
}

100% 的区别height: 100% 依赖父元素有明确高度,嵌套深了容易"塌陷"。100vh 直接取视口高度,不依赖任何父元素。

4.2 Flexbox 一步到位居中

css 复制代码
html, body {
    height: 100vh;
    display: flex;           /* 开启弹性布局 */
    flex-direction: row;     /* 主轴方向:row(默认水平)/ column(垂直) */
    justify-content: center; /* 主轴对齐:居中 */
    align-items: center;     /* 次轴对齐:居中 */
}

Flexbox 主轴 / 次轴图解:

flex-direction 主轴方向 次轴方向 justify-content 控制 align-items 控制
row(默认) 水平 → 垂直 ↓ 水平居中 垂直居中
column 垂直 ↓ 水平 → 垂直居中 水平居中

这就是为什么 4 行 CSS 就能同时搞定水平和垂直居中------不需要 margin 负值,不需要 transform 偏移,移动端视窗大小多变时尤其好用。


五、行内元素与块级元素 --- HTML 布局的基石

在深入定位之前,必须搞清楚 HTML 元素的两大分类------这是所有 CSS 布局的底层逻辑。

5.1 两大类元素

HTML 元素天生分为两类:

类型 默认 display 典型元素 特点
块级(block) block div p h1~h6 ul li section ① 可以设置宽高 ② 独占一行
行内(inline) inline span a strong em i ① 不能设置宽高 ② 不会挤走兄弟

5.2 块级元素 --- 独占一行的盒子

html 复制代码
<div class="block-demo">我是块级盒子1</div>
<div class="block-demo">我是块级盒子2</div>
css 复制代码
.block-demo {
    width: 300px;    /* ✅ 可以设置宽高 */
    height: 80px;
    background: #4299e1;
    /* 默认独占一行,下一个元素会换行 */
}

5.3 行内元素 --- 排排坐的文本流

html 复制代码
<span class="inline-demo">行内1</span>
<span class="inline-demo">行内2</span>
css 复制代码
.inline-demo {
    /* width: 200px;  ❌ 设置宽高无效! */
    background: #f5656f;
    padding: 5px 10px;
    /* 不会独占一行,兄弟元素跟在后面 */
}

5.4 display 属性 --- 手动切换元素类型

浏览器给了每个 HTML 标签一个默认的 display 值,但我们可以通过 display 手动切换,进而开启不同的格式化上下文(Formatting Context)

css 复制代码
/* 块级 ↔ 行内自由切换 */
.box { display: block;  }   /* 变成块级 */
.box { display: inline; }   /* 变成行内 */

三种重要的格式化上下文:

格式化上下文 特点
flex 弹性格式化上下文 一维布局,子元素自动沿主轴排列
inline-block 行内块级 兼具行内和块级的优点
grid 网格格式化上下文 二维布局,行 + 列同时控制

5.5 inline-block --- 鱼和熊掌兼得

css 复制代码
.item {
    display: inline-block;
    width: 50%;      /* ✅ 可以设置宽高! */
    /* ✅ 也不会把兄弟元素挤到下一行! */
}

inline-block 是行内元素和块级元素的"混血儿":

  • 对外像行内元素 ------ 不换行,跟兄弟元素排排坐
  • 对内像块级元素 ------ 可以设置宽高、margin、padding

⚠️ 天坑预警:inline-block 的空白间距

html 复制代码
<!-- 注意:两个 div 之间有个换行符 -->
<div class="box">1</div>
<div class="box">2</div>
css 复制代码
.box {
    display: inline-block;
    width: 50%;
    background: red;
}

你会发现两个 50% 的盒子没有并排,第二个被挤到下一行了------为什么?

原因 :HTML 中标签之间的换行符(\n\r)会被解析为一个空格字符 。这个空格在 inline-block 元素之间会占据一定的宽度,导致 50% + 空格 + 50% > 100%,第二个盒子只能换行。

解决方案:

html 复制代码
<!-- 方案1:去掉换行(不优雅) -->
<div class="box">1</div><div class="box">2</div>

<!-- 方案2:父元素 font-size: 0(推荐) -->
<style>
    .parent { font-size: 0; }  /* 让空格宽度变为 0 */
    .box { font-size: 16px; }   /* 子元素单独设回字号 */
</style>

<!-- 方案3:用 flex 替代 inline-block(推荐) -->
<style>
    .parent { display: flex; }
    .box { flex: 1; }
</style>

六、定位 --- 摆放 3D 面片的核心机制

在 3D 立方体中,6 个面都需要绝对定位 叠加在同一个位置,再由 transform 各自位移旋转。这就要用到 CSS 的定位体系。

6.1 position: relative --- 相对定位

css 复制代码
.parent {
    position: relative;  /* 建立定位参考系 */
}
  • 元素仍占据原来的文档流位置
  • 为子元素的 absolute 提供定位锚点
  • 自身可以通过 top/left/right/bottom 微调位置

6.2 position: absolute --- 绝对定位

css 复制代码
.child {
    position: absolute;
    top: 0;
    left: 0;
}
  • 元素脱离文档流(不占位,后面的元素会顶上来)
  • 相对于最近的已定位祖先positionstatic)定位
  • 如果找不到已定位祖先,则相对于 <body> 定位

黄金组合父 relative + 子 absolute,这是 CSS 布局中最经典的搭配。

6.3 定位在 3D 立方体中的应用

css 复制代码
.box {
    position: relative;  /* 父:建立坐标参考 */
}

.face {
    position: absolute;  /* 子:6 个面都叠在 (0,0) 原点 */
    width: 200px;
    height: 200px;
    top: 0;
    left: 0;
}

6 个面先全部叠在同一位置,再用 transform 分别推到不同方向------这就是 CSS 3D 立方体的核心思路:

css 复制代码
.front  { transform: translateZ(100px); }              /* 向前 100px */
.back   { transform: translateZ(-100px); }             /* 向后 100px */
.left   { transform: translateX(-100px) rotateY(-90deg); } /* 左移 + 旋转 */
.right  { transform: translateX(100px) rotateY(90deg);  } /* 右移 + 旋转 */
.top    { transform: translateY(-100px) rotateX(90deg);  } /* 上移 + 旋转 */
.bottom { transform: translateY(100px) rotateX(-90deg);  } /* 下移 + 旋转 */

七、实战:完整的 3D 旋转立方体

把上面所有知识点串起来,下面是完整代码。

7.1 HTML 结构

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CSS 3D 旋转立方体</title>
    <link rel="stylesheet" href="./common.css">
</head>
<body>
    <!-- 外层:视距容器(3D 舞台) -->
    <div class="box-wrap">
        <!-- 内层:3D 空间容器 + 动画主体 -->
        <div class="box">
            <div class="face front">前</div>
            <div class="face back">后</div>
            <div class="face left">左</div>
            <div class="face right">右</div>
            <div class="face top">上</div>
            <div class="face bottom">下</div>
        </div>
    </div>
</body>
</html>

7.2 CSS 完整代码(带逐行注释)

css 复制代码
/* ===== 全局重置 ===== */
* {
    margin: 0;
    padding: 0;
}

/* ===== 父容器:将立方体居中于屏幕 ===== */
html, body {
    height: 100vh;             /* 占满整个视口高度 */
    display: flex;             /* 开启弹性布局,移动端适配首选 */
    flex-direction: row;       /* 主轴方向(默认值,可省略) */
    justify-content: center;   /* 主轴居中对齐 */
    align-items: center;       /* 次轴居中对齐 */
}

/* ===== 外层盒子:3D 舞台(提供视距) ===== */
.box-wrap {
    width: 200px;
    height: 200px;
    perspective: 600px;        /* 👈 3D 核心属性:观察者到屏幕的距离 */
    /* 值越小 → 透视感越强 → 近大远小效果越夸张 */
}

/* ===== 内层盒子:3D 空间容器 + 动画主体 ===== */
.box {
    width: 100%;
    height: 100%;
    position: relative;         /* 为子元素绝对定位提供锚点 */
    transform-style: preserve-3d; /* 👈 保留子元素的 3D 空间(默认 flat 会把子元素拍扁) */
    animation: rotate 5s linear infinite; /* 绑定旋转动画 */
}

/* ===== 关键帧动画 ===== */
@keyframes rotate {
    100% {
        /* 同时绕 X 轴和 Y 轴旋转 360 度 */
        transform: rotateX(360deg) rotateY(360deg);
    }
}

/* ===== 6 个面的公共样式 ===== */
.face {
    width: 200px;
    height: 200px;
    position: absolute;        /* 脱离文档流,6 个面叠在同一位置 */
    top: 0;
    left: 0;
    /* 面上的文字居中 */
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 30px;
    color: #fff;
    opacity: 0.8;              /* 半透明,方便看到多个面 */
}

/* ===== 6 个面各自的颜色 + 3D 位移与旋转 ===== */

/* 正面:沿 Z 轴向屏幕外推 100px(200px 边长的一半) */
.front {
    background-color: #4299e1;
    transform: translateZ(100px);
}

/* 背面:沿 Z 轴向屏幕内推 100px */
.back {
    background-color: #f5656f;
    transform: translateZ(-100px);
}

/* 左面:先左移 100px,再绕 Y 轴逆时针转 90° */
.left {
    background-color: #99c266;
    transform: translateX(-100px) rotateY(-90deg);
}

/* 右面:先右移 100px,再绕 Y 轴顺时针转 90° */
.right {
    background-color: #6699cc;
    transform: translateX(100px) rotateY(90deg);
}

/* 上面:先上移 100px,再绕 X 轴转 90° */
.top {
    background-color: #65f59f;
    transform: translateY(-100px) rotateX(90deg);
}

/* 下面:先下移 100px,再绕 X 轴反向转 90° */
.bottom {
    background-color: #f5659f;
    transform: translateY(100px) rotateX(-90deg);
}

7.3 立方体 6 个面的空间定位原理

复制代码
                    ↑ Y 轴(上)
                   |
          ┌───────┼───────┐
          │  top  │       │
          │       │       │
          ├───────┼───────┤
  ←─── left ── │ front  │ right ── → X 轴(右)
          │       │       │
          ├───────┼───────┤
          │ bottom│       │
          │       │       │
          └───────┼───────┘
                  │
             back(Z 轴负方向,屏幕内)

    每个面都是 200×200px
    立方体中心到每个面的距离 = 100px(边长的一半)

关键理解

  1. 6 个面初始位置完全相同(absolute 后都叠在原点)
  2. translateZ(100px) 把前面推到离观察者近 100px 的位置
  3. translateX(-100px) rotateY(-90deg) 把左面推到左边,然后旋转让它"朝向"外面
  4. 其他面同理------先位移到对应位置,再旋转到正确的朝向

transform 的顺序很重要translateX() 后再 rotateY() 的效果,和先旋转再位移是完全不同的!CSS 的 transform 按从左到右的顺序执行。

7.4 效果预览

保存代码后在浏览器打开,你会看到一个半透明、自动旋转的 3D 立方体 ,6 个面颜色各异,平滑旋转无卡顿------这就是 GPU 加速带来的丝滑体验。


八、Flexbox 布局实战补充

上面的 3.html 演示了 Flexbox 的核心机制:

html 复制代码
<div class="box">
    <div class="item">1</div>
    <div class="item">2</div>
    <div class="item">3</div>
    <div class="item">4</div>
</div>
css 复制代码
.box {
    display: flex;
    /* flex-direction: column;  切换主轴方向 */
}

.item {
    flex: 1;            /* 等分剩余空间 */
    background-color: red;
    text-align: center;
}

flex: 1 的精髓:

flex: 1flex-grow: 1; flex-shrink: 1; flex-basis: 0% 的缩写,意思是:把父容器剩余的空间,按 1:1:1:1 的比例分给每个子元素 。4 个子元素各有 flex: 1,于是 4 等分。

css 复制代码
/* 不等分示例 */
.item:nth-child(1) { flex: 2; }  /* 占 2 份 */
.item:nth-child(2) { flex: 1; }  /* 占 1 份 */
/* 总共 3 份,第 1 个占 2/3,第 2 个占 1/3 */

全文总结

本文从 HTML5 Canvas 出发,引出了 CSS 3D 这一更适合前端 UI 动效的技术路径。我们从底层布局知识(行内/块级、弹性布局、居中策略)一路讲到定位体系,最终用 perspective + transform-style: preserve-3d + transform + 关键帧动画 组合拳,手把手实现了一个纯 CSS 驱动的 3D 旋转立方体。


核心知识点复盘

知识点 一句话总结
Canvas vs CSS 3D Canvas 用 JS 画图,CSS 3D 用属性声明,后者自动走 GPU 加速
vh / vw 视口单位,100vh 直接占满屏幕高度,不依赖父元素,移动端神器
Flexbox 居中 display: flex + justify-content: center + align-items: center,4 行搞定水平垂直居中
perspective 定义 3D 视距,值越小透视感越强;必须设在父元素
transform-style: preserve-3d 让子元素保留在 3D 空间中,不设这个属性 3D 出不来
position 组合 relative + 子 absolute,6 个面叠在同一原点再各自位移
transform 顺序 从左到右执行,translate + rotate 的顺序不同结果不同
inline-block 空白坑 换行符被解析为空格,导致 50% + 空格 + 50% > 100%,用 font-size: 0 或改用 flex 解决
flex: 1 等分剩余空间,flex-grow: 1 + flex-shrink: 1 + flex-basis: 0% 的缩写
GPU 加速原理 transform 触发合成层(Composite Layer),不触发重排,动画丝滑不掉帧

常见问题 / 避坑指南

❓ Q1:为什么我的 3D 效果看起来是"扁的"?

90% 的情况是忘了写 transform-style: preserve-3d 。这个属性必须加在包裹 6 个面的父容器 上(即 .box),表示"我的子元素要在 3D 空间里待着,别拍扁它们"。默认值是 flat

❓ Q2:perspective 写在哪儿?写多大合适?

写在外层容器 上(本文的 .box-wrap),不是写在内层 box 上。值一般在 500px ~ 1200px 之间选,600px 是比较自然的常用值。值越小透视感越强(变形越厉害),值越大越接近正交投影。

❓ Q3:rotateXrotateY 的旋转方向怎么记?

想象一根轴穿过元素中心 ------ rotateX 绕 X 轴转(像翻书,前后翻),rotateY 绕 Y 轴转(像转盘,左右转)。正值方向用左手定则:大拇指指向轴的正方向,四指弯曲方向即为旋转正方向。

❓ Q4:translateZ(100px) 中的 100px 为什么是立方体边长的一半?

因为 6 个面初始在立方体的中心点,每个面的尺寸是 200×200。要把前面从中心推到立方体的前表面,距离 = 边长 ÷ 2 = 100px。如果你改成 300px 大小的面,这里就是 150px。

❓ Q5:position: absolute 后元素从哪开始算位置?

最近的已定位祖先position 值不是默认的 static 即可),通常由父亲 position: relative 提供。如果找不到,就以 <body> 为基准。这就是为什么父元素要写 position: relative

❓ Q6:Flexbox 中 justify-contentalign-items 总搞混?

记住口诀:justify 主,align 次 。主轴方向由 flex-direction 决定,默认 row 时主轴是水平,所以 justify-content: center = 水平居中,align-items: center = 垂直居中。如果你把 flex-direction 切成了 column,两者就互换了。


如果这篇文章对你有帮助,欢迎点赞收藏,有问题可以在评论区留言交流 🚀