Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ball bounces along a parabolic trajectory

The question was initiated by a topic

In that question, the bounces were vertical

<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"
       width="200" height="200" viewBox="0 0 200 200" preserveAspectRatio="xMinYMin meet" style="border:1px solid" >  
 
<image xlink:href="https://i.stack.imgur.com/hXyA5.png" x="82" width="25px" height="25px" >
   <animateTransform id="anT"
     attributeName="transform"
     type="translate"
     dur="3s"
     begin="svg1.click+0.5s;anT.end+1s"
     values="
        0,0;
        0,168;
        0,84;
        0,168;
        0,126;
        0,168;
        0,148;
        0,168;
        0,158;
        0,168;
        0,163;
        0,168;
        0,166;
        0,168;
        "
        keyTimes="0;0.066;0.13;0.198;0.264;0.33;0.396;0.462;0.528;0.594;0.66;0.726;0.792;1"
        repeatCount="1"
        fill="freeze"
        restart="whenNotActive" />
</image>
   <polyline points="5,193 194,193" stroke="silver" stroke-width="4" />
 </svg>  

This question concerns bounces with different amounts of offset in height and length

enter image description here

But it is difficult to realize the unevenness of movement and speed.

How to implement realistic parabolic ball movement?

Any idea and solution would be appreciated.

like image 831
Alexandr_TT Avatar asked Jan 24 '23 15:01

Alexandr_TT


2 Answers

CSS animation can approximate this better. You need to:

  1. find the suitable bezier curve for the timing function (https://cubic-bezier.com/#.17,.76,.58,1)
  2. update the duration factor to what you want
  3. add as many steps as you want by adding more animation

Then you need to correctly calculate the delay based on the duration (the previous animation delay + 2*previous aniamation duration).

The main trick is that using altenate and running each animation twice will give you the mirror effect and a perfect parabolic curve (half of it defined by the bezier curve)

.box {
  width: 200px;
  height: 190px;
  border: 1px solid;
  display: flex;
  justify-content: center;
  align-items: flex-end;
}

img {
   --d:0.8s; /*duration factor*/

   width:30px;
   animation-timing-function:cubic-bezier(.17,.76,.58,1); /* control this: https://cubic-bezier.com/#.17,.76,.58,1*/
   animation-iteration-count:2; /* don't change*/
   animation-direction: alternate; /* don't change */
   animation-name:t1,t2,t3,t4,t5; /* don't change unless you need more steps*/

   animation-duration:
     var(--d),
     calc(var(--d)/2),
     calc(var(--d)/3),
     calc(var(--d)/4),
     calc(var(--d)/5);
   animation-delay: 
     calc(var(--d)*-1), 
     calc(var(--d)), 
     calc(var(--d)   + 2*var(--d)/2), 
     calc(var(--d)*2 + 2*var(--d)/3), 
     calc(var(--d)*2 + 2*var(--d)/3 + 2*var(--d)/4); 
}

@keyframes t1{to {transform:translateY(-160px)}}
@keyframes t2{to {transform:translateY(-110px)}}
@keyframes t3{to {transform:translateY(-60px)}}
@keyframes t4{to {transform:translateY(-30px)}}
@keyframes t5{to {transform:translateY(-10px)}}
<div class="box">
  <img src="https://i.stack.imgur.com/hXyA5.png">
<div>

Adding the horizontal movement:

.box {
   --d:0.8s; /*duration factor*/
   
  width: 200px;
  height: 190px;
  border: 1px solid;
  display: flex;
  align-items: flex-end;
  overflow:hidden;
}

img {
   width:30px;
   animation-timing-function:cubic-bezier(.17,.76,.58,1); /* control this: https://cubic-bezier.com/#.17,.76,.58,1*/
   animation-iteration-count:2; /* don't change*/
   animation-direction: alternate; /* don't change */
   animation-name:t1,t2,t3,t4,t5; /* don't change unless you need more steps*/

   animation-duration:
     var(--d),
     calc(var(--d)/2),
     calc(var(--d)/3),
     calc(var(--d)/4),
     calc(var(--d)/5);
   animation-delay: 
     calc(var(--d)*-1), 
     calc(var(--d)), 
     calc(var(--d)   + 2*var(--d)/2), 
     calc(var(--d)*2 + 2*var(--d)/3), 
     calc(var(--d)*2 + 2*var(--d)/3 + 2*var(--d)/4); 
}

@keyframes t1{to {transform:translateY(-160px)}}
@keyframes t2{to {transform:translateY(-110px)}}
@keyframes t3{to {transform:translateY(-60px)}}
@keyframes t4{to {transform:translateY(-30px)}}
@keyframes t5{to {transform:translateY(-10px)}}

.box > span {
  animation:m calc(var(--d)*2 + 2*var(--d)/3 + 2*var(--d)/4 + 2*var(--d)/5) linear forwards;
}

@keyframes m{to {transform:translateX(150px)}}
<div class="box">
  <span><img src="https://i.stack.imgur.com/hXyA5.png"></span>
<div>
like image 81
Temani Afif Avatar answered Jan 27 '23 11:01

Temani Afif


I allowed for this behaviour in my previous answer (in the linked question). Just set dx to a non-zero value.

ball = {x: 82, y: 0, dx: 1, dy: 0};
  • x and y are the starting position of the ball
  • dx and dy are the initial velocity of the ball

let ballElem = document.getElementById("ball");

let GRAVITY = 40;        // Acceleration due to gravity (pixels / sec /sec)
let FLOOR_Y = 200 - 25;  // Y coord of floor. The 25 here is because ball.y is the top of the ball.
let BOUNCINESS = 0.8;    // Velocity retained after a bounce
let LIMIT = 0.1;         // Minimum velocity required to keep animation running
let ball = {};
let lastFrameTime = null;

ballElem.addEventListener("click", startAnim);


function startAnim()
{
  ball = {x: 82, y: 0, dx: 1, dy: 0};
  lastFrameTime = null;
  requestAnimationFrame(animStep);
}


function animStep(timestamp)
{
  if (lastFrameTime === null)
    lastFrameTime = timestamp;
  // Milliseconds elapsed since last step
  const elapsed = timestamp - lastFrameTime;
  lastFrameTime = timestamp;
  
  ball.dy += GRAVITY * elapsed / 1000;
  ball.y += ball.dy;
  ball.x += ball.dx;   // not really used in this example

  if (ball.y > FLOOR_Y) {
    // Step has taken us below the floor, so we need to rebound the ball.
    ball.y -= (ball.y - FLOOR_Y);
    ball.dy = -ball.dy * BOUNCINESS;
  }
  
  // Update the <image> element x and y
  ballElem.x.baseVal.value = ball.x;
  ballElem.y.baseVal.value = ball.y;
  
  // Request another animation step
  if (Math.abs(ball.y - FLOOR_Y) > LIMIT ||  // Not on ground
      Math.abs(ball.dy) > LIMIT ||           // or still moving
      Math.abs(ball.dx) > LIMIT) {
    requestAnimationFrame(animStep);
  }
}
<svg id="svg1" 
     width="200" height="200" viewBox="0 0 200 200" preserveAspectRatio="xMinYMin meet" style="border:1px solid" >  
 
  <image id="ball" xlink:href="https://i.stack.imgur.com/hXyA5.png" x="82" width="25px" height="25px"/>
 
</svg>
like image 43
Paul LeBeau Avatar answered Jan 27 '23 13:01

Paul LeBeau