资讯详情

资讯详情

HTML + CSS 3D 响应式个人作品集:原理、性能与移动端实践

去年一年我断断续续看了几十个个人作品集站点做得精致的不少但真能把 3D 玩明白、又不牺牲加载速度和移动端体验的比例低得可怜。大部分所谓“3D 作品集”要么是套了个旋转立方体做装饰要么整个页面卡成幻灯片要么在手机上直接退化成一张张平铺的图片。HTML 实现响应式 3D 个人作品集网站这个需求看起来简单实际上它同时压着三条线视觉上的空间感、代码层面的渲染性能、以及不同屏幕尺寸下的布局稳定性。任何一条崩了整个站点的观感就塌了。这篇内容我想聊的就是这三条线怎么同时站住。核心是用原生 HTML CSS 3D 变换配合少量 JavaScript不引入重型 3D 引擎就能做出有纵深、有视差、能在手机上顺滑运行的立体作品集。适合正在准备个人主页的设计师、前端求职者、独立开发者也适合已经会写 CSS 但对perspective、preserve-3d这些参数始终一知半解、每次调都是靠感觉碰运气的朋友。下面我会把坐标系原理、参数计算、断点设计、完整代码和踩过的坑全摊开讲尽量做到你看完能直接抄。1. 项目整体设计与思路拆解1.1 先想清楚作品集站点到底在展示什么动手写第一行代码之前有个问题必须先回答作品集站点的第一诉求是什么。它跟营销落地页不一样跟产品官网也不一样。访客通常只有几十秒耐心他们来是为了在两三屏之内判断“这个人做东西的水平如何、审美在不在线、有没有跟自己业务匹配的案例”。所以整个页面的信息密度和视觉冲击力之间要做取舍。我见过太多反面案例首页先来一段 3D 粒子宇宙飞行动画飞了八秒才看到第一张作品图手机上还得左右滑动才能触发。这种设计在本地演示时很唬人一旦上线跳出率极高。真正有效的做法是把 3D 当成信息组织的工具而不是炫技的舞台——用空间的纵深关系表达作品的层级、用透视的变化引导视线、用轻微的视差让页面有呼吸感而不是把所有东西都甩到用户脸上。这也是我倾向于用 CSS 3D 变换而不是 WebGL 的根本原因CSS 3D 的“上限”刚好卡在作品集需要的那条线上。它能做出卡片堆叠、倾斜画廊、深度分层、鼠标跟随的轻微旋转这些足够撑起一个专业感很强的作品集同时它天然继承 DOM 的可访问性、文本可选择、SEO 可抓取、响应式布局体系完整。而 WebGL 一旦用上这些优势全都要用额外的工程量找补回来。1.2 CSS 3D 与 WebGL 两条路线的真实取舍技术选型这块我不喜欢讲“各有优劣”的和稀泥话直接摆结论以文字和图片为主的作品集优先 CSS 3D需要真实光照、模型、大量粒子的场景才考虑 WebGL。对比维度CSS 3D 变换WebGL如 Three.js首屏体积几乎为 0随 HTML/CSS 一起加载引擎本体通常 150KB 以上压缩后仍不小文本可访问性原生 DOM可选中、可被搜索抓取需额外做 DOM 镜像层或降级方案响应式适配直接吃媒体查询和容器宽度需要手动处理 canvas 尺寸与 DPR交互实现成本改transform即可调试直观需要场景、相机、射线拾取一整套真实光照与材质无只能靠渐变和阴影模拟完整支持低端机表现中等主要吃合成层性能差GPU 弱时明显掉帧适合的内容形态卡片、画廊、分层视差、倾斜面板3D 模型展示、粒子、复杂空间场景这张表的关键不在“谁更强”而在“谁的成本结构更匹配需求”。作品集的内容形态九成以上是“图片 标题 一段描述”本质是二维信息硬塞进三维引擎里等于用卡车运一个茶杯——不是运不了是每一环都在浪费。还有一个常被忽略的点CSS 3D 的降级路径极其干净。你把transform相关的几行 CSS 抽掉页面立刻变成一个正常的二维瀑布流内容一点不少。WebGL 做不到这一点canvas 里渲染失败就是一片空白还得额外维护一套降级 DOM。对于个人站点这种长期没人维护的项目降级路径的优雅程度直接决定了它三年后还能不能看。1.3 整体架构一层视口、一层舞台、若干卡片结构上我采用三层模型这个模型借鉴了舞台剧的物理逻辑理解起来很直观。视口层viewport负责定义观察者的位置对应 CSS 里的perspective相当于你站在台下第几排看舞台。这一层只设参数不做变换。舞台层stage整体可以旋转、倾斜的对象对应transform-style: preserve-3d。鼠标移动时轻微转动的就是它相当于舞台本身在缓缓转动。卡片层card舞台上的每一件展品各自有独立的位移和角度。它们可以被单独放大、翻转、向前推进相当于台上每一块布景板。分三层的意义在于职责隔离。如果所有变换都堆在一个元素上你想调“整体旋转 5 度”就会连带影响每个卡片的姿态改一个参数要重新校准全部。分层之后舞台负责全局视角卡片负责个体姿态两边互不干扰调参效率差别巨大。这个道理跟写代码时拆函数是一样的只不过对象换成了 DOM 节点。2. 3D 坐标系的底层参数先把坐标系吃透2.1 perspective 到底在改变什么很多人对perspective的理解停留在“数值越小越夸张”。这个描述没错但不够用因为它没告诉你夸张的物理来源。真实世界里近处的物体在视网膜上投影大远处的投影小这个比例关系由观察距离决定。CSS 的perspective就是那个观察距离单位是像素。假设你给一个 300px 宽的元素加上translateZ(150px)同时父容器设perspective: 600px。此时元素到观察者的距离从 600px 变成 450px视觉放大倍率约等于 600 / 450 ≈ 1.33元素看起来变成约 400px 宽。如果perspective设成 2000px距离从 2000 变成 1850倍率只有 1.08几乎看不出变化。这个公式可以直接用来反推参数缩放倍率 perspective / (perspective - translateZ)记住这个式子你在调“卡片向前浮出多少”的时候就不会再瞎试了。.viewport { perspective: 1400px; perspective-origin: 50% 45%; overflow: hidden; } .card { transform: translateZ(80px); }还有一个配套参数perspective-origin它定义的是观察点的水平垂直位置默认50% 50%也就是正中。把它调到50% 35%视觉上相当于你从稍高处俯视舞台卡片的上边缘会显得更“远”。这个参数在移动端特别有用——竖屏时人的视线落点偏上把 origin 往上提一点透视感会更自然不会出现下边缘被压扁的怪异感。提示perspective必须写在父元素上才对子元素生效。写在自己身上无效这是新手最常见的第一个错误。2.2 preserve-3d 与那些悄悄压平你的属性transform-style: preserve-3d的作用是让子元素保留三维空间关系。不加它父元素的 3D 变换会被“压平”成一张二维图片再交给子元素子元素之间的 Z 轴距离全部失效整个场景瞬间变成一个平面。这个属性本身不难难的是它会被一些看起来毫不相干的属性破坏。实际项目里我踩过的有这几个overflow: hidden在 3D 容器上使用会强制创建压平上下文。想做裁切就得把overflow提到更外层且外层不能参与 3D 变换。filter任何滤镜哪怕只是blur(0)都会触发渲染层重建并压平 3D。做卡片模糊效果时要在内部元素上加不能加在preserve-3d的节点上。opacity小于 1部分浏览器同样会压平。淡入动画如果在 3D 容器上做动画结束要记得复位。will-change用错层级加在 3D 容器上可能引发意外的层合成行为。/* 正确裁切在外层3D 在内层 */ .viewport { overflow: hidden; } .stage { transform-style: preserve-3d; -webkit-transform-style: preserve-3d; }注意写完 3D 效果发现“完全没有立体感”九成不是参数问题是某个祖先元素上有overflow: hidden或filter。排查时一路往上翻 DOM逐个临时禁用很快就能定位。2.3 变换顺序不是可交换的transform里多个函数的排列顺序会直接改变最终结果这是线性代数的基本性质。rotateX(20deg) translateZ(100px)和translateZ(100px) rotateX(20deg)得到的坐标完全不同前者是先倾斜坐标系再沿新坐标系的 Z 轴前进后者是先沿原 Z 轴前进再原地倾斜。用生活化的说法想象你手里端着一杯水。先往前走十米再转身九十度和先转身九十度再往前走十米最后站的位置完全不一样。CSS 变换就是这么回事。对作品集的卡片布局我总结出的顺序是先定位translate再旋转rotate最后做局部偏移translateZ。这样写的好处是卡片在舞台上的“落点”稳定旋转只影响姿态不影响位置调布局时不容易打架。.card { --i: 0; transform: translateX(calc(var(--i) * 40px)) translateY(calc(var(--i) * 20px)) rotateY(calc(var(--i) * 8deg)) translateZ(calc(var(--i) * -80px)); }这段代码用了一个递增序号--i让每张卡片依次右移、下移、旋转、后退形成一条向后退去的立体扇面。实际调参的时候只要改这四个系数就能在“紧密堆叠”和“散开排列”之间连续过渡比一张张手写transform高效得多。3. 响应式骨架构思一套结构撑起三种屏幕3.1 用 CSS 变量把断点和尺寸收成一个总开关传统媒体查询的写法是一个断点一段样式重复三遍之后改一个间距要跳三处改。我更倾向的做法是把尺寸和间距全部变量化媒体查询里只改变量值具体规则只写一次。:root { --card-w: clamp(240px, 30vw, 380px); --card-gap: clamp(16px, 3vw, 44px); --stage-depth: clamp(60px, 8vw, 120px); --tilt-max: 8deg; --persp: clamp(900px, 120vw, 1600px); } media (max-width: 768px) { :root { --card-w: 82vw; --card-gap: 12px; --stage-depth: 40px; --tilt-max: 4deg; --persp: 900px; } }这里clamp()是关键。它接受最小值、首选值、最大值三个参数浏览器会取中间那个值并裁剪到区间内。clamp(240px, 30vw, 380px)的意思是卡片宽度优先按视口宽度的 30% 走但绝不低于 240px也绝不超过 380px。一条规则覆盖了从手机到宽屏的全部情况中间不需要任何断点。这个写法的直接收益是中间尺寸不再有跳变。老式的media (min-width: 768px)加media (min-width: 1200px)方案在 768px 和 767px 之间会突然变一次平板横竖屏切换时能明显看到布局抖一下。clamp()是连续的从 320px 到 2560px 一路平滑。3.2 移动端该不该保留 3D这个问题我纠结过挺久最后的答案是保留但要换一种 3D。桌面端的 3D 是“多卡片散开 鼠标视差”三到五张卡片铺在视野里靠鼠标位置驱动倾斜。这个模式搬到手机上立刻出问题屏幕窄卡片散开会互相遮挡没有鼠标视差失去输入源性能也撑不住多个大图层同时合成。移动端我改成了“纵向单列 单张卡片深度翻转”。同一时刻只有一张卡片处于主视觉位置卡片入场时做一个约 15 度的 Y 轴翻转配合 Z 轴推进滚动到下一张时上一张退出。这样既保留了空间感又把同时参与 3D 合成的图层数量从五个降到一两个性能压力骤降。media (max-width: 768px) { .stage { display: flex; flex-direction: column; gap: var(--card-gap); transform: none; /* 移动端取消整体倾斜交给滚动驱动 */ } .card { transform: rotateY(0) translateZ(0) translateY(0); } .card.is-in { animation: card-rise 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) forwards; } keyframes card-rise { from { opacity: 0; transform: perspective(900px) rotateY(14deg) translateZ(-80px); } to { opacity: 1; transform: perspective(900px) rotateY(0) translateZ(0); } } }注意移动端我把perspective直接写进了transform函数里而不是依赖父容器的perspective属性。原因是部分移动端内核对父级perspective的继承处理不一致尤其在某些应用内置浏览器里父级透视会直接失效导致动画变成平面缩放。写成perspective()函数是把这个参数绑定在元素自身兼容性更稳。3.3 字号与触控目标不能只靠 rem作品集站点的文字层级通常很清晰大标题、作品名、一句描述、标签。字号我给的是三档全部用clamp()与视口挂钩.title { font-size: clamp(1.75rem, 5vw, 3.5rem); } .card-title { font-size: clamp(1rem, 2.2vw, 1.375rem); } .card-desc { font-size: clamp(0.8125rem, 1.5vw, 0.9375rem); }另外有个细节很容易被忽略倾斜和旋转的卡片上文字的可读性会下降。旋转角度超过 12 度时小字号文字会明显发虚尤其在低 DPI 屏上。我的处理方式是给卡片内容区加一点反向补偿——卡片转rotateY(8deg)内容文字所在的内层容器转rotateY(-3deg)让文字保持在接近正对屏幕的角度。这一步纯属经验没有任何规范要求但实际观感差别很大。触控目标方面移动端所有可点击元素的最小尺寸我给到 44×44 CSS 像素。3D 卡片本身尺寸足够但卡片上的“查看详情”按钮和底部分页点很容易做小这两个位置要单独检查。4. 逐段实现从卡片堆叠到可交互的3D画廊4.1 HTML 结构语义化且方便变换结构我尽量保持语义干净不为了样式往里塞额外的 div。每张作品就是一个article标题用h2页面主标题用h1图片用img并带loadinglazy和明确的宽高属性防止布局偏移。section classworks aria-labelledbyworks-title h2 idworks-title classsection-titleSelected Works/h2 div classviewport div classstage idstage article classcard style--i: 0 a classcard-link href./work-01.html div classcard-media img src./assets/w1.webp alt作品一预览图 width800 height600 loadinglazy /div div classcard-body h3 classcard-title项目名称/h3 p classcard-desc一句话说明做了什么、解决了什么。/p ul classcard-tagsli交互/lili前端/li/ul /div /a /article !-- 其余卡片同理只需递增 --i -- /div /div /section--i用内联样式写在元素上是为了让 CSS 能读到序号做递增计算。这个技巧比给每张卡片写一串.card:nth-child(1) { ... }要优雅得多加一张卡片只需要复制一段 HTML 改个数字。内联样式只承载这一个数值不涉及视觉样式所以不违背样式与结构分离的原则。4.2 3D 卡片的核心样式.viewport { perspective: var(--persp); perspective-origin: 50% 45%; overflow: hidden; padding-block: 6vh; } .stage { display: flex; justify-content: center; align-items: center; gap: var(--card-gap); transform-style: preserve-3d; transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)); transition: transform 0.35s ease-out; will-change: transform; } .card { flex: 0 0 var(--card-w); border-radius: 18px; overflow: hidden; background: #16181d; box-shadow: 0 24px 60px -18px rgba(0, 0, 0, 0.65); transform-style: preserve-3d; transform: rotateY(calc(var(--i) * 7deg)) translateZ(calc(var(--i) * -70px)); transition: transform 0.5s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.5s ease; } .card:hover, .card:focus-within { transform: rotateY(0deg) translateZ(70px) scale(1.03); box-shadow: 0 40px 80px -20px rgba(0, 0, 0, 0.8); z-index: 10; }几个细节值得单独说overflow: hidden加在.card上是为了裁切图片的圆角但注意它会把这张卡片内部的 3D 压平。因为卡片内部本来就没有需要保留 3D 的子元素所以这个副作用无害。如果是想让卡片内部也有立体层比如图片浮在文字上方就得把裁切换成clip-path或者给内部元素单独加圆角。z-index: 10在preserve-3d上下文里其实是无效的——三维场景下的层叠顺序由 Z 轴的几何位置决定不是由 z-index 决定。这里写它是为了照顾不支持 3D 的降级环境在那种环境下它会按正常的层叠规则生效。看起来是冗余代码其实是有意为之的兼容保险。backface-visibility: hidden我只加在做翻转的卡片上。不加的话卡片转到背面时图片会左右镜像显示看起来像出了 bug。但也不能无脑全加某些老版本内核对这个属性的处理会导致整张卡片消失用之前记得实测目标环境。4.3 鼠标视差与触摸拖拽的统一处理视差的核心是让舞台跟着指针位置轻微转动。直接绑mousemove改transform会有一个典型问题高频触发的样式写入会让浏览器在每一帧都重新计算稍微复杂一点的页面就掉帧。我的做法是用requestAnimationFrame加缓动把原始事件频率和渲染频率解耦。const stage document.getElementById(stage); let rafId null; let targetX 0, targetY 0; let curX 0, curY 0; const EASE 0.08; function schedule() { if (rafId null) rafId requestAnimationFrame(tick); } function tick() { curX (targetX - curX) * EASE; curY (targetY - curY) * EASE; stage.style.setProperty(--rx, (curY * -6).toFixed(2) deg); stage.style.setProperty(--ry, (curX * 10).toFixed(2) deg); const settled Math.abs(targetX - curX) 0.001 Math.abs(targetY - curY) 0.001; if (settled) { rafId null; } else { rafId requestAnimationFrame(tick); } } function onPointer(e) { const p e.touches ? e.touches[0] : e; const w window.innerWidth; const h window.innerHeight; targetX (p.clientX / w - 0.5) * 2; targetY (p.clientY / h - 0.5) * 2; schedule(); } window.addEventListener(pointermove, onPointer, { passive: true }); window.addEventListener(pointerleave, () { targetX 0; targetY 0; schedule(); });这里用pointermove而不是分别绑mousemove和touchmove是因为 Pointer Events 天然统一了鼠标、触摸、触控笔三种输入代码量少一半行为也一致。加{ passive: true }是告诉浏览器这个监听器不会调用preventDefault从而允许滚动不被阻塞这对移动端的滚动流畅度影响很大。缓动系数EASE 0.08是我试出来比较舒服的值。调大到 0.2跟手感强但会有抖动感调到 0.03非常丝滑但明显滞后像隔着一层水。0.06 到 0.10 之间是比较通用的区间具体看你的页面节奏。还有个细节settled判断。如果不做这个判断requestAnimationFrame会永远循环下去即使指针不动也在每帧写样式白白耗电。加上这个判断后动画收敛到接近目标值就自动停掉下次指针移动再加schedule()重新启动。移动端这一点尤其重要页面挂后台之前能省不少电。4.4 入场动画与交错延迟卡片一次性全部出现立体感会打折扣。我给它加了一个交错入场每张卡片根据--i计算延迟依次从远处飞入。.card { opacity: 0; animation: card-in 0.7s cubic-bezier(0.2, 0.8, 0.2, 1) forwards; animation-delay: calc(var(--i) * 90ms); } keyframes card-in { from { opacity: 0; transform: rotateY(calc(var(--i) * 7deg)) translateZ(calc(var(--i) * -70px - 220px)); } to { opacity: 1; transform: rotateY(calc(var(--i) * 7deg)) translateZ(calc(var(--i) * -70px)); } }延迟用calc(var(--i) * 90ms)五张卡片总动画时长 0.7 0.36 1.06 秒。这个节奏我调过好几版全部同时出现0ms 间隔显得廉价间隔 200ms 以上又会让用户等得不耐烦。90ms 是个比较讨巧的值既有递进感又不拖沓。注意交错动画的延迟别用在transition上用animation-delay。如果用transition你需要手动加类名触发还要处理重复触发的问题反而更麻烦。5. 性能与兼容那些文档里不写的坑5.1 移动端 3D 失效的四类原因线上出问题时现象往往是“桌面好好的手机上一片平”。我整理过排查顺序按出现频率从高到低现象常见原因处理方式完全无立体感祖先元素有overflow: hidden或filter逐级禁用排查把裁切挪到最外层卡片显示但无透视perspective写在了元素自身而非父级移到父容器或用perspective()函数动画闪烁/消失backface-visibility与老内核不兼容移除该属性改用旋转角度控制掉帧严重同时参与合成的图层过多减少可见卡片数给非活动卡片加visibility: hidden第四类最容易被忽视。3D 变换会让元素提升为独立的合成层每个层都要占 GPU 纹理内存。五张 400×500 的卡片在 3 倍屏下纹理内存占用大约是 400×500×3×3×4 字节×5 ≈ 72MB这还只是卡片本身不含阴影和模糊。中低端安卓机的 GPU 纹理预算普遍在 100MB 到 200MB 之间一超标就开始掉帧甚至直接白屏。我的优化手段是给不可见的卡片加visibility: hidden让浏览器把它们的合成层回收掉。同时把卡片的阴影从box-shadow换成事先渲染好的渐变色块因为大面积高斯模糊阴影在 3D 变换下会被反复重绘开销比想象中大得多。5.2 移动端的视口高度问题100vh在移动浏览器上是个经典陷阱。地址栏收起和展开时视口高度会变但vh单位不会跟着变导致页面底部被裁掉一截或者多出一块空白。.hero { min-height: 100vh; min-height: 100dvh; /* 支持时覆盖动态视口高度 */ }dvh是动态视口高度会随地址栏状态实时更新。写法上先写vh做兜底再写dvh覆盖不支持dvh的旧浏览器自动忽略第二行支持的就用新的。这个技巧现在已经是标配了但还有不少人在用100vh硬扛。5.3 无障碍与降级别把拒绝动效的用户挡在门外有些用户会因为前庭功能敏感而关闭系统动效操作系统层面提供了prefers-reduced-motion媒体查询。作品集网站如果无视这个设置鼠标一移动整个页面晃对这部分用户是很不友好的体验。media (prefers-reduced-motion: reduce) { .stage { transform: none !important; transition: none !important; } .card { animation: none !important; opacity: 1; transform: rotateY(0) translateZ(0); } * { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; } }这段规则把所有的 3D 倾斜和入场动画关掉页面退化成干净的二维列表。功能一个不少只是没了动效。这不是“高级优化”是基本素养——你的视觉表达不应该建立在别人的不适之上。另一层降级是supports。虽然现代浏览器对transform-style: preserve-3d的支持已经很好了但保险起见还是可以写supports not (transform-style: preserve-3d) { .stage { transform: none; } .card { transform: none; opacity: 1; } }6. 常见问题与排查技巧实录6.1 高频问题速查表问题现象排查方向解决方案卡片重叠顺序错乱3D 上下文中z-index失效调整translateZ值用几何位置排序文字在斜面上发虚旋转角度过大 缺少文字补偿内容区反向旋转 3 到 5 度滚动时整个页面抖动3D 元素参与滚动合成给滚动容器加overscroll-behavior: contain首次加载闪烁图片未预留尺寸导致布局偏移给img写死width和height动画结束后卡顿will-change未释放动画结束移除该属性或用animationend清理卡片在 Safari 上消失缺少-webkit-前缀补齐-webkit-transform-style等6.2 几个我反复踩的具体坑第一个坑will-change滥用。这个东西的定位是“提前告诉浏览器我要变了你准备好”但很多人把它当成性能万能药给所有卡片都加上。结果每个卡片都提前提升为合成层内存占用翻倍本来流畅的页面反而卡了。正确用法是只在即将发生动画的元素上加动画结束后移除。我在实际项目里的处理是给舞台加卡片不加因为卡片的变换由 CSS 的:hover触发浏览器自己会处理提升。第二个坑滚动容器的overflow连锁反应。页面主体嵌在一个overflow-y: auto的容器里容器内部又要做 3D这两者天然冲突。解法是把 3D 场景做成position: fixed的独立层滚动只影响内容层两层通过滚动进度做视觉联动。这个改动听起来大实际上就是把 DOM 结构拆成两块改完之后性能问题一次性解决。第三个坑WebP 图片在 3D 变换下的锯齿。卡片倾斜时图片边缘会出现明显的锯齿或者模糊。原因是浏览器在合成时对旋转的纹理做了双线性插值而图片本身的尺寸又不是整数倍缩放。我的做法是把图片的显示尺寸控制在原始尺寸的 0.5 倍到 1 倍之间避免二次采样同时给卡片加image-rendering: auto让浏览器自己选插值算法。实测下来图片按 2 倍尺寸导出、显示时缩到 0.5 倍是清晰度和体积之间比较好的平衡点。第四个坑键盘用户的焦点可见性。用:focus-within给卡片做悬停效果的时候记得同时给链接加清晰的outline。我见过不少站点用outline: none一刀切鼠标用户没感觉键盘用户彻底迷路。正确写法是outline: 2px solid currentColor; outline-offset: 4px;视觉上克制但存在。6.3 调试 3D 的三个小手法浏览器开发者工具有个Rendering面板里面可以打开“Paint flashing”和“Layer borders”。前者会把每次重绘的区域闪成绿色你能直观看到哪些元素在疯狂重绘后者会画出合成层边界3D 元素会显示成黄色边框一眼就能看出有没有意外提升的图层。第二个手法是临时给preserve-3d的容器加一个艳丽的背景色。如果背景色出现在子元素上方说明 3D 上下文被破坏了子元素被压到了父元素的平面里。这个方法简单粗暴但定位问题极快。第三个手法是把所有transform值写成可控的 CSS 变量然后在控制台里直接改。比如document.documentElement.style.setProperty(--persp, 800px)实时看到透视变化比改代码刷新快得多。最后分享一个我自己的习惯每次调完 3D 参数我会把浏览器窗口缩到 360px 宽再用设备模拟器切到低端安卓机型跑一遍。绝大多数问题都是在这个尺寸下暴露出来的桌面端再怎么调都觉得没问题。3D 这个东西很吃实际设备的表现参数在开发机上漂亮不代表用户在通勤路上的手机上看着舒服。把最窄的屏幕当主场景来设计往上放大反而容易反过来就难了。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →