Как сделать анимацию скатывания шарика по желобу и падение его вниз

Необходимо сделать анимацию скатывания шарика по желобу и падение его вниз c возвращением на исходную позицию.

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

Сценарий анимации:

  1. Поднимается вверх заслонка
  2. Шарик катится по жёлобу вниз
  3. Падает вниз
  4. Заслонка опускается на прежнее место
  5. Шарик появляется на исходной позиции
  6. Действия повторяются

Очень желательно выполнить анимацию средствами SMIL SVG


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

Автор решения: Sevastopol'

Анимация по клику

<svg id="svg" viewBox="-60 60 250 300" height="300" width="500">

<!--желоб-->
<path stroke="black" stroke-width="1.2px" fill="none" d="m 50,205 c 0,0 250,-65 250,-65 0,0 -30,-15 -30,-15 0,0 0,-20 0,-20 0,0 -255,60 -255,60 0,0 0,20 0,20 0,0 255,-60 255,-60 0,0 -255,60 -255,60 0,0 35,20 35,20 z" />

<!--мячик-->
<circle r="10" cx="250" cy="138" fill="#9fb8ea" stroke="#767fdf" stroke-width="2px">
<!--путь для движения мячика-->
<path d="m 0,0 c 0,0 -206.30494,54.24106 -206.30494,54.24106 -16.65381,5.76827 -33.30706,11.53635 -44.42193,30.70346 -11.11488,19.16711 -16.69001,51.73148 -22.26526,84.29654" stroke="none" stroke-width="1" fill="none" id="mPath"/>
<!--анимация движения мячика-->
<animateTransform begin="circle.end" id="pause" dur="3s" type="translate" attributeType="XML" attributeName="transform" restart="whenNotActive"/>
<animateMotion id="circle" dur="2s" begin="svg.click+0.3s; pause.end" fill="remove" restart="whenNotActive"><mpath xlink:href="#mPath"/></animateMotion>
</circle>

<!--заслонка-->
<path id="zzz" cx="0" cy="0" transform="translate(0,0)" stroke="black" stroke-width="1.2px" fill="none" d="m 225.72066,135.41308 25.72041,16.11051 0.74891,-49.39546 L 225,95.000001 Z" />
<!--анимация заслонки-->
<animateTransform xlink:href="#zzz" id="zzz_an" attributeName="transform" attributeType="XML" type="translate" begin="svg.click;zzz_an.end+4s" values="0 0; 0 -30; 0 -30; 0 0;" dur="1s" repeatCount="1" fill="freeze" restart="whenNotActive" />

</svg>

Анимация с двумя мячиками:

<svg id="svg" viewBox="-60 60 250 300" height="300" width="500">

<!--желоб-->
<path stroke="chocolate" stroke-width="1.2px" fill="khaki" d="m 50,205 c 0,0 250,-65 250,-65 0,0 -30,-15 -30,-15 0,0 0,-20 0,-20 0,0 -255,60 -255,60 0,0 0,20 0,20 0,0 255,-60 255,-60 0,0 -255,60 -255,60 0,0 35,20 35,20 z" />

<!--путь для движения мячика1-->
<path d="m 0,0 c 0,0 -206.30494,54.24106 -206.30494,54.24106 -16.65381,5.76827 -33.30706,11.53635 -44.42193,30.70346 -11.11488,19.16711 -16.69001,51.73148 -22.26526,84.29654" stroke="none" stroke-width="1" fill="none" id="mPath"/>
<!--путь для движения мячика2-->
<path d="m -20,6 c -20,6 -206.30494,54.24106 -206.30494,54.24106 -16.65381,5.76827 -33.30706,11.53635 -44.42193,30.70346 -11.11488,19.16711 -16.69001,51.73148 -22.26526,84.29654" stroke="none" stroke-width="1" fill="none" id="mPath2"/>

<!--мячик1-->
<circle r="10" cx="250" cy="138" fill="#9fb8ea" stroke="#767fdf" stroke-width="2px">
<!--анимация движения мячика1-->
<animateTransform begin="circle1.end" id="pause1" dur="3s" type="translate" attributeType="XML" attributeName="transform" restart="whenNotActive"/>
<animateMotion id="circle1" dur="2s" begin="svg.click+1s; pause1.end" fill="remove" restart="whenNotActive"><mpath xlink:href="#mPath"/></animateMotion>
</circle>

<!--мячик2-->
<circle r="10" cx="250" cy="138" transform="translate(20,-6)" fill="aquamarine" stroke="green" stroke-width="2px">
<!--анимация движения мячика2-->
<animateTransform begin="circle2.end" id="pause2" dur="3s" type="translate" attributeType="XML" attributeName="transform" restart="whenNotActive"/>
<animateMotion id="circle2" dur="2s" begin="svg.click+2s;pause2.end" fill="remove" restart="whenNotActive"><mpath xlink:href="#mPath2"/></animateMotion>
<animateTransform begin="svg.click+1s;transf.end+4s" id="transf" dur="1s" type="translate" attributeType="XML" attributeName="transform" values="20 -6; 0 0" restart="whenNotActive"/>
</circle>

<!--заслонка-->
<path id="zzz" cx="0" cy="0" transform="translate(0,0)" stroke="black" stroke-width="1.2px" fill="rgba(0,0,0,0.5)" d="m 225.72066,135.41308 25.72041,16.11051 0.74891,-49.39546 L 225,95.000001 Z" />
<!--анимация заслонки-->
<animateTransform  id="zzz_an" xlink:href="#zzz" attributeName="transform" attributeType="XML" type="translate" begin="svg.click;zzz_an.end+2s" values="0 0; 0 0; 0 -30; 0 0; 0 0; 0 -30; 0 0; 0 0;" dur="3s" repeatCount="1" fill="freeze" additive="sum" restart="whenNotActive" />

</svg>

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

path трассы движения шарика, жёлоба взял из ответа

Выполнил пункты сценария анимации с помощью логических цепочек в атрибуте begin

Для этого присвоил каждой анимации свой ID и написал условия запуска:

begin="anD.end", что означает - анимация движения шарика начинается после окончания анимации подъема заслонки и так далее по остальным пунктам сценария

<svg id="svg1" xmlns="http://www.w3.org/2000/svg"  xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-60 60 250 300" height="300" width="500" >
  <defs>
    <path id="trace" d="m0 0c0 0-206.3 54.2-206.3 54.2-16.7 5.8-33.3 11.5-44.4 30.7-11.1 19.2-16.7 51.7-22.3 84.3" stroke="none" stroke-width="1" fill="none"/>
    <radialGradient id="grad_blue" cx="20%" cy="20%" r="100%" fx="30%" fy="30%">
   <stop stop-color="white" offset="0"/>
   <stop stop-color="blue" offset="25%"/>
   <stop stop-color="rgb(0,0,192)" offset="50%"/>
   <stop stop-color="rgb(0,0,127)" offset="70%"/>
   <stop stop-color="rgb(0,0,64)" offset="85%"/>
   <stop stop-color="rgb(0,0,0)" offset="100%"/> 
   </radialGradient>
  </defs>
       
       <!-- Жёлоб -->
  <path stroke="black" stroke-width="1" fill="#EAEAEA" d="m50 205c0 0 250-65 250-65 0 0-30-15-30-15 0 0 0-20 0-20 0 0-255 60-255 60 0 0 0 20 0 20 0 0 255-60 255-60 0 0-255 60-255 60 0 0 35 20 35 20z"/>
         <!-- Шарик -->
<circle id="circ" r="12" cx="251" cy="138" fill="url(#grad_blue)" stroke="#767fdf" stroke-width="2">
      <animateMotion id="anM" begin="anD.end" dur="3s" restart="whenNotActive">
        <mpath xlink:href="#trace" />
  </circle>  
      <!-- Заслонка -->
 <path id="damper"   stroke="black" stroke-width="1" fill="#ACACAC" d="m225.7 135.4 25.7 16.1 0.7-49.4L225 95Z">
      <animateTransform id="anD" attributeName="transform" type="translate" begin="svg1.click;anM.end+1s" dur="0.5s" values="0,0;0 -30" fill="freeze"  restart="whenNotActive"/> 
         <animateTransform id="anD2" attributeName="transform" type="translate" begin="anM.begin+0.5s" dur="0.5s" values="0,-30;0 0" fill="freeze" restart="whenNotActive" />
  </path>  
 </svg>

На мой взгляд, появление шарика перед заслонкой выглядит слишком резко.

Попытался смягчить этот эффект дополнительной анимацией появления и исчезновения шарика с помощью атрибута opacity

<animate id="anOp2" attributeName="opacity" begin="anM.end-0.5s" dur="0.5s" 
   values="1;0" fill="freeze" restart="whenNotActive" />

<svg id="svg1" xmlns="http://www.w3.org/2000/svg"  xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-60 60 250 300" height="300" width="500" >
  <defs>
    <path id="trace" d="m0 0c0 0-206.3 54.2-206.3 54.2-16.7 5.8-33.3 11.5-44.4 30.7-11.1 19.2-16.7 51.7-22.3 84.3" stroke="none" stroke-width="1" fill="none"/>
    <radialGradient id="grad_red" cx="20%" cy="20%" r="100%" fx="30%" fy="30%">
   <stop stop-color="white" offset="0"/>
   <stop stop-color="blue" offset="25%"/>
   <stop stop-color="rgb(0,0,192)" offset="50%"/>
   <stop stop-color="rgb(0,0,127)" offset="70%"/>
   <stop stop-color="rgb(0,0,64)" offset="85%"/>
   <stop stop-color="rgb(0,0,0)" offset="100%"/> 
   </radialGradient>
  </defs>
       <!-- Жёлоб -->
  <path stroke="black" stroke-width="1" fill="#d3d3d3" d="m50 205c0 0 250-65 250-65 0 0-30-15-30-15 0 0 0-20 0-20 0 0-255 60-255 60 0 0 0 20 0 20 0 0 255-60 255-60 0 0-255 60-255 60 0 0 35 20 35 20z"/>
<circle id="circ" r="12" cx="250" cy="138" opacity="0" fill="url(#grad_red)" stroke="none" stroke-width="2">
      <animate id="anOp" attributeName="opacity" begin="svg1.click;anOp2.end" dur="0.5s" values="0;1" fill="freeze" restart="whenNotActive" />
      <animate id="anOp2" attributeName="opacity" begin="anM.end-0.5s" dur="0.5s" values="1;0" fill="freeze" restart="whenNotActive" />
      <animateMotion id="anM" begin="anD.end" dur="3s">
        <mpath xlink:href="#trace" />
      </animateMotion>  
  </circle>  
      <!-- Заслонка -->
 <path id="damper"   stroke="black" fill="#d3d3d3" stroke-width="1" fill="none" d="m225.7 135.4 25.7 16.1 0.7-49.4L225 95Z">
      <animateTransform id="anD" attributeName="transform" type="translate" begin="anOp.end" dur="0.5s" values="0,0;0 -30" fill="freeze" restart="whenNotActive" /> 
         <animateTransform id="anD2" attributeName="transform" type="translate" begin="anM.begin+0.5s" dur="0.5s" values="0,-30;0 0" fill="freeze" restart="whenNotActive" />
  </path>  
 </svg>

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

Защита от повторных кликов

Чтобы исключить влияние повторных кликов на работающее анимационное приложение svg, можно использовать:

1. restart = "whenNotActive"

Это значение указывает, что анимацию можно перезапустить только тогда, когда она неактивна (т.е. после активного окончания). Попытки перезапустить анимацию во время ее активной продолжительности игнорируются.

<svg id="svg1" xmlns="http://www.w3.org/2000/svg"  xmlns:xlink="http://www.w3.org/1999/xlink" 
         width="300" height="300" viewBox="0 0 300 300" style="border:1px solid" >
        
    <path transform="scale(0.35)" d="M100,10 L100,10 40,180 190,60 10,60 160,180 z" stroke="blue" fill="darkblue" stroke-width="4" >
    
    <animateTransform id="an"
       attributeName="transform"
       type="translate"
       begin="svg1.click"
       dur="3s"
       restart="whenNotActive"
       fill="freeze"
       values="0,0;650,650;0,0"
       repeatCount="indefinite"
       additive="sum"
       />
    </path>
</svg>   

Источник

В примере выше restart="whenNotActive" является эффективной защитой от повторных кликов, так как анимация бесконечна - repeatCount="indefinite" Но это не сработает, если анимация имеет фиксированную продолжительность begin="svg1.click" dur="0.5s"

2. restart = "never"

Это значение указывает, что анимацию нельзя перезапустить на время загрузки документа.

Решает проблему, но делает это грубо, так как нет повторного запуска анимации при клике. Значит это не применимо, если есть необходимость в бесконечном повторении анимации после прохождения цикла анимаций.

<svg id="svg1" xmlns="http://www.w3.org/2000/svg"  xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-60 60 250 300" height="300" width="500" >
  <defs>
    <path id="trace" d="m0 0c0 0-206.3 54.2-206.3 54.2-16.7 5.8-33.3 11.5-44.4 30.7-11.1 19.2-16.7 51.7-22.3 84.3" stroke="none" stroke-width="1" fill="none"/>
    <radialGradient id="grad_red" cx="20%" cy="20%" r="100%" fx="30%" fy="30%">
   <stop stop-color="white" offset="0"/>
   <stop stop-color="blue" offset="25%"/>
   <stop stop-color="rgb(0,0,192)" offset="50%"/>
   <stop stop-color="rgb(0,0,127)" offset="70%"/>
   <stop stop-color="rgb(0,0,64)" offset="85%"/>
   <stop stop-color="rgb(0,0,0)" offset="100%"/> 
   </radialGradient>
  </defs>
       
       <!-- Жёлоб -->
  <path stroke="black" stroke-width="1" fill="#d3d3d3" d="m50 205c0 0 250-65 250-65 0 0-30-15-30-15 0 0 0-20 0-20 0 0-255 60-255 60 0 0 0 20 0 20 0 0 255-60 255-60 0 0-255 60-255 60 0 0 35 20 35 20z"/>
  
<circle id="circ" r="12" cx="250" cy="138" opacity="0" fill="url(#grad_red)" stroke="none" stroke-width="2">
      <animate id="anOp" attributeName="opacity" begin="svg1.click;anOp2.end" dur="0.5s" values="0;1" fill="freeze" restart="never" />
      <animate id="anOp2" attributeName="opacity" begin="anM.end-0.5s" dur="0.5s" values="1;0" fill="freeze" restart="whenNotActive" />
      
      <animateMotion id="anM" begin="anD.end" dur="3s">
        <mpath xlink:href="#trace" />
      </animateMotion>  
  </circle>  
      <!-- Заслонка -->
 <path id="damper"   stroke="black" fill="#d3d3d3" stroke-width="1" fill="none" d="m225.7 135.4 25.7 16.1 0.7-49.4L225 95Z">
      <animateTransform id="anD" attributeName="transform" type="translate" begin="anOp.end" dur="0.5s" values="0,0;0 -30" fill="freeze" restart="whenNotActive" /> 
         <animateTransform id="anD2" attributeName="transform" type="translate" begin="anM.begin+0.5s" dur="0.5s" values="0,-30;0 0" fill="freeze" restart="whenNotActive" />
  </path>  
 </svg>

3. window.onload и begin="0s"

Событие load на объекте window наступает, когда загрузилась вся страница, включая стили, картинки и другие ресурсы.

Поэтому вместо клика begin="svg1.click", запускающего анимацию, нужно написать условие begin="0s" и когда HTML страничка загрузится, начнет работать SVG и клики по работающему SVG будут игнорироваться.

Повторный запуск всего цикла анимации будет возможен только после перезагрузки всей странички HTML

<svg id="svg1" xmlns="http://www.w3.org/2000/svg"  xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-60 60 250 300" height="300" width="500" >
  <defs>
    <path id="trace" d="m0 0c0 0-206.3 54.2-206.3 54.2-16.7 5.8-33.3 11.5-44.4 30.7-11.1 19.2-16.7 51.7-22.3 84.3" stroke="none" stroke-width="1" fill="none"/>
    <radialGradient id="grad_red" cx="20%" cy="20%" r="100%" fx="30%" fy="30%">
   <stop stop-color="white" offset="0"/>
   <stop stop-color="blue" offset="25%"/>
   <stop stop-color="rgb(0,0,192)" offset="50%"/>
   <stop stop-color="rgb(0,0,127)" offset="70%"/>
   <stop stop-color="rgb(0,0,64)" offset="85%"/>
   <stop stop-color="rgb(0,0,0)" offset="100%"/> 
   </radialGradient>
  </defs>
       
       <!-- Жёлоб -->
  <path stroke="black" stroke-width="1" fill="#d3d3d3" d="m50 205c0 0 250-65 250-65 0 0-30-15-30-15 0 0 0-20 0-20 0 0-255 60-255 60 0 0 0 20 0 20 0 0 255-60 255-60 0 0-255 60-255 60 0 0 35 20 35 20z"/>
  
<circle id="circ" r="12" cx="250" cy="138" opacity="0" fill="url(#grad_red)" stroke="none" stroke-width="2">
      <animate id="anOp" attributeName="opacity" begin="0s;anOp2.end" dur="0.5s" values="0;1" fill="freeze"  />
      <animate id="anOp2" attributeName="opacity" begin="anM.end-0.5s" dur="0.5s" values="1;0" fill="freeze" restart="whenNotActive" />
      
      <animateMotion id="anM" begin="anD.end" dur="3s">
        <mpath xlink:href="#trace" />
      </animateMotion>  
  </circle>  
      <!-- Заслонка -->
 <path id="damper"   stroke="black" fill="#d3d3d3" stroke-width="1" fill="none" d="m225.7 135.4 25.7 16.1 0.7-49.4L225 95Z">
      <animateTransform id="anD" attributeName="transform" type="translate" begin="anOp.end" dur="0.5s" values="0,0;0 -30" fill="freeze" restart="whenNotActive" /> 
         <animateTransform id="anD2" attributeName="transform" type="translate" begin="anM.begin+0.5s" dur="0.5s" values="0,-30;0 0" fill="freeze" restart="whenNotActive" />
  </path>  
 </svg>

→ Ссылка