How to add physics to CSS animations?

后端 未结 1 586
小鲜肉
小鲜肉 2021-02-05 15:02

I\'m just making a loading screen using CSS and I want it to have physically accurate behavior. I\'m trying with the animation-timing-function: cubic-bezier(1, 0, 1, 1)

相关标签:
1条回答
  • 2021-02-05 15:36

    You can use CSS-only but you will spend a lot of time figuring out the numbers for the Bezier, keyframe positions, scale and so on, and on top of that: a slight change in your layout, "gravity", dimensions, distance and you have to start "all over" (for the above part at least).

    CSS animations are nice, but you will gain a better result with a little JavaScript code, not to mention have a lot more flexibility if you need to change something -

    • Define a vector for the ball
    • Define a arbitrary gravity
    • Calculate the vector and bounce
    • Bind resulting values to DOM element using transforms (gives smoother result compared to position).
    • Animate using requestAnimationFrame which syncs to monitor and gives just as smooth animations as CSS animations does.

    Example

    This example shows the basic, does not include the shadow, but that is left as an exercise for the 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 you want a more accurate bounce of the floor, you can use the diff of actual position to reflect that as well:

    if (pos.y > bottom) {
        var diff = pos.y - bottom;
        pos.y = bottom - diff;
        ...
    

    And if you need this for several element, just create an instantiate-able object which embeds a reference to the element to animate, the calculations etc.

    If you now want to change direction, start point, gravity and so on, you just update the respective values and everything works smooth when replayed.

    Example intermediate step to produce CSS key-frames

    You can modify the code above to crunch numbers for a CSS-animation.

    Use number of frames and normalize the sequence range, run the calculations by counting frames. Then extract values per, lets say every 10 frames as well as every bounce, finally format numbers as key-frames.

    Ideally you will always include top and bottom position - you can detect this by monitoring the direction of the vector's y-value (the sign), not shown here.

    This will work as an intermediate step to produce the CSS-rule which we will use later:

    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>");

    If we grab the result from that and uses it as CSS for our final page, we get this result (sorry, non-prefixed version only in this demo):

    (You will of course have to tweak and fine-tune this, but you'll get the gist.)

    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>

    Personally I would recommend the JavaScript support though as it is more accurate for these type of animations, and as mentioned, it can easily adopt to new requirements.

    0 讨论(0)
提交回复
热议问题