如何在CSS动画中添加物理效果?

17

我正在使用CSS制作一个加载屏幕,希望它能具有物理上准确的行为。我尝试使用animation-timing-function: cubic-bezier(1, 0, 1, 1),看起来还不错,但不如我想象中真实。起初是因为我不知道参数的真正工作方式,我找到了这个网站,并随意尝试它们直到得到了令人满意的效果。

总之,我该如何在我的动画中添加物理上准确的行为呢? 我正在寻找一种仅使用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的东西,有常数物理变量定义或者可以将值添加到函数中并模拟物理行为,甚至可能已经有模拟某些行为的mixin,我不知道是否有像样且只使用CSS的简单方法。


4
你有两个选择,你可以使用JavaScript实现真实的物理规则,或者你可以使用数学函数来模拟反弹。在现实中,球应该遵循抛物线轨迹,并且应该具有恒定的加速度。我认为你应该尝试玩一下二次动画。 - steve
@vihan1086 我想要的是用CSS尽可能真实地模拟物理事件,比如一个球下落。 - Yandy_Viera
它应该尽可能接近这个的样子... - Sebastian Simon
1
类似这样的js代码--https://jsfiddle.net/3c6vps3j/--取自此指南--http://bassistance.de/2011/12/09/vector-math-basics-to-animate-a-bouncing-ball-in-javascript/--需要弄清如何停止球。 - Tasos
这不是一个真正的答案,但如果我想查询不同的js和css缓动效果,我会使用以下网站:http://easings.net/de - yacon
也许D3会有用?请查看:https://github.com/mbostock/d3/wiki/Transitions#d3_ease - Arius
1个回答

18
您可以使用纯CSS,但是您将花费很多时间来找出Bezier、关键帧位置、缩放等数字,而且在此基础上:布局、重力、尺寸、距离的轻微变化,您不得不重新开始(至少对于上面的部分)。
CSS动画很好,但是如果您需要更改某些内容,则使用一些JavaScript代码可以获得更好的结果,更不用说更灵活了-
- 定义球的向量 - 定义任意重力 - 计算向量和反弹 - 使用转换将结果值绑定到DOM元素(与位置相比,这会给出更平滑的结果)。 - 使用requestAnimationFrame进行动画处理,它与监视器同步,并提供与CSS动画同样平滑的动画效果。
示例
此示例显示基本内容,不包括阴影,但这留给读者作为练习。

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>

如果您想要更精确地反弹到地面,您可以使用实际位置的 diff 来反映这一点:
if (pos.y > bottom) {
    var diff = pos.y - bottom;
    pos.y = bottom - diff;
    ...

如果您需要将此应用于多个元素,只需创建一个可实例化的对象,其中包含要动画化的元素的引用、计算等。
如果您现在想要更改方向、起始点、重力等,则只需更新相应的值,当重新播放时一切都会很顺利。
产生CSS关键帧的示例中间步骤
您可以修改上面的代码来处理CSS动画。使用帧数并规范序列范围,通过计算帧数来运行计算。然后按每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来支持此类动画效果,因为它更加准确,并且正如提到的那样,它可以轻松适应新的要求。


1
非常好的回答,我一定会采纳你的建议。非常感谢! - Yandy_Viera

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接