如何制作网球从水平表面反弹的动画
How to animate a tennis ball to bounce off a horizontal surface
我有一张网球的图片:
有必要制作球落下并随后从固体表面反弹的动画。
我得到了这种动画,但是看起来不太真实:
要启动动画,请单击图像:
<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 attributeName="transform" type="translate" dur="1s" begin="svg1.click" values="0,0;0,168;0" repeatCount="3" />
</image>
<polyline points="5,190 190,190" stroke="silver" stroke-width="4" />
</svg>
需要第一次反弹小于球下落高度,第二次反弹小于第一次反弹高度,第三次反弹小于第二次
你是如何做到这一点的?解决方案可能在 SMIL SVG、CSS、JS
上
首选 SMIL SVG 解决方案。
最现实的方法是用JS模拟物理。
像这样:
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: 0, 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>
SVG SMIL 解决方案
可以在 animateTransform
动画命令的 values = ""
属性中设置可变的弹跳量。
可以使用 keyTimes
属性的值来控制每个时间段的球速。
restart = "whenNotActive"
- 防止在动画完全完成之前重新启动动画。
点击后动画开始
<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"
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>
循环动画示例
为此,在动画启动条件中写入如下条件:
begin = "svg1.click; anT.end + 1s"
,其中
svg1.click
- 点击后第一次开始动画
anT.end + 1s
- 1秒后重新开始动画,id = "anT"的动画结束后
<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>
我有一张网球的图片:
有必要制作球落下并随后从固体表面反弹的动画。
我得到了这种动画,但是看起来不太真实:
要启动动画,请单击图像:
<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 attributeName="transform" type="translate" dur="1s" begin="svg1.click" values="0,0;0,168;0" repeatCount="3" />
</image>
<polyline points="5,190 190,190" stroke="silver" stroke-width="4" />
</svg>
需要第一次反弹小于球下落高度,第二次反弹小于第一次反弹高度,第三次反弹小于第二次
你是如何做到这一点的?解决方案可能在 SMIL SVG、CSS、JS
上首选 SMIL SVG 解决方案。
最现实的方法是用JS模拟物理。
像这样:
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: 0, 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>
SVG SMIL 解决方案
可以在 animateTransform
动画命令的 values = ""
属性中设置可变的弹跳量。
可以使用 keyTimes
属性的值来控制每个时间段的球速。
restart = "whenNotActive"
- 防止在动画完全完成之前重新启动动画。
点击后动画开始
<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"
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>
循环动画示例
为此,在动画启动条件中写入如下条件:
begin = "svg1.click; anT.end + 1s"
,其中
svg1.click
- 点击后第一次开始动画
anT.end + 1s
- 1秒后重新开始动画,id = "anT"的动画结束后
<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>