当CSS谈了一场恋爱:解码两个小球的“亲吻”浪漫史

94 阅读9分钟

前言:代码里的浪漫,你读懂了吗?

在程序员的世界里,浪漫从不缺席——它藏在一行行代码的缝隙中,躲在像素级的动画细节里。
今天,我们要讲述的不是一段真实恋情,而是一场由 纯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(因 .ballrelative)定位。
  • 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-colorcolor 决定(通常为黑色)。
    • 视觉上形成“黑眼球”或“下垂眼”效果。
.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,就是一封写给浏览器的情书。

当你掌握了这些技术细节之后,请别忘了问自己:
“我的代码,能让人微笑吗?”

因为最动人的程序,从来不只是“能运行”,而是——
能让人心动。