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)
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 -
requestAnimationFrame
which syncs to monitor and gives just as smooth animations as CSS animations does.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.
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.