javascript - 如何将物理添加到 CSS 动画中?

标签 javascript html css 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,但您将花费大量时间来计算贝塞尔曲线的数字、关键帧位置、比例等等,最重要的是:你的布局,“重力”,尺寸,距离,你必须“从头开始”(至少对于上述部分)。

CSS 动画很好,但使用少量 JavaScript 代码会获得更好的效果,更不用说在需要更改某些内容时具有更大的灵 active -

  • 为球定义一个向量
  • 定义任意重力
  • 计算矢量和反弹
  • 使用转换将结果值绑定(bind)到 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>

如果你想要更准确的地板弹跳,你也可以使用实际位置的差异来反射(reflect):

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 支持,因为它对这些类型的动画更准确,而且如前所述,它可以轻松适应新要求。

关于javascript - 如何将物理添加到 CSS 动画中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30678792/

相关文章:

javascript - 滚动到目标部分时更改按钮的颜色

javascript - 如何使用护照检查 react 组件上的isAuthenticated

javascript - 如何获取servlet中从ajax调用传递过来的数组

javascript - 显示/隐藏 css/javascript 代码未在 Chrome 中正确隐藏内容

html - Canvas 项目无法正确呈现

jquery - bootstrap 4 文件输入不显示文件名

html - 在背景顶部对齐文本中心

html - 添加基于条件/输入属性 Angular 2+ 的 CSS 样式 url

javascript - 可能的错误 - 嵌入的 YouTube 视频在其标题附近显示了一个不需要的黑色圆圈动画

javascript - 在 jQuery 中使用 switch 语句