本文,我们将一起学习,使用纯 CSS,实现如下所示的动画效果:

上面的动画效果,非常有意思,核心有两点:

  1. 小球随机做 X、Y 方向的直线运动,并且能够实现碰撞到边界的时候,实现反弹效果
  2. 小球在碰撞边界的瞬间,颜色发生随机的变化

嗯?很有意思的效果。看上去,我们好像使用 CSS 实现了碰撞检测

然而,实际情况真的是这样吗?让我们一起一探究竟!

实现 X 轴方向的运动

这里其实我们并没有实现碰撞检测,因为小球和小球之间接触时,并没有发生碰撞效果。

我们只实现了,小球与边界之间的碰撞反应。不过这里,也并非碰撞检测,我们只需要设置好单个方向的运动动画,并且设置 animation-direction: alternate; 即可!

下面,我们一起来实现单个方向上的运动动画:

<div></div>
div {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
border-radius: 50%;
background: #0cf;
animation: horizontal 3s infinite linear alternate;
} @keyframes horizontal {
from {
left: 0;
}
to {
left: calc(100vw - 100px);
}
}

简单解读一下:

  1. 元素设置为 position: absolute 绝对定位,利用 left 进行 X 轴方向的运动
  2. 我们让元素 div 运动的距离为 left: calc(100vw - 100px),元素本身的高宽都是 100px,因此相当于运动到屏幕的最右侧
  3. 动画设置了 alternate 也就是 animation-direction: alternate; 的简写,表示动画在每个循环中正反交替播放

这样,我们就巧妙的实现了,在视觉上,小球元素移动到最右侧边界时,回弹的效果:

如法炮制 Y 轴方向的运动

好,有了上面的铺垫,我们只需要再如法炮制 Y 轴方向的运动即可。

利用元素的 top 进行 Y 轴方向的运动:

div {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
border-radius: 50%;
background: #0cf;
animation:
horizontal 3s infinite linear alternate,
vertical 3s infinite linear alternate;
} @keyframes horizontal {
from {
left: 0;
}
to {
left: calc(100vw - 100px);
}
} @keyframes vertical {
from {
top: 0;
}
to {
top: calc(100vh - 100px);
}
}

我们增加了一个 vertical 3s infinite linear alternate Y 轴的运动动画,实现小球从 top: 0top: calc(100vh - 100px); 的运动。

这样,我们就成功的得到了 X、Y 两个方向上的小球运动,它们叠加在一起的效果如下:

颜色的变化可以忽略,GIF 录制问题。

当然,此时的问题在于,缺少了随机性,小球的始终在左上和右下角之间来回运动。

为了解决这个问题,我们需要添加一定的随机性,这个问题也要解决,我们只需要让两个方向上运动时间不一致即可。

我们修改一下代码,让 X、Y 轴的运动时长不一致即可:

div {
position: absolute;
// ...
animation:
horizontal 2.6s infinite linear alternate,
vertical 1.9s infinite linear alternate;
}

如此一来,整体的效果就好上了不少,由于整个动画是无限反复进行的,随着时间的推进,整个动画呈现出来的就是无序、随机的运动

使用 transform 替代 top、left

当然,上面的效果基本上没有什么太大的问题了,但是代码层面不够优雅,主要有两点问题:

  1. 元素移动使用的是 topleft,性能相对较差,需要使用 transform 进行替代
  2. 代码中 hardcode 了 100px,由于 DEMO 中小球的大小是 100px x 100px,并且在动画的代码中也使用了 100px 这个值进行了运动终态的计算,因此如果想修改小球的元素大小,需要改动地方较多

上述两个问题,使用 transform: translate() 都可以解决,但是我们为什么一开始不用 transform 呢?

我们来尝试一下,使用 transform 替代 top、left:

div {
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
border-radius: 50%;
background: #0cf;
animation:
horizontal 2.6s infinite linear alternate,
vertical 1.9s infinite linear alternate;
}
@keyframes horizontal {
from { transform: translateX(0); }
to { transform: translateX(calc(100vw - 100%)); }
}
@keyframes vertical {
from { transform: translateY(0); }
to { transform: translateY(calc(100vh - 100%)); }
}

上述代码中,我们使用了 transform 替代 top、left 运动。并且,将动画代码中的 100px 替换成了 100%,这一点的好处是,在 transform: translate 中,100% 表示的是元素本身的高宽,这样,当我们改变元素本身的大小时,就无需再改变 @keyframes 中的代码,通用性更强。

我们来看看修改后的效果:

有点问题!预想中的效果并没有出现,整个动画只有 Y 轴方向上的动画效果。

这是什么原因呢?

其本质在于,定义的 vertical 1.9s infinite linear alternate 的垂直方向的动画效果覆盖了在其之前定义的 transform: translateX(calc(100vw - 100%)) 动画效果。

说人话就是 X、Y 轴的动画都使用了 transform 属性,两者之间造成了冲突

使用 animation-composition 进行动画合成

在之前,这种情况基本是无解的,常见的解决方案就是:

  1. 解法一:使用 topleft 替代 transform
  2. 解法二:多一层嵌套,将一个方向的动画拆解到元素的父元素上

不过,到今天,这个问题有了更好的解法!也就是 CSS animation 家族中的新属性 —— animation-composition

这是一个非常新的属性,表示动画合成属性,从 Chrome 112 版本开始支持。

有三种不同的取值:

{
animation-composition: replace; // 表示动画值替换
animation-composition: add; // 表示动画值追加
animation-composition: accumulate; // 表示动画值累加
}

本文不会详细介绍 animation-composition,感兴趣的可以看看 MDN 的属性介绍或者 XBOXYAN 大佬的这篇文章 -- 了解一下全新的CSS动画合成属性animation-composition

这里,基于上面的代码,我们只需要再多设置一个 animation-composition: accumulate 即可解决问题:

div {
animation:
horizontal 2.6s infinite linear alternate,
vertical 1.9s infinite linear alternate;
animation-composition: accumulate;
}

此时,我们就能通过一个元素,利用 transform 得到 X、Y 两个方向位移动画的合成效果,也就是我们想要的效果:

使用 steps 实现颜色切换

解决了位移动画的问题,我们就只剩下最后一个问题了,如何在碰撞的瞬间,实现颜色的切换?

这里也非常好解决,由于我们是知道每一轮 X、Y 方向上的动画时长的,那我们只需要在每次这个结点上,切换一次颜色即可。

并且,由于颜色不是过渡变换,而是直接的跳变,所以,我们需要用到 animation 中的 animation-timing-function: steps(),也就是步骤缓动函数。

animation-timing-function: steps() 还不太了解的,可能需要先补一补基础,可以看看这一篇文章:深入浅出 CSS 动画

举个例子,假设 X 方向上,单次的动画时长为 3s,那我们可以设置一个 steps(10) 的颜色动画,总时长为 30s,这样,每隔 3s 就会触发一次 steps() 步骤动画,颜色的变化就能够和小球与边界的碰撞动画发生在同一时刻。

那如何快速实现颜色的变化呢?利用 filter: hue-rotate() 即可快速实现颜色的变化。

理解一下下面的代码:

<div class="normal"></div>
<div class="steps"></div>
div {
width: 200px;
height: 200px;
background: #fc0;
}
.normal {
animation: colorChange 10s linear infinite;
}
.steps {
animation: colorChange 10s steps(5) infinite;
}
@keyframes colorChange {
100% {
filter: hue-rotate(360deg);
}
}

这里,我们用 filter: hue-rotate(360deg) 的改变,实现颜色的变化,观察下面的动图,理解 steps(5) 的作用。

  1. animation: colorChange 10s linear infinite 表示背景动画的过渡变化
  2. animation: colorChange 10s steps(5) infinite,这里表示 10s 的动画分成 5 步,每两秒,会触发一次动画:

效果如下:

理解了这一步,我们就可以把颜色的变化,也一起叠加到上述的小球变化中:

div {
animation:
horizontal 2.6s infinite linear alternate,
vertical 2s infinite linear alternate,
colorX 26s infinite steps(10),
colorY 14s infinite steps(7);
animation-composition: accumulate;
} @keyframes horizontal {
from { transform: translateX(0); }
to { transform: translateX(calc(100vw - 100%)); }
}
@keyframes vertical {
from { transform: translateY(0); }
to { transform: translateY(calc(100vh - 100%)); }
}
@keyframes colorX {
to {
filter: hue-rotate(360deg);
}
}
@keyframes colorY {
to {
filter: hue-rotate(360deg);
}
}

这样,我们就成功的得到了题图中的效果:

完整的代码,你可以戳这里:Random Circle Path

应用于图片效果、应用与多粒子效果

OK,上面,我们就把整个效果的完整原理剖析了一遍。

掌握了整个原理之后,我们就可以把这个效果应用于不同场景中。

譬如,假设我们有这么一张图片:

基于上面的效果,稍加改造,我们就可以得到类似的如下效果:

<div></div>
div {
width: 220px;
height: 97px;
background: linear-gradient(#f00, #f00), url(https://s1.ax1x.com/2023/08/15/pPQm9oT.jpg);
background-blend-mode: lighten;
background-size: contain;
animation: horizontal 3.7s infinite -1.4s linear alternate,
vertical 4.1s infinite -2.1s linear alternate,
colorX 37s infinite -1.4s steps(10),
colorY 28.7s infinite -2.1s steps(7);
animation-composition: accumulate;
}
@keyframes horizontal {
from { transform: translateX(0); }
to { transform: translateX(calc(100vw - 100%)); }
}
@keyframes vertical {
from { transform: translateY(0); }
to { transform: translateY(calc(100vh - 100%)); }
}
@keyframes colorX {
to {
filter: hue-rotate(2185deg);
}
}
@keyframes colorY {
to {
filter: hue-rotate(1769deg);
}
}

效果如下:

上面的 DEMO 是基于元素背景色的,本 DEMO 是基于图片的,因此这里多了一步,利用 mix-blend-mode,实现了图片颜色的变化。

完整的代码,你可以戳这里:CodePen Demo -- Random DVD Path

实现多粒子碰撞

OK,我们再进一步,基于上面的效果,我们可以实现各种有趣的粒子效果,如果同时让页面存在 1000 个粒子呢?

下面是我使用 CSS-Doodle 实现的纯 CSS 的粒子效果,其核心原理与上面的保持一致,只是添加了更多的随机性:

Amazing!是不是非常有趣,整个效果的代码基于 CSS-doodle 的语法,不超过 40 行。完整的代码,你可以戳这里:CSS Doodle - CSS Particles Animation

最后

总结一下,本文介绍了如何巧妙的利用 CSS 中的各种高阶技巧,组合实现类似于碰撞场景的动画效果。创建出了非常有趣的 CSS 动画,期间各种技巧的组合运用,值得好好琢磨学习。

OK,本文到此结束,希望本文对你有所帮助

想 Get 到最有意思的 CSS 资讯,千万不要错过我的公众号 -- iCSS前端趣闻

更多精彩 CSS 技术文章汇总在我的 Github -- iCSS ,持续更新,欢迎点个 star 订阅收藏。

如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。

震惊!CSS 也能实现碰撞检测?的更多相关文章

  1. Matplotlib数据可视化(3):文本与轴

      在一幅图表中,文本.坐标轴和图像的是信息传递的核心,对着三者的设置是作图这最为关心的内容,在上一篇博客中虽然列举了一些设置方法,但没有进行深入介绍,本文以围绕如何对文本和坐标轴进行设置展开(对图像 ...

  2. 震惊!很多人都不知道 CSS Grid 框架早就有了!

    前言 写作本文起源于知乎的一个问题:[CSS Grid 布局那么好,为什么至今没有人开发出基于 Grid 布局的前端框架呢?] 这篇文章拖沓了两个月,是因为真的不知道从哪里说好.这个问题的所有回答几乎 ...

  3. css重点章节复习—布局-边框-边距-浮动 (部分)

    css重点章节复习—布局-边框-边距-浮动 在第二个任务中,这一块的后面那条线真的弄了很久.起初也是在html里面写的代码.之后觉得这样不好,想到第一个页面中用到的border-bottom和bord ...

  4. div+css树形菜单

    自己做过的项目从来没有这种东西,但见过别人的项目都有,未免落伍,学来看看,也不知道自己找到的这个是不是正路子,先贴代码再分析. <!doctype html public "-//W3 ...

  5. 使用RGBa和Filter实现不影响子元素的CSS透明背景

    点击查看原文 问题 如果我们想要一个元素拥有半透明的背景,我们有两个选择: 使用CSS和 opacity 做一张 24-bit PNG 背景图片 在CSS中使用opacity有两个问题,一是为了适应所 ...

  6. CSS中link与import的区别

    一.import的用法 1,在html文件中 <style type="text/css"> @import url(http://www.dreamdu.com/st ...

  7. CSS3_元素拖曳原理_设置全局点击捕获_九宫格碰撞检测_自定义滚动条

    拖曳原理: 元素的初始位置 + 鼠标距离差 = 元素最终位置 使元素可以拖动 function dragElement(obj){ obj.onmousedown = function(e){ e = ...

  8. [0406]学习一个——Unit 1 Html、CSS与版本控制

    前言 最近发现了Github的Student认证,本来想用来注册Digital Ocean搭个梯子,结果注册验证不能用VISA借记卡=~=. 那么在这漫长的清明节假期里,只有学习能满足空虚的内心(划掉 ...

  9. 第五节 HTML&CSS -- 关于浮动和清除浮动的解说,以及两个大坑不要踩

    1.随便唠叨几句   这一节课我会对浮动元素和怎样清除浮动相关的技术进行一个讲解,同时,我会列举一些我们前端开发中常见的坑,希望大家以后不要在这些地方犯错.在开始今天的课程之前,有一个东西我需要先讲一 ...

  10. jQ 小球碰撞检测

    <!doctype html> <html> <head> <meta charset="UTF-8"> <title> ...

随机推荐

  1. 2021-03-06:go中,公共变量是协程安全吗?赋值操作是原子的吗?为什么?

    2021-03-06:go中,公共变量是协程安全吗?赋值操作是原子的吗?为什么? 福哥答案2021-03-06: 这是面试中被问到的.实力有限,真正的答案还不知道.我的想法是a=1是原子操作,a=b不 ...

  2. vue全家桶进阶之路1:前言

    Vue.js简称Vue,用于构建用户界面的渐进式框架. Vue是一款国产前端框架,它的作者尤雨溪(Evan You)是一位美籍华人,2014年2月,尤雨溪开源了一个前端开发库 Vue.js,2015年 ...

  3. 小H分糖果

    7-5 小H分糖果 (20 分) 小H来到一个小学分糖果,小学生们很听话,站成一排等着分糖果,小H将根据每个人的上次考试分数给一定的糖果,规则如下. 每个人都有自己分数ai​,代表上次考试成绩. 每个 ...

  4. ES6迭代器(Iterator)和生成器(Generator)

    平时我们迭代数据用得最多的应该就是for循环了 来看个简单的例子 var colors = ["red", "green", "blue"] ...

  5. python学习之一 OS 文件夹的操作和文件操作

    # OS模块 :查看一个文件夹下所有文件,这个文件夹有文件夹,不能用walk# -- coding: UTF-8 --import osimport sys#C:\Users\Administrato ...

  6. python selenium框架解决ip代理框不能自动化登录,解决pyautogui开不了多线程问题

    有时候我们使用python自动化框架的时候,打开一个网页的时候,它会出现出线这一种登录框,我们f12检查不了,用开发者工具强制检查里面没有任何属性. 那这时候我们就可以用到python第三方库:pya ...

  7. linux 递归和函数实验

    递归 作用:自己调用自己 1.例子:阶乘 2.遍历目录下所有文件 函数 1.函数能够接受一个参数,参数为用户名: 判断一个用户是否存在 如果存在,就返回此用户的shell 和 UID :并返回正常状态 ...

  8. Redis系列14:使用List实现消息队列

    Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 Redis系列4:高可用之Sentinel(哨兵模式) Redis系列5: ...

  9. jvm中类和对象定义存储基础知识

    1 类文件数据结构类型 Class文件结构主要有两种数据结构:无符号数和表 •无符号数:用来表述数字,索引引用.数量值以及字符串等,比如 图1中类型为u1,u2,u4,u8分别代表1个字节,2个字节, ...

  10. .NET 8 Preview 5发布,了解一下Webcil 是啥

    2023年6月13日 .NET 8 Preview 5,.NET 团队在官方博客发布了系列文章: Announcing .NET 8 Preview 5 ASP.NET Core updates in ...