Отскоки мяча по параболической траектории

Вопрос инициирован топиком: Как анимировать отскок теннисного мячика

В этом вопросе отскоки были по вертикали

<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>  

Данный вопрос касается отскоков с разными величинами смещения по высоте и длине

введите сюда описание изображения

Понятно, что можно нарисовать в векторном редакторе траекторию движения мяча, как на изображении и далее реализовать движение по траектории, как в примере выше.

Но вызывает затруднение реализация неравномерности движения и скорости.


Ответы (5 шт):

Автор решения: Stranger in the Q

Опережая Ваш следующий вопрос, сделал чтобы он еще и крутился:

let t, h, max = 176;

function start() {
  t = Date.now(); 
  h = max;
};

addEventListener('click', start);
start();

requestAnimationFrame(function a(){
  const dt = Date.now() - t;
  h *= 0.97;
  const wave = Math.cos(dt/(200 - dt/25));
  ballGroup.setAttribute('transform', `translate(${dt/(10+dt/150)}, ${max - Math.abs(wave)*h})`);
  ball.setAttribute('transform', `rotate(${3*dt/(10+dt/150)})`);
  requestAnimationFrame(a)
})
<svg width="200" height="200"viewBox="0 0 200 200" style="border:1px solid" >  
  <g id="ballGroup">
    <image id="ball" xlink:href="https://i.stack.imgur.com/hXyA5.png" x=-12.5 y=-12.5 width="25px" height="25px" />
  </g>
  <polyline points="5,190 190,190" stroke="silver" stroke-width="4" />
</svg>

→ Ссылка
Автор решения: Barmaley

Не силен в JavaScript, посему напишу некий псевдокод.

Как известно, отскок мяча (она же парабола) описывается по вертикали квадратным уравнением, а по горизонтали равномерным, то есть что-то типа:

y = vY*t - G*t^2/2
x = vX*t

vY - начальная вертикальная скорость, vX - начальная горизонтальная скорость, t - время, G - ускорение свободного падения, для Земли равна 9.81 м/c^2 (для Луны 6 раз ниже - 1.62 м/с^2).

Далее, при достижении поверхности земли (y=0), мяч отскакивает повторно и при каждом прыжке теряет некую долю своей скорости/энергии, соответственно, формулы будут такие

y = N*k*vY*t - G*t^2/2
x = N*k*vX*t

Где k - коэффициент отскока (приблизительно равен ~90%), N - количество отскоков.

Осталось это запрограммировать в вашу анимацию, что очевидно не составит проблем.

P.S. Это точно лучше, чем эмуляция параболы cos() - да и быстрее кстати, в плане скорости расчета ну и физически более правдоподобно

→ Ссылка
Автор решения: Alexandr_TT

Решение svg smil

Траекторию движения мяча создал по картинке в вопросе в векторном редакторе Inkscape

Для анимации движения вдоль траектории использовал - animateMotion

.container {
width:60%;
height:60%;
}
<div class="container">
<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"
       viewBox="0 0 488 232" preserveAspectRatio="xMinYMin meet" style="border:1px solid" >  
<defs>
 <path id="trace" d="m 25.652812,16.016049 v 181.392771 0 0 l -0.596577,25.31062 c 0,0 12.411076,-62.72171 21.476772,-93.33069 5.109455,-17.25132 11.107049,-34.303277 18.493888,-50.709043 6.68532,-14.84773 11.981393,-31.214941 23.266504,-42.953546 5.128983,-5.335092 11.703068,-10.892125 19.090461,-11.334963 9.33174,-0.559391 18.66956,5.119788 25.65281,11.334963 12.5853,11.201058 18.46109,28.314233 25.65282,43.550123 7.68011,16.270538 13.21524,33.508736 18.49388,50.709046 9.35008,30.46698 17.6561,61.30229 23.26651,92.73411 0,0 7.63247,-46.2686 13.72127,-68.87103 4.15486,-15.42339 7.4192,-31.5679 15.511,-45.33985 5.42122,-9.226722 10.78091,-23.515729 21.47677,-23.863083 11.38465,-0.369722 18.27651,14.296709 24.45966,23.863083 8.5031,13.15573 11.7205,29.10909 16.10758,44.1467 6.69922,22.96293 9.48984,49.49348 15.511,70.06418 5.7106,-34.92901 6.57028,-45.00528 16.10758,-65.29157 5.26265,-11.19392 9.73289,-28.98491 22.07335,-29.82885 11.92521,-0.81554 20.99188,14.41196 25.05624,25.65281 4.04949,11.19974 6.57393,20.61892 9.54523,31.02201 3.6399,12.74398 5.25389,18.16556 10.14181,38.4456 2.98041,-18.27579 1.7071,-16.73913 4.17604,-24.72433 1.6821,-5.44034 3.72502,-10.96841 7.15892,-15.511 3.31674,-4.3876 7.05439,-11.27709 12.52812,-10.73839 7.04712,0.69355 9.92525,10.387 13.12469,16.70416 5.35774,10.57863 5.4736,18.42066 9.54524,34.26956 0.57788,-7.32318 1.59738,-10.47263 1.78973,-13.98594 0.5125,-9.36095 6.36879,-18.32474 10.84695,-17.16023 9.45261,2.45807 13.01612,16.09152 13.01612,16.09152" style="fill:none;stroke-opacity:0.9;stroke-width:2;stroke:#e8204f"/>
</defs>
 
<image xlink:href="https://i.stack.imgur.com/hXyA5.png" x="0" y="0" width="25px" height="25px" >
   <animateMotion id="anT"
     
     dur="1.5s"
     begin="svg1.click+0.5s;anT.end+1s"
        repeatCount="1"
        fill="freeze"
        restart="whenNotActive" > 
        <mpath xlink:href="#trace" />
    </animateMotion>    
</image>
   </svg>
 </div>

→ Ссылка
Автор решения: Alexandr_TT

Решение CSS

CSS-анимация может лучше это приблизить. Вам нужно:

  1. найти подходящую кривую Безье для функции синхронизации https://cubic-bezier.com/#.17,.76,.58,1
  2. обновите коэффициент продолжительности до желаемого
  3. добавьте столько шагов, сколько хотите, добавив больше анимации

Затем вам нужно правильно рассчитать задержку на основе продолжительности (предыдущая задержка анимации + 2 * предыдущая продолжительность анимации).

Главный трюк заключается в том, что использование чередования и запуск каждой анимации дважды даст вам зеркальный эффект и идеальную параболическую кривую (половина ее определяется кривой Безье).

.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>

Добавление горизонтального движения:

.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>

Свободный перевод ответа от участника @Temani Afif.

→ Ссылка
Автор решения: serega da

добавлю в общую кучку парабол. Анимация-bounce Там расчет общей параболы(число прыжков, контрольные точки) в EXCEL. Чистая парабола, по y=x2. Остается скопировать в CSS в linear() (Перевести в JS, в накопление массива -- не потяну )).

→ Ссылка