如何将物理添加到 CSS 动画?
How to add physics to CSS animations?
我只是使用 CSS 制作加载屏幕,我希望它具有物理上准确的行为。我正在尝试使用 animation-timing-function: cubic-bezier(1, 0, 1, 1)
,看起来不错但不像我想要的那样真实,起初因为我不知道 cubic-bezier
参数是如何工作的,我找到了 this 网站和只是和他们一起玩,直到我得到一些好东西。
总而言之,我如何才能将物理上准确的行为添加到我的动画中?
我正在寻找仅 CSS 的解决方案,但如果不可能,JavaScript 也可以。
这里有一个例子:
body{
background-color: #02a2bb;
}
.wrapper {
padding: 50px;
text-align: center;
}
.content {
height: 125px;
margin: 0 auto;
position: relative;
display: inline-block;
}
.ball {
width: 25px;
height: 25px;
display: inline-block;
border-radius: 50%;
bottom: 0;
position: relative;
background-color: #fff;
z-index: 1;
}
.ball-shadow {
width: 20px;
height: 6px;
border-radius: 50%;
position: absolute;
bottom: 9px;
left: 50%;
-webkit-transform: translateX(-50%);
-moz-transform: translateX(-50%);
transform: translateX(-50%);
}
.animated {
-webkit-animation-duration: 1s;
-moz-animation-duration: 1s;
-ms-animation-duration: 1s;
-o-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
-moz-animation-fill-mode: both;
-ms-animation-fill-mode: both;
-o-animation-fill-mode: both;
animation-fill-mode: both;
-webkit-animation-iteration-count: infinite;
-moz-animation-iteration-count: infinite;
-ms-animation-iteration-count: infinite;
-o-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
.animated.jump, .animated.displace, .animated.diffuse-scale {
-webkit-animation-duration: 3s;
-moz-animation-duration: 3s;
-ms-animation-duration: 3s;
-o-animation-duration: 3s;
animation-duration: 3s;
}
@-webkit-keyframes jump {
0% {
opacity: 1;
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: translate(0, 0);
}
15% {
opacity: 1;
-webkit-transform: translate(0, 100px) scale(1.1, 0.9);
}
30% {
opacity: 1;
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: translate(0, 15px);
}
45% {
opacity: 1;
-webkit-transform: translate(0, 100px) scale(1.08, 0.92);
}
60% {
opacity: 1;
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: translate(0, 45px);
}
70% {
opacity: 1;
-webkit-transform: translate(0, 100px) scale(1.05, 0.95);
}
80% {
opacity: 1;
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: translate(0, 65px);
}
85% {
opacity: 1;
-webkit-transform: translate(0, 100px) scale(1.03, 0.97);
}
90% {
opacity: 1;
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: translate(0, 80px);
}
95% {
opacity: 1;
-webkit-transform: translate(0, 100px) scale(1.01, 0.99);
}
97% {
opacity: 1;
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: translate(0, 95px);
}
100% {
opacity: 0;
-webkit-transform: translate(0, 100px);
}
}
@keyframes jump {
0% {
opacity: 1;
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: translate(0, 0);
transform: translate(0, 0);
}
15% {
opacity: 1;
-moz-transform: translate(0, 100px) scale(1.1, 0.9);
transform: translate(0, 100px) scale(1.1, 0.9);
}
30% {
opacity: 1;
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: translate(0, 15px);
transform: translate(0, 15px);
}
45% {
opacity: 1;
-moz-transform: translate(0, 100px)scale(1.08, 0.92);
transform: translate(0, 100px)scale(1.08, 0.92);
}
60% {
opacity: 1;
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: translate(0, 45px);
transform: translate(0, 45px);
}
70% {
opacity: 1;
-moz-transform: translate(0, 100px)scale(1.05, 0.95);
transform: translate(0, 100px)scale(1.05, 0.95);
}
80% {
opacity: 1;
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: translate(0, 65px);
transform: translate(0, 65px);
}
85% {
opacity: 1;
-moz-transform: translate(0, 100px) scale(1.03, 0.97);
transform: translate(0, 100px) scale(1.03, 0.97);
}
90% {
opacity: 1;
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: translate(0, 80px);
transform: translate(0, 80px);
}
95% {
opacity: 1;
-moz-transform: translate(0, 100px) scale(1.01, 0.99);
transform: translate(0, 100px) scale(1.01, 0.99);
}
97% {
opacity: 1;
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: translate(0, 95px);
transform: translate(0, 95px);
}
100% {
opacity: 0;
-moz-transform: translate(0, 100px);
transform: translate(0, 100px);
}
}
@-webkit-keyframes diffuse-scale {
0% {
box-shadow: 0 14px 8px rgba(0, 0, 0, 0.5);
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: scale(1.5, 1) translateX(-50%);
}
15% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-webkit-transform: scale(1, 1) translateX(-50%);
}
30% {
box-shadow: 0 14px 7px rgba(0, 0, 0, 0.5);
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: scale(1.4, 1) translateX(-50%);
}
45% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-webkit-transform: scale(1, 1) translateX(-50%); }
60% {
box-shadow: 0 14px 5px rgba(0, 0, 0, 0.5);
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: scale(1.3, 1) translateX(-50%); }
70% {
box-shadow: 0 14 2px rgba(0, 0, 0, 0.5);
-webkit-transform: scale(1, 1) translateX(-50%);
}
80% {
box-shadow: 0 14px 4px rgba(0, 0, 0, 0.5);
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: scale(1.2, 1) translateX(-50%);
}
85% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-webkit-transform: scale(1, 1) translateX(-50%);
}
90% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: scale(1.1, 1) translateX(-50%);
}
95% {
box-shadow: 0 14px 3px rgba(0, 0, 0, 0.5);
-webkit-transform: scale(1, 1) translateX(-50%);
}
97% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: scale(1.05, 1) translateX(-50%);
}
100% {
-webkit-transform: scale(1, 1) translateX(-50%);
}
}
@keyframes diffuse-scale {
0% {
box-shadow: 0 14px 8px rgba(0, 0, 0, 0.5);
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: scale(1.5, 1) translateX(-50%);
transform: scale(1.5, 1) translateX(-50%);
}
15% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-moz-transform: scale(1, 1) translateX(-50%);
transform: scale(1, 1) translateX(-50%);
}
30% {
box-shadow: 0 14px 7px rgba(0, 0, 0, 0.5);
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: scale(1.4, 1) translateX(-50%);
transform: scale(1.4, 1) translateX(-50%);
}
45% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-moz-transform: scale(1, 1) translateX(-50%);
transform: scale(1, 1) translateX(-50%);
}
60% {
box-shadow: 0 14px 5px rgba(0, 0, 0, 0.5);
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: scale(1.3, 1) translateX(-50%);
transform: scale(1.3, 1) translateX(-50%);
}
70% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-moz-transform: scale(1, 1) translateX(-50%);
transform: scale(1, 1) translateX(-50%);
}
80% {
box-shadow: 0 14px 4px rgba(0, 0, 0, 0.5);
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: scale(1.2, 1) translateX(-50%);
transform: scale(1.2, 1) translateX(-50%);
}
85% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-webkit-transform: scale(1, 1) translateX(-50%);
-moz-transform: scale(1, 1) translateX(-50%);
transform: scale(1, 1) translateX(-50%);
}
90% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: scale(1.1, 1) translateX(-50%);
transform: scale(1.1, 1) translateX(-50%);
}
95% {
box-shadow: 0 14px 3px rgba(0, 0, 0, 0.5);
-moz-transform: scale(1, 1) translateX(-50%);
transform: scale(1, 1) translateX(-50%);
}
97% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: scale(1.05, 1) translateX(-50%);
transform: scale(1.05, 1) translateX(-50%);
}
100% {
-moz-transform: scale(1, 1) translateX(-50%);
transform: scale(1, 1) translateX(-50%);
}
}
@-webkit-keyframes displace {
from {
-webkit-animation-timing-function: linear;
-webkit-transform: translateX(0);
}
to {
-webkit-transform: translateX(100px);
}
}
@keyframes displace {
from {
-moz-animation-timing-function: linear;
animation-timing-function: linear;
-moz-transform: translateX(0);
transform: translateX(0);
}
to {
-moz-transform: translateX(100px);
transform: translateX(100px);
}
}
.jump {
-webkit-animation-name: jump;
-moz-animation-name: jump;
-ms-animation-name: jump;
-o-animation-name: jump;
animation-name: jump;
}
.diffuse-scale {
-webkit-animation-name: diffuse-scale;
-moz-animation-name: diffuse-scale;
-ms-animation-name: diffuse-scale;
-o-animation-name: diffuse-scale;
animation-name: diffuse-scale;
}
.displace {
-webkit-animation-name: displace;
-moz-animation-name: displace;
-ms-animation-name: displace;
-o-animation-name: displace;
animation-name: displace;
}
<div class="wrapper">
<div class="content animated infinite displace">
<span class="ball animated infinite jump"></span>
<span class="ball-shadow animated infinite diffuse-scale"></span>
</div>
</div>
建议
类似 less 或 SCSS 的东西,具有已定义的常量物理变量,或者您可以添加到函数中并计算物理行为的值,甚至可能已经有模拟某些行为的混合,我这样做不知道简单的东西,只知道 CSS.
您 可以 使用 CSS-only 但您将花费大量时间来计算贝塞尔曲线、关键帧位置、比例等的数字,并且最重要的是:您的布局、"gravity"、尺寸、距离略有变化,您必须开始 "all over"(至少对于上述部分)。
CSS 动画很好,但是你会用一点 JavaScript 代码获得更好的结果,更不用说如果你需要改变一些东西会有更多的灵活性 -
- 为球定义一个向量
- 定义任意重力
- 计算矢量和反弹
- 使用变换将结果值绑定到 DOM 元素(与位置相比,结果更平滑)。
- 使用
requestAnimationFrame
动画,它同步到监视器并提供与 CSS 动画一样流畅的动画。
例子
这个例子展示了基本的,不包括阴影,但是留给reader作为练习。
var div = document.querySelector("div"),
v = {x: 2.3, y: 1}, // some vector
pos = {x: 100, y: 20}, // some position
g = 0.5, // some gravity
absorption = 0.7, // friction/absorption
bottom = 150, // floor collision
frames = 0; // to reset animation (for demo)
// main calculation of the animation using a particle and a vector
function calc() {
pos.x += v.x; // update position with vector
pos.y += v.y;
v.y += g; // update vector with gravity
if (pos.y > bottom) { // hit da floor, bounce
pos.y = bottom; // force position = max bottom
v.y = -v.y * absorption; // reduce power with absorption
}
if (pos.x < 0 || pos.x > 620) v.x = -v.x;
}
// animate
(function loop() {
calc();
move(div, pos);
if (++frames > 220) { // tweak, use other techniques - just to reset bounce
frames = 0; pos.y = 20;
}
requestAnimationFrame(loop)
})();
function move(el, p) {
el.style.transform = el.style.webkitTransform = "translate("+p.x+"px,"+p.y+"px)";
}
div {
width:20px;
height:20px;
background:rgb(0, 135, 222);
border-radius:50%;
position:fixed;
}
<div></div>
如果你想要更准确的地板反弹,你也可以使用实际位置的差异来反映:
if (pos.y > bottom) {
var diff = pos.y - bottom;
pos.y = bottom - diff;
...
如果您需要为多个元素使用它,只需创建一个可实例化的对象,该对象嵌入对元素的引用以进行动画处理、计算等。
如果你现在想改变方向、起点、重力等,你只需更新各自的值,重播时一切都会很顺利。
生成CSS关键帧的示例中间步骤
您可以将上面的代码修改为 CSS 动画的 c运行ch 数字。
使用帧数并归一化序列范围,运行通过计算帧数进行计算。然后提取每个值,比方说每 10 帧以及每次反弹,最后将数字格式化为关键帧。
理想情况下,您将始终包括顶部和底部位置 - 您可以通过监视矢量的 y 值(符号)的方向来检测这一点,此处未显示。
这将作为生成 CSS-规则的中间步骤,我们稍后将使用该规则:
var v = {x: 2.3, y: 1}, // some vector
pos = {x: 100, y: 20}, // some position
g = 0.5, // some gravity
absorption = 0.7, // friction/absorption
bottom = 150, // floor collision
frames = 0, // to reset animation (for demo)
maxFrames = 220, // so we can normalize
step = 10, // grab every nth + bounce
heights = [], // collect in an array as step 1
css = ""; // build CSS animation
// calc CSS-frames
for(var i = 0; i <= maxFrames; i++) {
var t = i / maxFrames;
pos.x += v.x; // update position with vector
pos.y += v.y;
v.y += g; // update vector with gravity
if (pos.y > bottom) {
pos.y = bottom;
v.y = -v.y * absorption;
heights.push({pst: t * 100, y: pos.y});
}
else if (!(i % step)) {heights.push({pst: t * 100, y: pos.y})}
}
// step 2: format height-array into CSS
css += "@keyframes demo {\n";
for(i = 0; i < heights.length; i++) {
var e = heights[i];
css += " " + e.pst.toFixed(3) + "% {transform: translateY(" + e.y.toFixed(3) + "px)}\n";
}
css += "}";
document.write("<pre>" + css + "</pre>");
如果我们从中获取结果并将其用作最终页面的 CSS,我们将得到以下结果(抱歉,本演示中仅使用无前缀版本):
(您当然需要对此进行调整和微调,但您会明白要点的。)
div {
animation: demo 3s linear infinite;
width:20px;
height:20px;
border-radius:50%;
background:rgb(0, 148, 243);
position:fixed;
left:100px;
}
@keyframes demo {
0.000% {transform: translateY(21.000px)}
4.545% {transform: translateY(58.500px)}
9.091% {transform: translateY(146.000px)}
9.545% {transform: translateY(150.000px)}
13.636% {transform: translateY(92.400px)}
18.182% {transform: translateY(75.900px)}
22.727% {transform: translateY(109.400px)}
25.455% {transform: translateY(150.000px)}
27.273% {transform: translateY(127.520px)}
31.818% {transform: translateY(106.320px)}
36.364% {transform: translateY(135.120px)}
37.727% {transform: translateY(150.000px)}
40.909% {transform: translateY(125.563px)}
45.455% {transform: translateY(133.153px)}
47.273% {transform: translateY(150.000px)}
50.000% {transform: translateY(134.362px)}
54.545% {transform: translateY(148.299px)}
55.000% {transform: translateY(150.000px)}
59.091% {transform: translateY(138.745px)}
61.818% {transform: translateY(150.000px)}
63.636% {transform: translateY(141.102px)}
67.727% {transform: translateY(150.000px)}
68.182% {transform: translateY(147.532px)}
72.727% {transform: translateY(150.000px)}
77.273% {transform: translateY(150.000px)}
81.818% {transform: translateY(150.000px)}
86.364% {transform: translateY(150.000px)}
90.909% {transform: translateY(150.000px)}
95.455% {transform: translateY(150.000px)}
100.000% {transform: translateY(150.000px)}
}
<div></div>
我个人会推荐 JavaScript 支持,因为它对这些类型的动画更准确,而且如前所述,它可以轻松适应新要求。
我只是使用 CSS 制作加载屏幕,我希望它具有物理上准确的行为。我正在尝试使用 animation-timing-function: cubic-bezier(1, 0, 1, 1)
,看起来不错但不像我想要的那样真实,起初因为我不知道 cubic-bezier
参数是如何工作的,我找到了 this 网站和只是和他们一起玩,直到我得到一些好东西。
总而言之,我如何才能将物理上准确的行为添加到我的动画中? 我正在寻找仅 CSS 的解决方案,但如果不可能,JavaScript 也可以。
这里有一个例子:
body{
background-color: #02a2bb;
}
.wrapper {
padding: 50px;
text-align: center;
}
.content {
height: 125px;
margin: 0 auto;
position: relative;
display: inline-block;
}
.ball {
width: 25px;
height: 25px;
display: inline-block;
border-radius: 50%;
bottom: 0;
position: relative;
background-color: #fff;
z-index: 1;
}
.ball-shadow {
width: 20px;
height: 6px;
border-radius: 50%;
position: absolute;
bottom: 9px;
left: 50%;
-webkit-transform: translateX(-50%);
-moz-transform: translateX(-50%);
transform: translateX(-50%);
}
.animated {
-webkit-animation-duration: 1s;
-moz-animation-duration: 1s;
-ms-animation-duration: 1s;
-o-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
-moz-animation-fill-mode: both;
-ms-animation-fill-mode: both;
-o-animation-fill-mode: both;
animation-fill-mode: both;
-webkit-animation-iteration-count: infinite;
-moz-animation-iteration-count: infinite;
-ms-animation-iteration-count: infinite;
-o-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
.animated.jump, .animated.displace, .animated.diffuse-scale {
-webkit-animation-duration: 3s;
-moz-animation-duration: 3s;
-ms-animation-duration: 3s;
-o-animation-duration: 3s;
animation-duration: 3s;
}
@-webkit-keyframes jump {
0% {
opacity: 1;
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: translate(0, 0);
}
15% {
opacity: 1;
-webkit-transform: translate(0, 100px) scale(1.1, 0.9);
}
30% {
opacity: 1;
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: translate(0, 15px);
}
45% {
opacity: 1;
-webkit-transform: translate(0, 100px) scale(1.08, 0.92);
}
60% {
opacity: 1;
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: translate(0, 45px);
}
70% {
opacity: 1;
-webkit-transform: translate(0, 100px) scale(1.05, 0.95);
}
80% {
opacity: 1;
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: translate(0, 65px);
}
85% {
opacity: 1;
-webkit-transform: translate(0, 100px) scale(1.03, 0.97);
}
90% {
opacity: 1;
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: translate(0, 80px);
}
95% {
opacity: 1;
-webkit-transform: translate(0, 100px) scale(1.01, 0.99);
}
97% {
opacity: 1;
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: translate(0, 95px);
}
100% {
opacity: 0;
-webkit-transform: translate(0, 100px);
}
}
@keyframes jump {
0% {
opacity: 1;
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: translate(0, 0);
transform: translate(0, 0);
}
15% {
opacity: 1;
-moz-transform: translate(0, 100px) scale(1.1, 0.9);
transform: translate(0, 100px) scale(1.1, 0.9);
}
30% {
opacity: 1;
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: translate(0, 15px);
transform: translate(0, 15px);
}
45% {
opacity: 1;
-moz-transform: translate(0, 100px)scale(1.08, 0.92);
transform: translate(0, 100px)scale(1.08, 0.92);
}
60% {
opacity: 1;
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: translate(0, 45px);
transform: translate(0, 45px);
}
70% {
opacity: 1;
-moz-transform: translate(0, 100px)scale(1.05, 0.95);
transform: translate(0, 100px)scale(1.05, 0.95);
}
80% {
opacity: 1;
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: translate(0, 65px);
transform: translate(0, 65px);
}
85% {
opacity: 1;
-moz-transform: translate(0, 100px) scale(1.03, 0.97);
transform: translate(0, 100px) scale(1.03, 0.97);
}
90% {
opacity: 1;
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: translate(0, 80px);
transform: translate(0, 80px);
}
95% {
opacity: 1;
-moz-transform: translate(0, 100px) scale(1.01, 0.99);
transform: translate(0, 100px) scale(1.01, 0.99);
}
97% {
opacity: 1;
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: translate(0, 95px);
transform: translate(0, 95px);
}
100% {
opacity: 0;
-moz-transform: translate(0, 100px);
transform: translate(0, 100px);
}
}
@-webkit-keyframes diffuse-scale {
0% {
box-shadow: 0 14px 8px rgba(0, 0, 0, 0.5);
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: scale(1.5, 1) translateX(-50%);
}
15% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-webkit-transform: scale(1, 1) translateX(-50%);
}
30% {
box-shadow: 0 14px 7px rgba(0, 0, 0, 0.5);
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: scale(1.4, 1) translateX(-50%);
}
45% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-webkit-transform: scale(1, 1) translateX(-50%); }
60% {
box-shadow: 0 14px 5px rgba(0, 0, 0, 0.5);
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: scale(1.3, 1) translateX(-50%); }
70% {
box-shadow: 0 14 2px rgba(0, 0, 0, 0.5);
-webkit-transform: scale(1, 1) translateX(-50%);
}
80% {
box-shadow: 0 14px 4px rgba(0, 0, 0, 0.5);
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: scale(1.2, 1) translateX(-50%);
}
85% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-webkit-transform: scale(1, 1) translateX(-50%);
}
90% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: scale(1.1, 1) translateX(-50%);
}
95% {
box-shadow: 0 14px 3px rgba(0, 0, 0, 0.5);
-webkit-transform: scale(1, 1) translateX(-50%);
}
97% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-webkit-animation-timing-function: cubic-bezier(1, 0, 1, 1);
-webkit-transform: scale(1.05, 1) translateX(-50%);
}
100% {
-webkit-transform: scale(1, 1) translateX(-50%);
}
}
@keyframes diffuse-scale {
0% {
box-shadow: 0 14px 8px rgba(0, 0, 0, 0.5);
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: scale(1.5, 1) translateX(-50%);
transform: scale(1.5, 1) translateX(-50%);
}
15% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-moz-transform: scale(1, 1) translateX(-50%);
transform: scale(1, 1) translateX(-50%);
}
30% {
box-shadow: 0 14px 7px rgba(0, 0, 0, 0.5);
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: scale(1.4, 1) translateX(-50%);
transform: scale(1.4, 1) translateX(-50%);
}
45% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-moz-transform: scale(1, 1) translateX(-50%);
transform: scale(1, 1) translateX(-50%);
}
60% {
box-shadow: 0 14px 5px rgba(0, 0, 0, 0.5);
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: scale(1.3, 1) translateX(-50%);
transform: scale(1.3, 1) translateX(-50%);
}
70% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-moz-transform: scale(1, 1) translateX(-50%);
transform: scale(1, 1) translateX(-50%);
}
80% {
box-shadow: 0 14px 4px rgba(0, 0, 0, 0.5);
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: scale(1.2, 1) translateX(-50%);
transform: scale(1.2, 1) translateX(-50%);
}
85% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-webkit-transform: scale(1, 1) translateX(-50%);
-moz-transform: scale(1, 1) translateX(-50%);
transform: scale(1, 1) translateX(-50%);
}
90% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: scale(1.1, 1) translateX(-50%);
transform: scale(1.1, 1) translateX(-50%);
}
95% {
box-shadow: 0 14px 3px rgba(0, 0, 0, 0.5);
-moz-transform: scale(1, 1) translateX(-50%);
transform: scale(1, 1) translateX(-50%);
}
97% {
box-shadow: 0 14px 2px rgba(0, 0, 0, 0.5);
-moz-animation-timing-function: cubic-bezier(1, 0, 1, 1);
animation-timing-function: cubic-bezier(1, 0, 1, 1);
-moz-transform: scale(1.05, 1) translateX(-50%);
transform: scale(1.05, 1) translateX(-50%);
}
100% {
-moz-transform: scale(1, 1) translateX(-50%);
transform: scale(1, 1) translateX(-50%);
}
}
@-webkit-keyframes displace {
from {
-webkit-animation-timing-function: linear;
-webkit-transform: translateX(0);
}
to {
-webkit-transform: translateX(100px);
}
}
@keyframes displace {
from {
-moz-animation-timing-function: linear;
animation-timing-function: linear;
-moz-transform: translateX(0);
transform: translateX(0);
}
to {
-moz-transform: translateX(100px);
transform: translateX(100px);
}
}
.jump {
-webkit-animation-name: jump;
-moz-animation-name: jump;
-ms-animation-name: jump;
-o-animation-name: jump;
animation-name: jump;
}
.diffuse-scale {
-webkit-animation-name: diffuse-scale;
-moz-animation-name: diffuse-scale;
-ms-animation-name: diffuse-scale;
-o-animation-name: diffuse-scale;
animation-name: diffuse-scale;
}
.displace {
-webkit-animation-name: displace;
-moz-animation-name: displace;
-ms-animation-name: displace;
-o-animation-name: displace;
animation-name: displace;
}
<div class="wrapper">
<div class="content animated infinite displace">
<span class="ball animated infinite jump"></span>
<span class="ball-shadow animated infinite diffuse-scale"></span>
</div>
</div>
建议
类似 less 或 SCSS 的东西,具有已定义的常量物理变量,或者您可以添加到函数中并计算物理行为的值,甚至可能已经有模拟某些行为的混合,我这样做不知道简单的东西,只知道 CSS.
您 可以 使用 CSS-only 但您将花费大量时间来计算贝塞尔曲线、关键帧位置、比例等的数字,并且最重要的是:您的布局、"gravity"、尺寸、距离略有变化,您必须开始 "all over"(至少对于上述部分)。
CSS 动画很好,但是你会用一点 JavaScript 代码获得更好的结果,更不用说如果你需要改变一些东西会有更多的灵活性 -
- 为球定义一个向量
- 定义任意重力
- 计算矢量和反弹
- 使用变换将结果值绑定到 DOM 元素(与位置相比,结果更平滑)。
- 使用
requestAnimationFrame
动画,它同步到监视器并提供与 CSS 动画一样流畅的动画。
例子
这个例子展示了基本的,不包括阴影,但是留给reader作为练习。
var div = document.querySelector("div"),
v = {x: 2.3, y: 1}, // some vector
pos = {x: 100, y: 20}, // some position
g = 0.5, // some gravity
absorption = 0.7, // friction/absorption
bottom = 150, // floor collision
frames = 0; // to reset animation (for demo)
// main calculation of the animation using a particle and a vector
function calc() {
pos.x += v.x; // update position with vector
pos.y += v.y;
v.y += g; // update vector with gravity
if (pos.y > bottom) { // hit da floor, bounce
pos.y = bottom; // force position = max bottom
v.y = -v.y * absorption; // reduce power with absorption
}
if (pos.x < 0 || pos.x > 620) v.x = -v.x;
}
// animate
(function loop() {
calc();
move(div, pos);
if (++frames > 220) { // tweak, use other techniques - just to reset bounce
frames = 0; pos.y = 20;
}
requestAnimationFrame(loop)
})();
function move(el, p) {
el.style.transform = el.style.webkitTransform = "translate("+p.x+"px,"+p.y+"px)";
}
div {
width:20px;
height:20px;
background:rgb(0, 135, 222);
border-radius:50%;
position:fixed;
}
<div></div>
如果你想要更准确的地板反弹,你也可以使用实际位置的差异来反映:
if (pos.y > bottom) {
var diff = pos.y - bottom;
pos.y = bottom - diff;
...
如果您需要为多个元素使用它,只需创建一个可实例化的对象,该对象嵌入对元素的引用以进行动画处理、计算等。
如果你现在想改变方向、起点、重力等,你只需更新各自的值,重播时一切都会很顺利。
生成CSS关键帧的示例中间步骤
您可以将上面的代码修改为 CSS 动画的 c运行ch 数字。
使用帧数并归一化序列范围,运行通过计算帧数进行计算。然后提取每个值,比方说每 10 帧以及每次反弹,最后将数字格式化为关键帧。
理想情况下,您将始终包括顶部和底部位置 - 您可以通过监视矢量的 y 值(符号)的方向来检测这一点,此处未显示。
这将作为生成 CSS-规则的中间步骤,我们稍后将使用该规则:
var v = {x: 2.3, y: 1}, // some vector
pos = {x: 100, y: 20}, // some position
g = 0.5, // some gravity
absorption = 0.7, // friction/absorption
bottom = 150, // floor collision
frames = 0, // to reset animation (for demo)
maxFrames = 220, // so we can normalize
step = 10, // grab every nth + bounce
heights = [], // collect in an array as step 1
css = ""; // build CSS animation
// calc CSS-frames
for(var i = 0; i <= maxFrames; i++) {
var t = i / maxFrames;
pos.x += v.x; // update position with vector
pos.y += v.y;
v.y += g; // update vector with gravity
if (pos.y > bottom) {
pos.y = bottom;
v.y = -v.y * absorption;
heights.push({pst: t * 100, y: pos.y});
}
else if (!(i % step)) {heights.push({pst: t * 100, y: pos.y})}
}
// step 2: format height-array into CSS
css += "@keyframes demo {\n";
for(i = 0; i < heights.length; i++) {
var e = heights[i];
css += " " + e.pst.toFixed(3) + "% {transform: translateY(" + e.y.toFixed(3) + "px)}\n";
}
css += "}";
document.write("<pre>" + css + "</pre>");
如果我们从中获取结果并将其用作最终页面的 CSS,我们将得到以下结果(抱歉,本演示中仅使用无前缀版本):
(您当然需要对此进行调整和微调,但您会明白要点的。)
div {
animation: demo 3s linear infinite;
width:20px;
height:20px;
border-radius:50%;
background:rgb(0, 148, 243);
position:fixed;
left:100px;
}
@keyframes demo {
0.000% {transform: translateY(21.000px)}
4.545% {transform: translateY(58.500px)}
9.091% {transform: translateY(146.000px)}
9.545% {transform: translateY(150.000px)}
13.636% {transform: translateY(92.400px)}
18.182% {transform: translateY(75.900px)}
22.727% {transform: translateY(109.400px)}
25.455% {transform: translateY(150.000px)}
27.273% {transform: translateY(127.520px)}
31.818% {transform: translateY(106.320px)}
36.364% {transform: translateY(135.120px)}
37.727% {transform: translateY(150.000px)}
40.909% {transform: translateY(125.563px)}
45.455% {transform: translateY(133.153px)}
47.273% {transform: translateY(150.000px)}
50.000% {transform: translateY(134.362px)}
54.545% {transform: translateY(148.299px)}
55.000% {transform: translateY(150.000px)}
59.091% {transform: translateY(138.745px)}
61.818% {transform: translateY(150.000px)}
63.636% {transform: translateY(141.102px)}
67.727% {transform: translateY(150.000px)}
68.182% {transform: translateY(147.532px)}
72.727% {transform: translateY(150.000px)}
77.273% {transform: translateY(150.000px)}
81.818% {transform: translateY(150.000px)}
86.364% {transform: translateY(150.000px)}
90.909% {transform: translateY(150.000px)}
95.455% {transform: translateY(150.000px)}
100.000% {transform: translateY(150.000px)}
}
<div></div>
我个人会推荐 JavaScript 支持,因为它对这些类型的动画更准确,而且如前所述,它可以轻松适应新要求。