【问题标题】:How to add physics to CSS animations?如何为 CSS 动画添加物理特性?
【发布时间】:2015-08-21 03:04:07
【问题描述】:

我只是使用 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 之类的东西,定义了恒定的物理变量,或者你可以添加到函数中并总结物理行为的值,甚至可能已经有模拟某些行为的 mixin,我不知道一些简单的东西,只有 CSS .

【问题讨论】:

  • 您有两个选择,您可以使用 java 脚本实现真实的物理规则,也可以使用数学函数来模拟弹跳。实际上,球应该遵循抛物线,并且应该具有恒定的加速度。我认为您应该尝试使用二次动画
  • @vihan1086 我想要的是用css模拟“尽可能真实”的物理事件,例如球落下
  • 它应该尽可能接近this...
  • 类似这样的 js -- jsfiddle.net/3c6vps3j -- 摘自本指南 -- bassistance.de/2011/12/09/… -- 需要弄清楚如何停止球
  • 这不是一个真正的答案,但如果想查找不同的 js 和 css 缓动,我使用以下站点:easings.net/de

标签: javascript html css css-animations


【解决方案1】:

可以仅使用 CSS,但您将花费大量时间计算贝塞尔曲线、关键帧位置、比例等的数字,最重要的是:在你的布局,“重力”,尺寸,距离,你必须“从头再来”(至少对于上述部分)。

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;
}
&lt;div&gt;&lt;/div&gt;

如果您想要更准确的地板反弹,您也可以使用实际位置的差异来反映这一点:

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)}
}
&lt;div&gt;&lt;/div&gt;

我个人会推荐 JavaScript 支持,因为它对这些类型的动画更准确,并且如前所述,它可以轻松适应新的要求。

【讨论】:

  • 很好的答案,当然,我会接受你的建议。非常感谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多