资讯动态

纯CSS3心形loading动画:伪元素拼接、心跳关键帧与排错实战

发布时间:2026/10/26 5:46:32 来源:尧图企业网站定制
红色的心形 loading 特效是很多人学 CSS3 动画时绕不开的一个小练习。它看起来只是“画个心再让它跳一下”但真正动手做的时候坑比想象中多——心形画出来是个方块、跳着跳着形状塌了、加了阴影结果影子是方形的、在 flex 里被挤成一条线。我从最早用三个 div 拼心形到后来用两个伪元素搞定中间反复改过好几版也见过不少同学卡在同样的地方。这篇就把一个纯 CSS3 心形 loading 从几何原理到动画编排、再到封装成可复用组件、最后排错完整拆一遍。适合刚接触 CSS3 transform 和 animation、想弄明白“为什么这么写”的人也适合已经会写、但总在细节上翻车的朋友。全程不依赖任何第三方库一个 HTML 标签就够了。1. 一个纯 CSS 心形难在哪先说结论心形本身不难难的是“让它稳定地像心形”。CSS 不是画图工具它没有路径概念你能用的只有盒子、圆角、变换和伪元素。所以画心形本质上是用几个基本图形拼出一个视觉上成立的心再靠一次旋转把方向调正。拼法一旦定了后面所有的动画、配色、响应式都得围着这套拼接关系转所以第一步的结构设计非常关键改起来成本很高。1.1 心形的几何本质三块拼图加一次旋转把心形拆开看它其实就是两个圆加一个正方形。正方形旋转 45 度之后三个角朝下变成一个尖角剩下两个角分别被两个圆盖住两个圆相交的地方自然形成心形顶部的那个凹槽。这个拼法之所以成立是因为圆的半径和正方形边长相等的时候圆和正方形的边能视觉上对齐接缝处看不出明显断裂。具体来说设正方形边长为s正方形本体尺寸s × s整体旋转-45deg第一个伪元素是一个直径s的圆圆心对准正方形上边的中点也就是top: -s/2; left: 0第二个伪元素也是直径s的圆圆心对准正方形右边的中点也就是top: 0; left: s/2。旋转之后正方形原来右下角那个顶点被转到正下方成为心尖两个圆的圆弧在顶部交汇形成凹陷。这套数值关系不需要调只要边长统一接缝就永远是对的。很多人画出来心形“歪了”或者“胖了”几乎都是因为两个圆的直径和正方形边长不一致比如圆写了120px但正方形是100px接缝立刻裂开。我把这个拼法理解成“三块同尺寸的拼图”一块方的两块圆的方的那块转个角度两块圆的往上和往右各贴一半。想清楚这层后面所有的尺寸推导都变成同一个变量--size的倍数关系改一个值全身跟着动。1.2 为什么不用 SVG 或图片场景决定方案有人会问画个心形用 SVG 一条 path 就解决了为什么要费劲拼盒子这个问题得分场景。SVG 的优势是形状精确、可缩放、能做描边动画如果你的 loading 需要复杂形变、描边生长、路径跟随SVG 是更合适的选择。但如果你要的是体积极小、零请求、可被 CSS 变量随时改尺寸和颜色、能复用现有主题变量的一个小动效纯 CSS 方案的优势就出来了。我实际用下来纯 CSS 心形的几个实际好处没有额外资源请求首屏 loading 场景下不会出现“loading 图还没加载出来”的尴尬颜色、尺寸、速度全部走 CSS 变量换主题不用改结构动画是纯 transform 驱动走合成层性能开销极低挂到任何前端框架里都是一段 HTML 加一段 CSS不需要引入组件库。代价是形状的精度依赖数学关系改比例时得重新算而且做不了特别复杂的心形变体比如心电图那种波形。所以在选型阶段我的判断标准很简单形状固定、只需缩放和呼吸动画用 CSS形状要变、要描边、要做序列动画用 SVG。这篇讨论的是前者。2. 从零搭出心形骨架这一步的目标很明确不加动画先把一个静止的、红色实心的心形画出来而且保证它在任何尺寸下都不变形。结构上只需要一个容器加一个形状元素两个伪元素负责两个圆HTML 里不要出现多余的标签这是纯 CSS 方案的基本要求。2.1 两个伪元素的定位数值推导先定 HTML只有一个元素承担形状div classheart-loader rolestatus aria-label加载中 div classheart-shape/div /div外层的.heart-loader只负责留白和居中因为心形旋转之后伪元素会明显超出.heart-shape的盒子范围如果不给它留足位置视觉上会显得偏心甚至被相邻元素压住。关键的 CSS 是这样.heart-loader { --size: 96px; --color-main: #e8384f; display: inline-flex; align-items: center; justify-content: center; padding: calc(var(--size) * 0.55); box-sizing: content-box; } .heart-shape { position: relative; width: var(--size); height: var(--size); background: var(--color-main); transform: rotate(-45deg); } .heart-shape::before, .heart-shape::after { content: ; position: absolute; width: var(--size); height: var(--size); background: var(--color-main); border-radius: 50%; } .heart-shape::before { top: calc(var(--size) * -0.5); left: 0; } .heart-shape::after { top: 0; left: calc(var(--size) * 0.5); }这里的核心是四个数值我一个个说明为什么要这么写。position: relative必须加在.heart-shape上因为两个伪元素是position: absolute脱离文档流之后的定位基准就是最近的已定位祖先。如果漏了这一步伪元素会以整个页面或者别的已定位元素为基准位置直接飘走看起来就是“心形散架”了。伪元素必须写content: 这是伪元素生效的前提。content缺省时伪元素不生成页面上什么都不会出现。这个坑后面还会专门讲。两个伪元素的宽高都用var(--size)和本体保持一致这是保证圆弧和方边能对齐的关键。border-radius: 50%把方形变成圆百分比是相对自身宽高算的所以只要宽高相等就一定是个正圆。top: calc(var(--size) * -0.5)的意思是把圆向上推半个身位让它的圆心正好落在正方形上边的中点。同理left: calc(var(--size) * 0.5)是把第二个圆向右推半个身位。这里用calc加变量而不是硬编码数值是为了后面换尺寸时只改一个变量。注意calc里面乘负数时写法是calc(var(--size) * -0.5)不能写成calc(var(--size) * 0.5 * -1)虽然结果一样但前者可读性更好也少一层解析。2.2 旋转方向与视觉重心的校正旋转角度是最容易写反的地方。这套拼法出来的原始图形心尖是朝右上方的正方形未旋转时两个圆在上边和右边交汇凹陷在右上所以要把它转正必须逆时针转 45 度也就是rotate(-45deg)。如果写成rotate(45deg)得到的是一个倒过来的心心尖朝上、凹陷朝下看起来像个屁股。写对角度之后还有第二个问题视觉重心。因为旋转是围绕元素的几何中心进行的而心形的视觉重心比几何中心偏上心尖部分细长体积集中在上半部分所以旋转完成后心形在容器里会显得略微偏下。我第一次做的时候没注意把它丢进一个按钮里看起来就是“心往下掉了一截”。修正办法有两个看你的使用场景选修正方式写法适用场景用容器补偿外层加padding-bottom或translateY独立 loading 组件位置无所谓只是视觉居中调整 transform-origintransform-origin: 50% 60%需要精确对齐基线、和文字并排显示用负 margin 拉回margin-top: calc(var(--size) * -0.1)快速修补不想动结构我一般用第一种给外层容器多留一点下边距因为 loading 组件通常单独占位微调上下不影响别人。如果是内联在按钮里的小图标用transform-origin更干净不会影响布局流。还有一点容易被忽略旋转之后整颗心占的宽度其实比--size大。正方形对角线是s√2 ≈ 1.414s加上两个圆往外各溢出半个身位整体的实际渲染宽度接近1.9s。所以外层容器的padding我给了0.55 × size两边加起来正好覆盖溢出部分避免被兄弟元素挤到。3. 心跳节奏的编排不只是 scale 来回骨架搭好之后动画是第二阶段。很多人第一版写出来的效果是“匀速放大缩小”看着像呼吸而不像心跳。心形的动画之所以要单独调是因为人对“心跳”这个动作有很强的节奏预期两下急促的搏动然后是相对长的停顿。这个节奏感靠单一的ease-in-out是调不出来的必须在关键帧分布上做文章。3.1 双峰心跳曲线的关键帧分布真实的心跳节律在视觉上可以简化成一次较强的收缩、快速回落、一次较弱的收缩、然后是一个明显的舒张停顿。映射到scale上就是两个峰值中间有一个不完全回到 1 的低谷末尾有一段静止。keyframes heart-pump { 0% { scale: 1; } 12% { scale: 1.14; } 26% { scale: 1; } 38% { scale: 1.10; } 56% { scale: 1; } 100% { scale: 1; } }这里有几个数值上的取舍值得说。第一两个峰值不是一样大的。第一个峰1.14第二个峰1.10这样更接近真实的双搏动感。如果两个峰一样大看起来就变成机械的往复少了那种“第二下弱一点”的自然感。第二两次跳动之间的间隔很短12% 到 26% 只有 14% 的时长但第二次跳动之后到 56% 才开始停。也就是说收缩要快舒张要慢这个比例关系是心形动画像不像的关键。很多教程写的是对称的效果明显差一截。第三56% 到 100% 是一段完全静止的保持。这段静止不能省因为心跳的本质是“有间歇的重复”没有静止段就是一坨持续抖动的红块。这段静止占比接近一半视觉上才有“等一下再跳”的感觉。配合的时间函数也有讲究。我试过linear太生硬、ease-in-out有点软、cubic-bezier(.25, .8, .35, 1)前段冲得快、后段收得住最后这个比较接近肌肉收缩的质感。整段动画时长用1.2s ~ 1.6s都合理短于 1s 会显得焦虑长于 2s 就显得懒散。我默认给1.4s。3.2 transform 被覆盖旋转与缩放的层级拆分这是这条路上最经典的坑也是我见过最多人卡住的地方。骨架阶段用了transform: rotate(-45deg)动画阶段又要用transform: scale()如果直接写.heart-shape { transform: rotate(-45deg); animation: heart-pump 1.4s infinite; } keyframes heart-pump { 0% { transform: scale(1); } 50% { transform: scale(1.14); } 100% { transform: scale(1); } }动画一跑起来心形立刻变回一个正方形。原因很简单transform是一个整体属性动画关键帧里的transform会完全覆盖声明块里的transform而不是叠加。于是旋转角度丢了只剩缩放。这个问题有三种解法我按推荐程度排。第一种把旋转写进每一帧。最直接也最不容易出错keyframes heart-pump { 0% { transform: rotate(-45deg) scale(1); } 12% { transform: rotate(-45deg) scale(1.14); } 26% { transform: rotate(-45deg) scale(1); } 38% { transform: rotate(-45deg) scale(1.10); } 56% { transform: rotate(-45deg) scale(1); } 100% { transform: rotate(-45deg) scale(1); } }缺点是每帧都要重复一遍旋转改角度时容易漏改。但优点是逻辑最直白兼容性最好所有浏览器都认。第二种拆层级。外层做旋转内层做缩放。结构上多一个 div但职责清晰div classheart-shape div classheart-pulse/div /div.heart-shape { transform: rotate(-45deg); } .heart-pulse { width: 100%; height: 100%; background: inherit; animation: heart-pump 1.4s infinite; }不过一旦拆了层伪元素是挂在.heart-shape上的缩放的是内层两个圆不会跟着放大接缝立刻裂开。所以拆层方案必须把伪元素挪到内层元素上等于整个形状的宿主换了一个。改起来比重写一遍还费劲除非你一开始就设计成两层。第三种用独立变换属性。这是新写法把旋转和缩放拆成两个互不干扰的 CSS 属性.heart-shape { rotate: -45deg; animation: heart-pump 1.4s cubic-bezier(.25, .8, .35, 1) infinite; } keyframes heart-pump { 0%, 26%, 56%, 100% { scale: 1; } 12% { scale: 1.14; } 38% { scale: 1.10; } }rotate和scale是独立的变换属性在动画里改scale不会影响rotate写起来最干净。这套属性在近几年的主流浏览器里都支持得不错但如果项目要兼容比较老的浏览器还是老实选第一种。注意rotate、scale、translate这些独立属性和transform混用时叠加顺序是固定的先独立属性后transform不要两套混着用很容易算不清最终效果。3.3 时间函数与停顿感的细微调校关键帧定好了之后剩下的就是微调。我一般会做三件事。第一件关掉动画的填充模式。animation-fill-mode默认是none动画结束后元素回到初始状态。对于无限循环的 loading 来说这个没影响但如果你做的是一个“加载完成后播放一次心跳”的效果就必须考虑forwards否则动画结束瞬间会跳回原形。第二件检查animation-direction。alternate会让动画来回播放看起来像是缩放正放一遍再倒放一遍节奏会翻倍。心跳动画千万不要用alternate就用默认的normal靠关键帧本身来控制回落。第三件在低端设备上降级。心形动画本身开销很小但如果页面里同时有几十个 loading 实例比如列表里每一项都在加载合成层压力会上来。我一般的做法是给 loading 容器限制数量或者用animation-play-state: paused在不可见时停掉。.heart-loader:not(.is-active) .heart-shape { animation-play-state: paused; }这一行加上之后配合 IntersectionObserver 做可见性判断列表页的 CPU 占用能降下来一大截。这个是实测有效的不是理论。4. 让红色有体积感光影分层纯色红心看起来是一块扁平的色块缺少立体感尤其在大尺寸下特别明显。想要有体积感核心思路是模拟一个固定方向的光源让靠近光的一侧亮、远离光的一侧暗。CSS 里能做这件事的工具就三个box-shadow、linear-gradient、filter: drop-shadow()。但它们用在拼接心形上的效果差别非常大。4.1 box-shadow 的轮廓陷阱与 drop-shadow 的替代先说个反直觉的结论给这个心形加box-shadow得到的影子是方的。因为box-shadow作用的是元素的盒子轮廓不是渲染后的形状。心形由三个盒子拼成你给本体加阴影得到的是正方形投影你给伪元素加阴影得到的是两个圆形投影。三者叠在一起视觉上就是一个方块加两个圆饼非常难看。我最早就是踩在这个坑里调了半天阴影参数怎么看都不对后来才反应过来是作用对象错了。正确的工具是filter: drop-shadow()它作用的是元素渲染后的实际轮廓包括所有伪元素合成之后的形状.heart-shape { filter: drop-shadow(0 0 calc(var(--size) * 0.12) rgba(232, 56, 79, 0.45)); }这行加上之后心形周围会有一圈柔和的红光形状完全贴合心形轮廓没有方块边。这个差别在深色背景上尤其明显是那种“一眼看上去专业”和“一眼看上去凑合”的区别。需要注意的是filter会创建新的层叠上下文和包含块如果心形内部还有绝对定位的元素比如我们后面要放的高光层定位基准可能受影响。不过对我们这个结构来说伪元素是相对.heart-shape定位的加filter不影响。还有一个细节drop-shadow的模糊半径不要给太大。给到尺寸的0.12倍左右比较合适再大就会糊成一团失去“发光”的锐度。颜色透明度我一般控制在0.4到0.5之间太浓会显得脏。4.2 渐变的错位问题与纯色分层方案第二个想做的事通常是给心形加渐变让它从上到下有个明暗过渡。但这里有个物理限制background不会从父元素继承到伪元素。你给.heart-shape设了linear-gradient两个圆还是独立的盒子它们的渐变从各自盒子的左上角开始算和本体的渐变对不上接缝处会出现一条明显的色带断层。有人会想到background: inherit这确实能让伪元素拿到父元素的背景声明但渐变的计算基准仍然是伪元素自己的盒子位置和尺寸都不同结果还是错位。想要对齐得手动调background-size和background-position把三个盒子的渐变坐标系统一到同一个虚拟画布上。这套操作极其繁琐而且每改一次尺寸就要重算一遍维护成本很高。我的实际方案是放弃渐变用纯色打底加分层阴影做体积。具体做法.heart-shape, .heart-shape::before, .heart-shape::after { background: var(--color-main); } .heart-shape { box-shadow: inset 0 0 calc(var(--size) * 0.25) rgba(0, 0, 0, 0.16); } .heart-shape::before { box-shadow: inset calc(var(--size) * 0.06) calc(var(--size) * 0.06) calc(var(--size) * 0.2) rgba(0, 0, 0, 0.18); } .heart-shape::after { box-shadow: inset calc(var(--size) * 0.06) calc(var(--size) * 0.06) calc(var(--size) * 0.2) rgba(0, 0, 0, 0.14); }用inset内阴影的好处是它只在元素内部绘制不会溢出到元素外所以不会破坏心形的轮廓。三个盒子各自加一点内阴影整体上会形成“边缘略暗、中间略亮”的错觉加上drop-shadow的外发光体积感就出来了。这里要注意内阴影的方向。因为整体旋转了-45deg伪元素的内阴影方向也跟着转了所以两个圆的阴影方向和本体是统一的都是从左上到右下看起来像同一个光源。如果手动给不同的伪元素设不同方向光就会被“打散”立体感反而消失。提示内阴影的模糊值不要超过尺寸的0.3倍超过之后边缘会糊掉心形的锐利感就没了。心形这种偏锐利的形状边缘清晰比柔和更重要。5. 封装成能反复用的 loading 组件到这一步视觉和动画都定下来了接下来要考虑的是“怎么在多个页面里复用”。如果每次用都复制一大段 CSS改一次配色要改十个地方那就失去了封装的意义。CSS 变量是这里最合适的工具因为它能在不改变结构的前提下把尺寸、配色、速度全部参数化。5.1 用 CSS 变量把尺寸、配色、速度全部参数化我把所有可变的值都提到外层容器上形状元素只引用不定义.heart-loader { --size: 96px; --color-main: #e8384f; --color-glow: rgba(232, 56, 79, 0.45); --speed: 1.4s; --easing: cubic-bezier(.25, .8, .35, 1); }这么做的关键原因是自定义属性会沿 DOM 树继承。伪元素虽然不在 DOM 里但它们在样式继承上是父元素的子节点所以能读到--size和--color-main。这样一来只需要在 HTML 上换个 class 或者写个内联 style就能得到不同尺寸的实例div classheart-loader style--size: 48px; --speed: 1.2s;/div div classheart-loader heart-loader--small/div我一般会再准备几个预设.heart-loader--small { --size: 40px; --speed: 1.1s; } .heart-loader--large { --size: 140px; --speed: 1.7s; }这里有一个小细节--size变小的时候内阴影和drop-shadow的模糊值如果写的是固定像素比例就失调了。所以我在所有阴影参数里都用calc(var(--size) * 系数)这样缩到 40px 时阴影也跟着缩视觉比例保持一致。这个习惯是我在做过一次“同一个 loading 在三个尺寸下效果完全不同”之后养成的后来所有基于变量的图形组件都这么写。还有一点--speed不只是时长它其实隐含了节奏。尺寸大的时候动画应该放慢一点因为同样的缩放比例大尺寸的位移量更大尺寸小的时候要快一点。所以我把--size和--speed捆绑在一起调而不是让用户单独改其中一个。5.2 挂到真实页面时的布局与显隐控制loading 组件在实际项目里最常见的问题不是长得不好看而是布局把它挤变形了。心形元素有固定尺寸但在 flex 容器里如果有多个子元素争抢空间flex-shrink默认是 1心形会被压扁画出来的形状立刻失真。.heart-loader { flex: 0 0 auto; flex-shrink: 0; }这两行基本能解决 90% 的挤压问题。如果是在 grid 里要检查列宽是不是auto固定尺寸的列不会压扁内容。第二个常见问题是层级遮挡。因为伪元素会溢出.heart-shape的盒子如果父容器有overflow: hidden比如卡片、滚动容器、圆角按钮心形的上半部分和右半部分会被裁掉看起来就是“缺了一块”。我的处理原则是loading 组件外层不要有overflow: hidden。如果确实身处一个必须裁剪的容器里就把padding加够让溢出部分落在裁剪区之外。比如外层给padding: calc(var(--size) * 0.6)配合box-sizing: content-box就能保证伪元素完全在容器内。第三个问题是显隐控制。用display: none隐藏会让动画暂停再显示时从头播放这个行为通常是我们想要的。但如果用visibility: hidden或者opacity: 0动画其实还在跑白耗 CPU。所以切换显示状态时优先用display或者配合animation-play-state: paused。最后补一个可访问性的小处理。loading 是一个状态提示不是装饰屏幕阅读器应该能读到div classheart-loader rolestatus aria-livepolite span classvisually-hidden加载中/span /div.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }另外对开启了“减少动态效果”的用户动画应该降级media (prefers-reduced-motion: reduce) { .heart-shape { animation: none; } }这时候静止的心形依然能表达“正在加载”只是不动。这是我在无障碍审查里被提过的问题后来成了固定动作。6. 排查实录心形塌陷、动画抽搐、颜色断裂前面讲的是怎么把它做出来这一节讲怎么写坏了之后怎么办。我把实际遇到过、也帮别人排查过的问题整理成一条排查链路按照“症状 → 可能原因 → 验证方法”走比直接给答案有用。6.1 心形变成方块或散架的几种典型原因症状一完全看不到心形页面上只有一个红色方块。按顺序排查这几点。第一伪元素有没有写content: 这是最基础的缺了它伪元素不渲染只剩本体那个正方形。第二伪元素有没有position: absolute如果漏了两个圆会按静态流排在正方形下面页面变成“一个方块加两个圆”的奇怪组合。第三border-radius是不是写成了50少了百分号或者50px前者无效后者在尺寸大于 100px 时就不够圆。第四父元素.heart-shape有没有position: relative没有的话绝对定位的基准就跑了。症状二心形只有一半或者上边/右边被切平了。这个几乎一定是overflow: hidden造成的。因为伪元素的圆有一半在父盒子外面任何裁剪都会把它们切掉。排查方法很简单在开发者工具里把可疑祖先元素的overflow改成visible试一下形状马上恢复就说明是它。症状三心尖朝上看起来是个倒置的心。旋转角度写反了。这套拼法必须是-45deg写成45deg就是倒的。这个错误特别容易发生在从别的教程里抄代码的时候因为有些教程的拼法是两个圆放在上边和左边配合45deg也能得到正心形。拼法和角度是绑定的不能拆开抄。症状四心形整体偏移不在容器中间。旋转会改变元素的视觉位置因为旋转是绕transform-origin默认是元素中心进行的而心形的视觉重心不等于几何中心。前面讲过用容器 padding 或者调整transform-origin补偿。症状五动画一跑心形变回方块。这就是transform被关键帧覆盖的经典问题回到 3.2 节的三种解法。这五条我建议做成一个清单新写一个心形的时候过一遍能省掉大量调试时间。6.2 动画卡顿与抖动的定位路径loading 动画出现卡顿排查思路是从“谁在重绘”入手。第一步在开发者工具的性能面板里录一段看合成层的情况。如果心形元素有独立的合成层通常由transform、filter、opacity触发帧率应该很稳。如果发现每一帧都在重绘多半是动画里改了会触发重排或重绘的属性比如width、height、box-shadow、background。心跳动画只应该改scale或者transform其他属性一律不动。第二步检查有没有在动画元素上加了filter: drop-shadow()同时又让scale动画。filter加transform的组合在部分设备上会走 CPU 合成帧率会掉。如果确实卡可以把drop-shadow换成静态的、不参与动画的光晕层或者干脆去掉发光只保留纯色。第三步看页面里有多少个 loading 实例。如果是列表页每项一个几十个元素同时跑无限动画即使每个都很轻加起来也会拖垮主线程。这种情况要么限制数量要么用animation-play-state配合可见性检测暂停不可见的实例。还有一种“抖动”不是性能问题而是视觉抖动心形在缩放时边缘出现锯齿或者闪烁。原因是缩放边界正好落在像素格的中间浏览器做抗锯齿时产生了位移感。解决办法是给元素加will-change: transform提前提升为合成层或者把尺寸设成偶数像素减少半像素渲染。注意will-change不要滥用。给几十个元素都加will-change: transform会占用大量显存反而更卡。只给当前活跃的那一两个实例加动画结束后移除。6.3 移动端与高倍屏下的细节偏差桌面调好的效果到了手机上经常有肉眼可见的差异。第一类是尺寸偏差。移动端的视口设置如果不正确px和 CSS 像素的映射会变心形看起来会偏小或偏大。建议尺寸用px而不是vw因为 loading 组件需要稳定的视觉大小跟着视口缩放没有意义。如果确实要做响应式在媒体查询里改--size变量就可以。第二类是动画时序差异。部分移动端浏览器在省电模式下会降低动画帧率甚至暂停后台标签页的动画。这种情况下animation会跳帧心跳看起来一顿一顿的。处理办法是接受它或者用更长的周期减少顿挫感。不要在省电模式下降级成静态图因为用户会以为页面卡死了。第三类是高倍屏的渲染精度。在 2 倍、3 倍屏上如果尺寸是奇数像素缩放动画的边缘会出现轻微模糊。我的习惯是把--size设成 4 的倍数比如 40、48、96、140这样在各种缩放比下都能落在相对整齐的像素格上。这个细节不明显但在深色背景配亮色心形的时候边缘的模糊感是能看出来的。第四类是圆角比例在小尺寸下失真。同样是border-radius: 50%尺寸 40px 的时候两个圆的弧度视觉上和 96px 时不太一样因为内阴影的模糊半径占比变大了。前面说过用calc按比例算阴影就是为了解决这个。改完之后的验证方法是把三个尺寸并排放在一起截图看看形状是不是同一颗心的等比缩放。最后分享一个我自己觉得挺有用的小技巧调试心形的时候给.heart-shape临时加一个outline: 1px dashed #999同时给伪元素各加一个不同颜色的半透明背景一眼就能看出三个盒子的相对位置有没有错。等形状调好之后把这几行删掉。这比反复在脑子里推演坐标快得多尤其是接缝对不齐的时候加个底色立刻就能看出是圆心偏了还是直径不对。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑