简介:一份基于HTML5与CSS3的3D旋转效果示例代码包,面向前端初学者、网页设计爱好者以及想在页面中加入动态立体展示的开发者。资源以城市模型(CityDemo)为演示场景,通过CSS3的rotate3d、perspective和backface-visibility等属性构建3D空间,并配合JavaScript的requestAnimationFrame实现连续旋转与用户交互,可帮助理解从静态布局到动态3D视觉的实现思路。压缩包内共3个文件,包含1个可直接运行的HTML页面、1张示例效果图与1张城市模型图片,包体仅3.41MB,轻量便于下载学习。目前已有4099人学习下载,适合用于产品展示、数据可视化或教学演示等场景。通过学习可掌握3D变换核心参数、动画驱动方式及交互触发逻辑,为后续开发更复杂的Web 3D效果打下基础。
1. Html5+CSS3实现3D旋转效果:不用纯视频也能让页面"立"起来
当你做作品展示页,想让产品图标、卡片、相册封面在屏幕上转起来时,第一反应往往是Three.js或canvas,但如果你只需要一个旋转的立方体、一张翻转卡片、一个3D轮播相册,Html5+CSS3实现3D旋转效果才是更轻、更好维护的选择。它用HTML5填内容,靠CSS3的transform、perspective和animation把平面元素推到三维空间里,不写一行canvas也能做出观众看得懂的立体动效。这个方案适合活动页前端、做作品集的学生以及想快速出效果的项目负责人。下面我会从坐标系原理讲到可复现代码,再把这几个月遇到过的问题和验证技巧一并交代。
2. 先理解CSS3 3D坐标系:transform、perspective、preserve-3d 的配合逻辑
2.1 从translate3d到rotateX/Y/Z:坐标系和旋转顺序的门道
CSS3的3D变换不是玄学,它有一套固定的坐标规则:X轴向右,Y轴向下,Z轴指向屏幕外,也就是朝向观察者这边。默认情况下,rotateX(45deg)会让元素的上边向后倒、下边往前翘;rotateY(45deg)会让右边朝外转、左边朝里转;rotateZ(45deg)则是顺时针方向。方向记不住没关系,我一般在开发者工具里手动改角度,比背坐标系效率高。
一个元素的3D变换可以从两个方向叠加:
/* 先旋转再平移 */ .a { transform: rotateY(90deg) translateZ(100px); } /* 先平移再旋转 */ .b { transform: translateZ(100px) rotateY(90deg); }这两段代码结果完全不同。.a是先转Y轴90度,再把元素沿“旋转后的Z轴”推出去,最终位置在中心点的右侧偏前;.b是先把元素沿原始Z轴推出100px,再整体旋转,最终位置和朝向都不一样。CSS的transform函数从右往左作用于元素坐标系,但对初学者来说,只需要记住一条实操经验:做立方体六个面时,统一把“旋转”写在“translate”前面,先转到目标面向,再沿Z轴推出半边长。
transform-origin也会影响旋转轨迹。默认值是center center,元素绕着自身中心转。如果改成left center,rotateY(180deg)就会变成翻书的动作,元素以左边为轴翻到另一侧。做翻转卡片时,我偶尔会用它来做边缘翻页效果,但核心3D体我仍然用默认中心原点,这样各面之间的相对位置更好计算。
调试3D变换还有一个高效技巧:在浏览器开发者工具里给元素添加transform,然后按住键盘上下键微调角度,实时看效果。你可以拿一个图片元素单独试rotateX、rotateY、rotateZ,再对比加了perspective之后的变化,坐标系的理解会快很多。
2.2 perspective视距与perspective-origin:立体感的比例尺
如果只做旋转不设置perspective,元素看起来就像在平面上被斜着压扁,完全没有近大远小的纵深感。perspective的值相当于观察者眼睛到Z=0平面的距离,单位是px。值越大,透视越平缓;值越小,近大远小越明显,但边缘变形也越夸张。常见的活动页我取600px到1000px,移动端正屏展示我习惯用900px左右,既能看到明显深度,又不会让文字扭曲。
perspective有两种写法,效果差异要分清:
/* 写法A:写在场景父级,所有后代共享视场 */ .scene { perspective: 800px; } /* 写法B:写在单个元素的transform里 */ .item { transform: perspective(800px) rotateY(45deg); }写法A会为整个场景建立一个统一的3D渲染上下文,多个子元素之间近大远小的关系是一致的,更接近真实空间。写法B是每个元素各自计算透视投影,如果有多个元素同时旋转,它们之间不会形成一个稳定的空间关系。所以我会在3D场景的根节点上写perspective,让所有子元素在同一个空间里互相配合。
perspective-origin决定观察点的位置。默认是50% 50%,也就是从正前方看。改成left top,画面会呈现从左上角俯视的效果;改成right bottom,会像是从右下角往上看。比如做产品展示时,我会用perspective-origin: 50% 30%,让物体视觉焦点偏上一点,看起来更自然。
值得注意,perspective只对直接子元素生效。如果你在.scene上写了perspective,但.scene下面隔了一层.wrapper再放旋转体,透视就会被.wrapper拦截,最终效果不对。这种层级问题在后面的避坑章节里还会出现,提前记住能省很多排查时间。
2.3 preserve-3d 与 backface-visibility:平面渲染和3D空间的分界线
默认情况下,CSS渲染会把元素和它的后代当成一个平面来处理。即使你对子元素写了rotateY(45deg)和translateZ(100px),如果没有transform-style: preserve-3d,浏览器会在某个层级把这些3D位移压平,最终看起来像一张图片在平面上做不规则运动。
需要把这句话划重点:preserve-3d不是全局开关,它只作用于当前元素的直接子元素。如果子元素内部还有更深层的3D结构,中间每一层都要继续设置preserve-3d,否则再往下的深度信息也会被拍扁。一个常见的层级结构是:
.stage { perspective: 800px; } .cube { transform-style: preserve-3d; transform: rotateY(30deg); } .face { transform: rotateY(45deg) translateZ(100px); }这里.stage提供视场,.cube负责保留3D空间,.face才能把translateZ的距离真正展现出来。如果.cube缺少preserve-3d,.face的旋转和平移就会在.stage的平面上下文里被扭曲,最后你看到的不是一个立起来的板子,而是一块被斜切的图片。
backface-visibility控制元素背面是否可见。默认是visible,所以翻转卡片时,你会看到背面文字镜像;设置为hidden后,元素转到超过90度就直接消失,直到转回来才重新出现。在立方体Demo里,我会给每个面加上backface-visibility: hidden,这样减少背面重叠时的绘制干扰。但注意,如果元素没有处于一个有效的3D渲染上下文中,这个属性可能被浏览器忽略。
兼容性方面,现代浏览器对这三个属性支持都不错,但如果你要兼容旧版移动浏览器,需要补-webkit-前缀。我一般用Autoprefixer统一处理,不在手写CSS里分散注意力。
3. 写一个能跑的3D旋转Demo:纯CSS实现立方体与卡片翻转
3.1 立方体Demo:六个面怎么摆、旋转动画怎么写
先做一个最简单的立方体,方便你看到每个面的位置是否正确。HTML结构如下:
<div class="scene"> <div class="cube"> <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>对应的CSS:
.scene { width: 200px; height: 200px; margin: 100px auto; perspective: 800px; } .cube { width: 200px; height: 200px; position: relative; transform-style: preserve-3d; animation: cubeRotate 10s infinite linear; } .face { position: absolute; width: 200px; height: 200px; background: rgba(80, 140, 220, 0.55); border: 2px solid #3060a0; border-radius: 4px; font-size: 32px; display: flex; align-items: center; justify-content: center; } .front { transform: translateZ(100px); } .back { transform: rotateY(180deg) translateZ(100px); } .right { transform: rotateY(90deg) translateZ(100px); } .left { transform: rotateY(-90deg) translateZ(100px); } .top { transform: rotateX(90deg) translateZ(100px); } .bottom { transform: rotateX(-90deg) translateZ(100px); } @keyframes cubeRotate { from { transform: rotateX(0deg) rotateY(0deg); } to { transform: rotateX(360deg) rotateY(360deg); } }这段代码里有几个参数值得展开说明。.scene的width和height要跟立方体边长一致,perspective: 800px控制视距。想看到更强烈的立体感,把perspective降到500px,但画面边缘会有比较明显的拉伸。.face里用了半透明背景,作用是为了让你能看到背面和侧面的重叠关系,调试完再换成实色。
六个面的摆放逻辑是统一的:每个面先旋转到它应该朝向的方向,再沿Z轴推出立方体边长的一半。因为边长是200px,半边长就是100px,所以translateZ(100px)。front不用旋转,直接推到前方;back先转180度再推,这样它朝外;right先转90度再推,朝右;left转-90度;top沿X轴转90度朝上;bottom沿X轴转-90度朝下。
动画cubeRotate同时让rotateX和rotateY从0加到360度,所以立方体会在水平、垂直两个方向同时翻滚。10s infinite linear表示10秒一圈、无限循环、匀速运动。如果想要更有弹性的手感,把linear改成cubic-bezier(0.4, 0, 0.2, 1),但循环动画的终点和起点要正好衔接,否则会出现顿挫。
3.2 图片墙/卡片翻转:真实项目中更常用的两种用法
展示产品时,用得最多的不是旋转立方体,而是翻转卡片和旋转图片墙。翻转卡片的HTML结构如下:
<div class="card-scene"> <div class="card"> <div class="card-face card-front">产品标题</div> <div class="card-face card-back">产品详情说明</div> </div> </div>对应CSS:
.card-scene { width: 260px; height: 180px; perspective: 700px; } .card { width: 100%; height: 100%; position: relative; transform-style: preserve-3d; transition: transform 0.6s; } .card:hover { transform: rotateY(180deg); } .card-face { position: absolute; inset: 0; backface-visibility: hidden; border-radius: 10px; } .card-front { background: #f0f8ff; display: flex; align-items: center; justify-content: center; } .card-back { transform: rotateY(180deg); background: #2c3e50; color: #fff; padding: 16px; }这里用transition而不是animation,因为翻转是一个状态切换:鼠标进入时转过去,移出时自动转回来。transition: transform 0.6s只定义过渡时长,浏览器自己补中间帧。正面和反面都要加backface-visibility: hidden,否则正面转到背面时会把背面镜像透出来。背面预转180度,这样当card整体转180度时,card-back恰好变成正对观察者,文字方向正确。
图片墙也很常见,核心思路是把所有图片围成一个圆环,让容器旋转。以5张图为例,固定容器宽度,每个图片基于中心做Y轴旋转,角度间隔是360除以5,等于72度:
.carousel { position: relative; transform-style: preserve-3d; animation: carouselRotate 12s infinite linear; } .carousel-item { position: absolute; width: 180px; height: 240px; backface-visibility: hidden; } .carousel-item:nth-child(1) { transform: rotateY(0deg) translateZ(280px); } .carousel-item:nth-child(2) { transform: rotateY(72deg) translateZ(280px); } .carousel-item:nth-child(3) { transform: rotateY(144deg) translateZ(280px); } .carousel-item:nth-child(4) { transform: rotateY(216deg) translateZ(280px); } .carousel-item:nth-child(5) { transform: rotateY(288deg) translateZ(280px); }translateZ(280px)是旋转圆环的半径。如果图片大小是180px宽,我会让半径至少是图片宽度的1.5倍,否则转起来图片之间会相互遮挡。每一次旋转角度递增72度,配合容器整体旋转,就能形成一个旋转木马效果。注意给每个.carousel-item加backface-visibility: hidden,否则你在圆环外侧会直接透过第一张图看到后面镜像的图片,非常影响观感。
3.3 配合HTML5结构:语义化与canvas之外的纯CSS方案
如果只是做Demo,用div没问题。但放到真实项目里,我会把每一面用figure包裹,里面放图片和figcaption,让内容有语义:
<div class="scene"> <div class="cube"> <figure class="face front"> <img src="front.jpg" alt="前景图"> <figcaption>正面</figcaption> </figure> </div> </div>这种结构的好处是:图片有alt描述,文字依然在HTML里,搜索引擎能爬到,CSS禁用时至少能看平面内容。而canvas方案需要在绘图代码里处理事件、文本换行、高清屏适配,如果你只是要一个旋转相册,这些复杂度是很不划算的。
纯CSS方案的边界也要讲清楚:它没有真正的3D光照,也没有精确的遮挡排序。多个面在空间中重叠时,浏览器按照DOM顺序和Z坐标综合决定绘制顺序,偶尔会出现“应该被挡住的面反而透出来”的情况。如果要做复杂的3D粒子或者光照场景,还是得用Three.js。但反过来,你可以把CSS3 3D当成一个低成本原型设计工具,先确认交互方向,再决定要不要上更重的方案。
4. 动画性能与交互:transition和animation的取舍、触发方式
4.1 transition vs animation:什么时候用哪个
| 维度 | transition | animation |
|---|---|---|
| 触发方式 | hover、focus、class切换 | 自动执行或class控制 |
| 关键帧 | 只有开始和结束两个状态 | 支持0%到100%多关键帧 |
| 循环控制 | 无法直接循环 | 支持infinite和次数 |
| 反向过渡 | 状态还原时自动反向 | 需要单独写reverse或重置 |
| 适用场景 | 卡片翻转、菜单弹出 | 自动旋转、循环轮播 |
能用transition的地方我尽量不用animation。transition的中间状态由浏览器自动计算,移出时还能自动倒着回去,比如翻转卡片鼠标移开后自动复原,这是animation要写两套keyframes才能模拟的。而立方体一直在转,没有明确状态切换,就用animation。一个常见的误用:有人把弹出的遮罩层做成animation,切换显隐时又要担心动画重复触发,不如用transition配合class切换干净。
4.2 鼠标拖拽旋转与滚动驱动的3D效果:非跳转交互的常见做法
纯CSS没有鼠标状态,所以拖拽旋转要配合JavaScript。常见做法是监听mousedown、mousemove、mouseup,记录位移,把角度写到目标元素的style.transform上:
const cube = document.querySelector('.cube'); let isDragging = false; let startX = 0; let startY = 0; let rotX = -20; let rotY = 0; document.addEventListener('mousedown', (e) => { isDragging = true; startX = e.clientX; startY = e.clientY; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const deltaX = e.clientX - startX; const deltaY = e.clientY - startY; rotY += deltaX * 0.3; rotX -= deltaY * 0.3; cube.style.transform = `rotateX(${rotX}deg) rotateY(${rotY}deg)`; startX = e.clientX; startY = e.clientY; }); document.addEventListener('mouseup', () => { isDragging = false; });0.3是灵敏度系数,数值越大转得越快。如果你的场景自动旋转动画写在CSS里,那么JS写入的style.transform会覆盖CSS动画,两者不能同时生效。我会把自动旋转放在外层容器上,拖拽只改内层容器的transform,这样互不干扰。移动端需要把mousedown换成touchstart,mousemove换成touchmove,同时阻止默认滚动行为。
滚动驱动也一样,在scroll事件里根据页面滚动进度计算角度:
const progress = window.scrollY / (document.documentElement.scrollHeight - window.innerHeight); const scene = document.querySelector('.scroll-scene'); scene.style.transform = `rotateX(${progress * 360}deg)`;注意scroll事件触发频率很高,不能每一帧都写style,最好用requestAnimationFrame做节流,否则旋转会一卡一卡。
4.3 性能踩坑:will-change、图层合成与帧率
3D旋转动效卡顿,八成是动画属性选错了。浏览器能高效合成的属性主要是transform和opacity,尽量只改它们。如果旋转时同时改box-shadow、border-radius、filter,每一帧都要重新走布局和绘制,帧率必然掉。
will-change是个双刃剑。在动画元素上写will-change: transform可以让浏览器提前把元素提升到独立合成层,避免动画过程中反复计算层级。但它不是越多越好,每个合成层都占用GPU内存,一个页面上超过十几个反而会卡。我通常只给主旋转容器加will-change: transform,子元素不加。
在3D场景里,给容器设置perspective本身会创建3D渲染上下文。子元素数量多时,把不参与竞争的背景块设置backface-visibility: hidden,有些浏览器会优化绘制顺序。我还会用开发者工具的Performance面板录制动画,看Main线程有没有长任务,以及Layer边界是不是和屏幕一样大。如果合成层过大,用overflow: hidden限制一下,或者减小背景图尺寸。
5. 避坑指南:3D旋转效果最常见的五个翻车现场
5.1 现象:六个面叠成一团,完全看不出立体
原因:最常见的是忘了在旋转容器上写transform-style: preserve-3d。没有这个属性,所有面的transform只作用在各自平面,然后被父级拍扁,translateZ的深度被忽略。另一个原因是外层包装层有overflow: hidden或filter,这些属性在某些浏览器里会强制触发扁平化渲染。
解决:在旋转容器上加上transform-style: preserve-3d。如果外面还有包装层,检查包装层是否也保留了3D空间。overflow: hidden可以移到更外层的容器上,或者把filter去掉。我把这列为第一条,因为它是最容易让我们怀疑“CSS3 3D是不是个玄学”的坑。
5.2 现象:卡片翻转后,背面文字是镜像的
原因:两个面没有做背面处理。当你把.card旋转180度,正面转到后方,背面结构因默认可见,而且自身没有旋转,浏览器直接把背面原样显示。由于观察方向反了,文字就变成镜像。
解决:对两个面都加backface-visibility: hidden,并把背面预先transform: rotateY(180deg)。这样当card整体转180度时,背面自己也转180度,文本方向就正了。另一个细节:如果你用rotateX做上下翻转,背面也要用rotateX(180deg),不要混用方向。
5.3 现象:perspective写在父级没反应,写在子级却有效
原因:perspective属性只影响直接子元素的3D变换。如果你在.scene上写了perspective,但.scene和旋转体之间还隔着中间层,透视就会作用在中间层上,旋转体真正转的时候已经脱离透视上下文。
解决:检查DOM嵌套,perspective尽量放在离3D旋转体最近的那个父级上,中途不要穿插独立的transform。也可以采用transform: perspective(800px) rotateY(30deg)直接写在旋转体上,但这种方法会让每个后代各自计算透视,多面体场景里不容易统一。我习惯在.scene上写perspective,在.cube上写preserve-3d,层级简单清晰。
5.4 现象:低版本移动浏览器里3D被"拍扁"
原因:早期WebKit内核需要-webkit-perspective、-webkit-transform-style私有前缀,而且对backface-visibility支持不够稳定。内容透明、未开启硬件加速也会影响效果。
解决:用Autoprefixer补前缀,或者手动加-webkit-前缀。不要依赖backface-visibility做关键结构,因为有些浏览器在合层时忽略它。更实在的做法是做一个平面兜底:在容器上写一个普通的2D背景,即使3D失效,也不至于白屏。这种渐进增强的思路在兼容性测试中真的能救命。
5.5 现象:动画本身流畅,但切换到另一个页面再回来时卡一下
原因:页面切换触发整个动画重新计算,或者动画元素用的合成层被销毁重建。比如你把3D场景放在display: none的容器里,显示时浏览器要重新创建合成层,就会出现停顿。
解决:不要在动画运行期间把父容器display: none,用visibility: hidden或移到屏幕外。如果必须用display控制,可以在显示完成后再触发动画状态,不要同时监听多个变化。做单页应用时,我习惯先让3D场景处于隐藏位置,再用opacity切换,避免销毁图层。
6. 进阶:从旋转Demo到可交互的3D相册,一个验证学习成果的完整技巧
6.1 让旋转连续进行:animation的无限循环与贝塞尔曲线
在立方体Demo里,我用的是animation: cubeRotate 10s infinite linear。linear保证匀速运动,循环接缝处非常自然。如果你想让图片墙有缓动效果,可以用cubic-bezier(0.45, 0, 0.2, 1),但循环动画的终点必须和起点无缝衔接。一个安全做法是只做0%到100%完整一圈,并且保持同样的速度关键词,这样就不需要考虑二次补间的跳变。
6.2 加一点JavaScript:控制转速、暂停与恢复
用按钮控制动画,比拖拽更直观:
const cube = document.querySelector('.cube'); const btn = document.querySelector('#toggle'); btn.addEventListener('click', () => { const running = getComputedStyle(cube).animationPlayState === 'running'; cube.style.animationPlayState = running ? 'paused' : 'running'; });这里用animationPlayState直接控制动画播放。想改变转速,可以修改animationDuration,比如从10秒改成4秒,立方体会明显转得更快。注意拼接字符串时别把单位写错,animationDuration接收的是完整CSS时间值,例如'4s'。
6.3 我的验证习惯:每次写完都强制走一遍"三查一看"
最后分享一个我实际的验证流程。每次写完3D旋转效果,我会先开DevTools的Performance面板录制3秒动画,然后依次检查:第一查perspective有没有生效,把perspective从500px改成1500px,如果立体感变化不明显,说明透视没生效;第二查preserve-3d有没有被某级wrapper“吞”掉,在Elements面板选中某个面,看它的transform数值是否包含translateZ,如果变成0说明被拍扁了;第三查backface-visibility的隐藏状态是否符合预期,尤其是翻转卡片。一看是看console有没有合成相关的warning,比如提示“Layer with no backing store”或者“Layer is too rugmented”。从那以后我每次做3D效果都强制走一遍这套流程,遇到玄学问题基本都能快速定位。希望帮到你。
本文还有配套的精品资源,点击获取