How to add physics to CSS animations?


Tags: javascript,html,css,css3

Problem :

I'm just doing some loading using css and I want them to have a real behavior, I'm trying with the animation-timing-function: cubic-bezier(1, 0, 1, 1), looks fine but not as real like I want, at first because I don't know how do cubic-bezier parameters really work, I found this site and just played around with them until I got something nice.

To sum up, how to add a real physic behavior to my animation?

PLEASE ONLY CSS, if not possible, a JS solution is welcome.

Here you have a FIDDLE example.

Just a suggestion to guide

Something like a less or SCSS with constant physical variables that are defined, or values that you can add to the function and sumule the physical behavior may even have already mixins that simulates certain behavior, I do not know something simple and only CSS.

Thx in advance



Solution :

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.


    CSS Howto..

    How to change the css class name dynamically in angular 2

    Pure CSS Slideshow with animation delay fadein fadeout effects

    how to bold words within a paragraph in HTML/CSS?

    DropDown Menu won't show for more than a second..not working.

    How can I center a graphic under a
      -based nav menu?

    How to draw outside borders only in thead

    How to color the first word using CSS

    Jquery how to add :hover to css style sheet

    How to make this layout more responsive in CSS?

    How to split an HTML paragraph up into its lines of text with JavaScript

    How do I add transition to the effect of hide() and show() on these elements?

    How to create a Page Header (an Image) for print using css

    How to clear the parent css

    How to overwrite css animation

    How do I keep text from wrapping under an element which floats to its left?

    simplecart.js Images not Showing in IE8 on blogger.com site

    How to use “Wedding text ” in Css font family?

    How to Change a Plugin's CSS

    How to put the table inside the box

    How do I handle the hover in this recursive menu using jQuery?

    How do I make the cursor stay as a pointer on text using jquery/javascript?

    How to add style to words (HTML/CSS)

    JS/JQuery: How to switch class several times in a loop way?

    How to create a full CSS box-shadow, not clipped

    How to use body horizontal scrollbar in free jqgrid

    How to push down second dropdown when open first ?

    How do I combine two columns with different formatting?

    How to stop FOUC when using css loaded by webpack

    How to positioning the tooltip above cursor

    CSS3 Transitions in Stylus - how to degrade for IE9