当前位置:编程学堂 > 震惊! CSS也能实现碰撞检测吗?

震惊! CSS也能实现碰撞检测吗?

  • 发布:2023-10-05 13:15

这篇文章我们将一起学习使用纯CSS实现如下图的动画效果:

上面的动画效果很有趣。它有两个核心点:

  1. 小球在X、Y方向随机做直线运动,碰撞边界时可以达到反弹效果
  2. 小球碰撞边界的那一刻颜色随机变化

嗯?非常有趣的效果。 看起来我们正在使用CSS来实现碰撞检测

然而,事实真的是这样吗?我们一起来了解一下吧!

实现X轴方向的移动

其实我们这里并没有实现碰撞检测,因为球与球接触时并没有碰撞效果。

我们只实现了球与边界之间的碰撞反应。但这里,不是碰撞检测。我们只需要把运动动画设置为单一方向即可,设置animation-direction:alternate;就可以了!

现在,让我们实现单个方向的运动动画:

div {
    位置:绝对;
    顶部:0;
    左:0;
    宽度:100px;
    高度:100px;
    边界半径:50%;
    背景:#0cf;
    动画:水平3s无限线性交替;
}

@关键帧水平{
    从 {
        左:0;
    }
    到 {
        左:计算(100vw - 100px);
    }
}

简单说明:

  1. 元素设置为位置:absolute进行绝对定位,使用left沿X轴方向移动
  2. 我们让元素div向左移动距离为:calc(100vw - 100px),元素本身的高度和宽度为100px ,因此相当于移动到屏幕的最右侧
  3. 动画设置为alternate,即animation-direction:alternate的缩写;,表示动画在每次循环中交替向前和向后播放

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

与Y轴方向相同的移动

好了,有了上面的基础,我们只需要按照同样的方法来控制Y轴方向的运动即可。

使用元件的顶部进行Y轴方向的移动:

div {
    位置:绝对;
    顶部:0;
    左:0;
    宽度:100px;
    高度:100px;
    边界半径:50%;
    背景:#0cf;
    动画片:
        水平3秒无限线性交替,
        垂直3s无限线性交替;
}

@关键帧水平{
    从 {
        左:0;
    }
    到 {
        左:计算(100vw - 100px);
    }
}

@关键帧垂直{从 {
        顶部:0;
    }
    到 {
        顶部:计算(100vh - 100px);
    }
}
的运动。

这样我们就成功获得了小球在X、Y方向的运动情况。叠加后的效果如下:

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

当然,此时的问题是缺乏随机性,球总是在左上角和右下角之间来回移动。

为了解决这个问题,我们需要添加一定程度的随机性。这个问题也必须得到解决。我们只需要让两个方向的移动时间不一致即可。

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

div {
    位置:绝对;
    // ...
    动画片:
        水平2.6s无限线性交替,
        垂直1.9s无限线性交替;
}

这样整体效果就会好很多。由于整个动画是无限重复的,随着时间的推移,整个动画会呈现出无序、随机的运动

使用变换代替上、左

当然,上面的效果基本上没有什么大问题,只是代码水平不够优雅。主要有两个问题:

  1. 元素移动使用。性能比较差,需要更换为transform
  2. 代码硬编码为100px,因为DEMO中小球的大小为100px x 100px,动画代码中也使用了100px 这个值已经计算出了最终的运动状态,所以如果要修改小球的单元大小,需要改变很多地方

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

让我们尝试一下,使用变换代替顶部和左侧:

div {
    位置:绝对;
    顶部:0;
    左:0;
    宽度:100px;
    高度:100px;
    边界半径:50%;
    背景:#0cf;
    动画片:
        水平2.6s无限线性交替,
        垂直1.9s无限线性交替;
}
@关键帧水平{
    来自 { 变换:translateX(0); }
    到 { 变换:translateX(calc(100vw - 100%)); }
}
@关键帧垂直{
    来自 { 变换:translateY(0); }
    到 { 变换:translateY(calc(100vh - 100%)); }
}

在上面的代码中,我们使用transform来代替顶部和左侧的移动。另外,将动画代码中的100px替换为100%。这样做的好处是,在变换中:translate100%代表元素本身的高度和宽度。这样,当我们改变元素本身的大小时,就不需要改变@keyframes中的代码,这样就更加通用了。

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

有问题!预期的效果并没有出现。整个动画只有Y轴方向的动画效果。

这是什么原因呢?

的本质是定义的垂直1.9s无限线性交替的垂直动画效果覆盖了之前定义的transform:translateX(calc(100vw - 100%))动画影响。

说白了,X轴和Y轴动画都使用了transform属性,造成了两者之间的冲突

使用animation-composition进行动画合成

在以前,这种情况基本上是无法解决的。常见的解决方案是:

  1. 解决方案1:使用top, left代替transform
  2. 解决方案二:多加一层嵌套,将动画单向拆解到元素的父元素

不过,到今天为止,这个问题有了更好的解决方案!也就是说,CSS 动画家族中的新属性 - animation-composition

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

具有三个不同的值:

{
    动画合成:替换​​; // 代表动画值替换
    动画合成:添加; // 表示动画值相加
    动画合成:累积; // 表示动画值的累加
}

本文不会详细介绍动画构图。有兴趣的话可以阅读MDN属性介绍或者XBOXYAN老大的这篇文章——了解新的CSS动画合成属性animation-composition

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

div {
    动画片:
        水平2.6s无限线性交替,
        垂直1.9s无限线性交替;
    动画合成:累积;
}

此时,我们可以使用一个元素,使用transform来得到X、Y方向位移动画的复合效果,这就是我们想要的效果:

使用步骤实现颜色切换

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

这个也很好解决。由于我们知道X和Y方向上每轮动画的持续时间,因此我们只需要在该节点切换一次颜色即可。

而且,由于颜色不是过渡变换,而是直接跳转,所以我们需要使用动画中的animation-timing-function:steps(),也就是step easing函数。

动画计时函数:steps()如果你还不太了解,你可能需要先了解基础知识。你可以阅读这篇文章:CSS动画简单术语

例如,假设单个动画的持续时间为3s,则steps()步骤动画将被触发一次,颜色变化可以与之间的碰撞动画同时发生球和边界。

如何快速换色?使用滤镜:hue-rotate()快速实现颜色变化。

理解以下代码:

div {
    宽度:200px;
    高度:200px;
    背景:#fc0;
}
。普通的 {
    动画:colorChange 10s线性无限;
}
。脚步 {
    动画:colorChange 10s steps(5) 无限;
}
@keyframes colorChange {
    100% {
        滤镜:色调旋转(360deg);
    }
}

这里,我们使用滤镜:hue-rotate(360deg)来实现颜色变化。观察下面的动画即可了解steps(5)效果。

  1. 动画:colorChange 10s线性无限表示背景动画的过渡变化
  2. 动画:colorChange 10s steps(5)无限,表示10s动画分为5步。每两秒就会触发一个动画:

效果如下:

了解了这一步后,我们就可以将颜色变化叠加到上述球的变化中:

div {
    动画片:
        水平2.6s无限线性交替,
        垂直2s无限线性交替,
        colorX 26s 无限步(10),
        colorY 14s 无限步(7);
    动画合成:累积;
}

@关键帧水平{
    来自 { 变换:translateX(0); }
    到 { 变换:translateX(calc(100vw - 100%)); }
}
@关键帧垂直{
    来自 { 变换:translateY(0); }
    到 { 变换:translateY(calc(100vh - 100%)); }
}
@keyframes colorX {
    到 {
        滤镜:色调旋转(360deg);
    }
}
@关键帧颜色Y {
    到 {
        滤镜:色调旋转(360deg);
    }
}

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

完整代码可以点击这里:随机圆路径

适用于图片效果、应用程序和多粒子效果

OK,上面我们已经分析了整个效果的完整原理了。

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

例如,假设我们有这样一张图片:

在上述效果的基础上,稍微修改一下,我们可以得到类似的效果如下:

div {
    宽度:220px;
    高度:97 像素;
    背景:线性渐变(#f00,#f00),url(https://www.sychzs.cn/2023/08/15/pPQm9oT.jpg);
    背景混合模式:变亮;
    背景大小:包含;
    动画:水平3.7s无限-1.4s线性交替,
            垂直4.1s无限-2.1s线性交替,
            colorX 37s 无限 -1.4s 步骤(10),
            colorY 28.7s 无限 -2.1s 步骤(7);
    动画合成:累积;
}
@关键帧水平{来自 { 变换:translateX(0); }
    到 { 变换:translateX(calc(100vw - 100%)); }
}
@关键帧垂直{
    来自 { 变换:translateY(0); }
    到 { 变换:translateY(calc(100vh - 100%)); }
}
@keyframes colorX {
    到 {
        滤镜:色调旋转(2185deg);
    }
}
@关键帧颜色Y {
    到 {
        滤镜:色调旋转(1769deg);
    }
}

效果如下:

以上DEMO是基于元素背景色的。这个DEMO是基于图片的,所以这里多了一步,使用mix-blend-mode来实现图片颜色的变化。

完整代码可以点击这里:CodePen Demo -- 随机DVD路径

实现多粒子碰撞

好吧,让我们更进一步。基于以上效果,我们可以实现各种有趣的粒子效果。如果页面上同时有 1000 个粒子怎么办?

以下是我使用CSS-Doodle实现的纯CSS粒子效果。核心原理与上面相同,但增加了更多随机性:

太棒了!是不是很有趣呢?整个效果代码基于CSS-doodle语法,不超过40行。完整代码可以点击这里:CSS Doodle - CSS 粒子动画

终于

总结一下,本文介绍了如何巧妙地利用CSS中的各种高级技术来实现类似于碰撞场景的动画效果。一个非常有趣的CSS动画就被创作出来了,过程中所使用的各种技术的结合值得深思和学习。

好了,这篇文章就到这里了,希望这篇文章对你有帮助😃

如果你想获取最有趣的CSS资讯,不要错过我的公众号 -- iCSS前端趣事 😄

更多精彩CSS技术文章汇总在我的Github——iCSS中,会持续更新。欢迎点击星星订阅收藏。

如果还有什么问题或者建议,可以多交流。这是一篇原创文章。文笔有限,才华浅薄。如果文章有什么错误的地方,请告诉我。

相关文章

最新资讯