前言:代码里的浪漫,你读懂了吗?
在程序员的世界里,浪漫从不缺席——它藏在一行行代码的缝隙中,躲在像素级的动画细节里。
今天,我们要讲述的不是一段真实恋情,而是一场由 纯CSS驱动的数字浪漫:两个白色小球,一左一右,在绿色背景的见证下,完成了一次心跳般的“亲吻”。
没有JavaScript,没有框架,仅凭HTML与CSS,开发者用@keyframes写情书,用transform表达心动,用opacity制造心跳暂停的瞬间。
这不仅是一段动画,更是一场关于定位、层级、时间轴与视觉欺骗的精密编排。
准备好进入这场由CSS主演的爱情短片了吗?
让我们一帧一帧,拆解这场“亲吻”背后的深情代码。
一、HTML 结构总览
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS Animation</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="container">
<!-- 女主 -->
<div class="ball" id="l-ball">
<div class="face face-l">
<div class="eye eye-l"></div>
<div class="eye eye-r"></div>
<div class="mouth"></div>
</div>
</div>
<!-- 男主 -->
<div class="ball" id="r-ball">
<div class="face face-r">
<div class="eye eye-l eye-r-p"></div>
<div class="eye eye-r eye-r-p"></div>
<div class="mouth mouth-r"></div>
<div class="kiss-m">
<div class="kiss"></div>
<div class="kiss"></div>
</div>
</div>
</div>
</div>
</body>
</html>
<div class="container">:整个动画的居中容器,确保两个小球位于页面中央。<div class="ball" id="l-ball">:代表女主的圆形身体,会向右移动靠近男主。<div class="face face-l">:女主的脸部容器,包含她的眼睛和嘴巴。<div class="eye eye-l">:女主的左眼,用边框模拟黑眼球。<div class="eye eye-r">:女主的右眼,与左眼对称放置。<div class="mouth">:女主的微笑嘴巴,由底部边框构成弧形。<div class="ball" id="r-ball">:代表男主的圆形身体,执行“飞吻”动作。<div class="face face-r">:男主的脸部容器,位置略靠左上方。<div class="eye eye-l eye-r-p">:男主的左眼,向上看,显得更有表现力。<div class="eye eye-r eye-r-p">:男主的右眼,与左眼一样具有特殊朝向。<div class="mouth mouth-r">:男主的嘴巴,会在亲吻瞬间短暂消失。<div class="kiss-m">:飞吻特效的包裹容器,控制红唇的显示时机。<div class="kiss">:第一个红唇图形,两个叠加形成完整“亲吻”效果。<div class="kiss">:第二个红唇图形,与前一个堆叠呈现立体感。
二、CSS 样式与动画逐行精解
第一部分:全局重置与基础布局
* {
margin: 0;
padding: 0;
}
- 选择器:通配符选择器
*,匹配所有元素。 - 作用:清除浏览器默认的外边距(margin)和内边距(padding)。
- 必要性:不同浏览器对元素(如 body、h1、ul)有不同默认样式,重置可保证跨浏览器一致性。
- 性能注意:通配符选择器效率较低,但在小型项目中可接受。
body {
background-color: #78e98f;
}
- 设置页面背景色为柔和的绿色(RGB: 120, 233, 143),营造清新氛围。
- 颜色值使用十六进制表示法。
.container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 238px;
}
-
position: absolute;
将.container脱离正常文档流,相对于最近的已定位祖先元素(无则为<html>)定位。 -
top: 50%; left: 50%;
将元素左上角定位在父容器(此处为视口)水平和垂直方向的 50% 处。 -
transform: translate(-50%, -50%);
关键居中技巧:translate(-50%, -50%)表示在 X 和 Y 方向分别向左、向上移动自身宽度和高度的 50%。- 效果:元素的中心点与父容器中心点重合,实现真正居中。
- 若仅用
top:50%; left:50%,则左上角居中,而非中心。
-
width: 238px;
设定容器宽度,确保两个.ball并排后总宽合理(每个 ball 宽 100px + border 8px × 2 = 116px,两球共 232px,加上间距约 238px 合理)。
第二部分:小球主体 .ball
.ball {
background-color: white;
border: 8px solid;
width: 100px;
height: 100px;
border-radius: 50%;
display: inline-block;
position: relative;
}
-
background-color: white;
填充内部颜色为白色。 -
border: 8px solid;
边框宽度 8px,样式为实线。由于未指定颜色,继承自color属性(默认黑色),形成深色外环。 -
width: 100px; height: 100px;
固定尺寸,创建正方形基础。 -
border-radius: 50%;
将正方形变为完美圆形。当值为 50% 时,圆角半径等于宽高一半,形成圆形。 -
display: inline-block;
允许元素像行内元素一样并排排列,同时保留块级元素的宽高设置能力。- 若用
block,两球会换行。 - 若用
inline,无法设置宽高。
- 若用
-
position: relative;
建立局部坐标系,使内部子元素(如.face)可以使用absolute定位,并相对于此球定位。
第三部分:面部装饰(伪元素)
.face {
width: 70px;
height: 30px;
position: absolute;
right: 0;
top: 30px;
}
.face是面部容器,位于.ball内部。position: absolute;相对于.ball(因.ball为relative)定位。right: 0; top: 30px;将面部右对齐,垂直偏移 30px(从球顶部算起)。
.face::after, .face::before {
content: "";
position: absolute;
width: 18px;
height: 8px;
background-color: #badc58;
top: 20px;
border-radius: 50%;
}
-
伪元素
::before和::after:- 是 CSS 生成的内容,不占用 HTML DOM 节点。
- 必须配合
content属性使用,即使为空字符串""。 - 可用于装饰、图标、提示等,减少 HTML 冗余。
-
此处创建两个椭圆形装饰(耳朵?腮红?),颜色为浅绿
#badc58。 -
border-radius: 50%将矩形变为椭圆。
.face::before {
right: -8px;
}
.face::after {
left: -5px;
}
- 分别将两个伪元素向左和向右偏移,形成对称效果。
right: -8px表示距离父元素右侧 -8px(即向左伸出 8px)。left: -5px表示距离左侧 -5px(向左伸出 5px)。
第四部分:眼睛与嘴巴
.eye {
width: 15px;
height: 14px;
border-radius: 50%;
border-bottom: 5px solid;
position: absolute;
}
-
创建圆形眼睛轮廓。
-
border-bottom: 5px solid;是关键:- 上、左、右边框透明(默认)。
- 下边框为实线,颜色由
border-color或color决定(通常为黑色)。 - 视觉上形成“黑眼球”或“下垂眼”效果。
.eye-l { left: 10px; }
.eye-r { right: 5px; }
- 左眼靠左 10px,右眼靠右 5px,形成面部对称布局。
.eye-r-p {
border-top: 5px solid;
border-bottom: 0px solid;
}
- 用于右侧角色的眼睛,将边框改为
border-top,模拟“向上看”或“眨眼”感。 border-bottom: 0移除底部边框,避免重叠。
.mouth {
width: 30px;
height: 14px;
border-radius: 50%;
border-bottom: 5px solid;
position: absolute;
bottom: -5px;
left: 0; right: 0;
margin: auto;
transform: translate(3px);
}
-
嘴巴是一个半圆弧:
border-radius: 50%形成椭圆。border-bottom: 5px solid只显示下半部分边框,形成“微笑嘴”。
-
bottom: -5px将嘴略微下移,超出球体边缘,增强立体感。 -
left: 0; right: 0; margin: auto;实现水平居中。 -
transform: translate(3px)微调位置,可能用于视觉对齐。
三、核心动画系统深度剖析
动画 1:女主小球靠近 (#l-ball)
#l-ball {
animation: close 4s ease infinite;
position: relative;
z-index: 100;
}
-
animation: close 4s ease infinite;close:调用名为close的关键帧动画。4s:动画周期为 4 秒。ease:缓动函数,先快后慢(贝塞尔曲线cubic-bezier(0.25, 0.1, 0.25, 1))。infinite:无限循环播放。
-
z-index: 100;
提升层叠顺序,确保在移动过程中始终显示在#r-ball之上,避免被遮挡。
@keyframes close {
0% { transform: translate(0); }
20% { transform: translate(20px); }
35% { transform: translate(20px); }
55% { transform: translate(0px); }
100% { transform: translate(0px); }
}
-
时间轴解析:
0%→20%(0.8s):从原点向右移动 20px。20%→35%(0.6s):保持在 20px 位置(停留)。35%→55%(0.8s):返回原点。55%→100%(1.8s):静止等待下一轮。
-
效果:模拟“害羞靠近 → 停顿 → 返回”的动作。
动画 2:女主面部晃动 (face-l)
.face-l {
animation: face 4s ease infinite;
}
@keyframes face {
0% { transform: translate(0) rotate(0); }
10% { transform: translate(0) rotate(0); }
20% { transform: translate(5px) rotate(-2deg); }
28% { transform: translate(0) rotate(0); }
35% { transform: translate(5px) rotate(-2deg); }
50% { transform: translate(0) rotate(0); }
100% { transform: translate(0) rotate(0); }
}
- 在
20%和35%时刻,面部向右平移 5px 并逆时针旋转 -2 度,模拟点头或晃动。 - 其余时间恢复原状。
- 与
#l-ball的移动同步,增强真实感。
动画 3:男主“飞吻”动作 (#r-ball)
#r-ball {
animation: kiss 4s ease infinite;
}
@keyframes kiss {
40% { transform: translate(0); }
50% { transform: translate(30px) rotate(20deg); }
60% { transform: translate(-33px); }
67% { transform: translate(-33px); }
77% { transform: translate(0px); }
}
-
关键帧行为:
40%:静止。50%(第 2 秒):突然向右移动 30px 并顺时针旋转 20 度,模拟“发射”动作。60%:瞬间跳回-33px(左侧远处),制造“红唇已飞出”的错觉。67%:保持隐藏状态。77%:回到原点,准备下一轮。
-
这是一种视觉欺骗技巧:通过快速位移隐藏主体,让观众注意力集中在“飞出的红唇”上。
动画 4:红唇出现 (kiss-m) 与嘴巴消失 (mouth-r)
.kiss-m {
position: absolute;
left: 20px;
top: 22px;
opacity: 0;
animation: kiss-m 4s ease infinite;
}
.kiss {
width: 13px;
height: 10px;
background-color: white;
border-left: 5px solid;
border-radius: 50%;
}
.kiss-m初始隐藏(opacity: 0)。.kiss使用border-left创建半个嘴唇形状,两个叠加形成完整“飞吻”。
@keyframes kiss-m {
0% { opacity: 0; }
55% { opacity: 0; }
66% { opacity: 1; }
66.1% { opacity: 0; }
}
- 在
66%时刻(第 2.64 秒)突然出现,仅持续 0.1%,形成“一闪而过”的飞吻效果。 - 时间点与
#r-ball的跳跃动画精确对齐。
.mouth-r {
animation: mouth-m 4s ease infinite;
}
@keyframes mouth-m {
0% { opacity: 1; }
54.9% { opacity: 1; }
55% { opacity: 0; }
66% { opacity: 1; }
66.1% { opacity: 1; }
}
- 男主的嘴在
55%时消失(表示“张嘴亲吻”)。 - 在
66%时恢复,与红唇出现同步,暗示“亲吻完成”。
四、技术亮点总结
| 技术 | 说明 |
|---|---|
| 绝对定位 + transform 居中 | top:50%; left:50%; transform: translate(-50%,-50%) 是最可靠的居中方案。 |
| 伪元素装饰 | ::before/::after 减少 HTML 节点,提升语义清晰度。 |
| border 技巧 | 用 border-bottom 创建眼睛,是轻量级图形绘制方法。 |
| z-index 控制层级 | 防止动画过程中元素遮挡。 |
| 精确时间轴编排 | 多个 @keyframes 协同工作,形成连贯叙事。 |
| 视觉欺骗 | 通过快速位移+透明度变化模拟“飞出”效果。 |
结语:从代码到情感,CSS的诗意表达
我们常以为,代码是理性的、冰冷的,是用来解决问题的工具。
但这段“亲吻小球”的动画提醒我们:技术也可以有温度,逻辑也能承载情感。
它没有一行JavaScript,却让两个静态的小球拥有了“性格”——
一个羞涩靠近,一个大胆示爱;
一次飞吻,一次回眸;
在4秒的循环里,上演了一场永不落幕的甜蜜邂逅。
这正是前端开发的魅力所在:
我们不仅是程序员,更是视觉诗人、交互导演、用户体验的造梦者。
一个transform可以是心跳,一个opacity可以是脸红,一段@keyframes,就是一封写给浏览器的情书。
当你掌握了这些技术细节之后,请别忘了问自己:
“我的代码,能让人微笑吗?”
因为最动人的程序,从来不只是“能运行”,而是——
能让人心动。