JS – 3-20 动画

贝塞尔曲线

贝塞尔曲线用于计算机图形绘制形状,CSS 动画和许多其他地方。

控制点

贝塞尔曲线由控制点定义。

  1. 控制点不总是在曲线上这是非常正常的,稍后我们将看到曲线是如何构建的。
  2. 曲线的阶次等于控制点的数量减一。 对于两个点我们能得到一条线性曲线(直线),三个点 — 一条二阶曲线,四个点 — 一条三阶曲线。
  3. 曲线总是在控制点的凸包内部:
    Pasted image 20240703104759.png

    由于最后一个属性,在计算机图形学中,可以优化相交测试。如果凸包不相交,则曲线也不相交。因此,首先检查凸包的交叉点可以非常快地给出“无交叉”结果。检查交叉区域或凸包更容易,因为它们是矩形,三角形等(见上图),比曲线简单的多

数学

贝塞尔曲线可以使用数学方程式来描述。
...

德卡斯特里奥算法

德卡斯特里奥算法与曲线的数学定义相同,但直观地显示了曲线是如何被建立的。
德卡斯特里奥算法构造三点贝塞尔曲线:

  1. 绘制控制点。在上面的演示中,它们标有:12 和 3
  2. 创建控制点 1 → 2 → 3 间的线段. 在上面的演示中它们是棕色的。
  3. 参数 t 从 0 to 1 变化。 在上面的演示中取值 0.05:循环遍历 0, 0.05, 0.1, 0.15, ... 0.95, 1
    对于每一个 t 的取值:

    • 在每一个棕色线段上我们取一个点,这个点距起点的距离按比例 t 取值。由于有两条线段,我们能得到两个点
      例如,当 t=0 — 所有点都在线段起点处,当 t=0.25 — 点到起点的距离为线段长度的 25%,当 t=0.5 — 50%(中间),当 t=1 — 线段终点。
    • 连接这些点,下面这张图中连好的线被绘制成蓝色。
      Pasted image 20240703105127.png
  4. 现在在蓝色线段上取一个点,距离比例取相同数值的 t。也就是说,当 t=0.25(左图)时,我们取到的点位于线段的左 1/4 终点处,当 t=0.5(右图)时 — 线段中间。在上图中这一点是红色的。
  5. 随着 t 从 0 to 1 变化,每一个 t 的值都会添加一个点到曲线上。这些点的集合就形成的贝塞尔曲线。它在上面的图中是红色的,并且是抛物线状的。
    对于 4 个点
    算法:
  • 控制点通过线段连接:1 → 2、2 → 3 和 3 → 4。 我们能得到 3 条棕色的线段。
  • 对于 0 to 1 之间的每一个 t
    • 我们在这些线段上距起点距离比例为 t 的位置取点。把这些点连接起来,然后得到两条绿色线段。
    • 在这些线段上同样按比例 t 取点,得到一条蓝色线段。
    • 在蓝色线段按比例 t 取点。在上面的例子中是红色的。
  • 这些点在一起组成了曲线。
    该算法是递归的,并且可以适应于任意数量的控制点。
    给定 N 个控制点,我们将它们连接起来以获得初始的 N-1 个线段。
    然后对从 0 到 1 的每一个 t
  • 在每条线段上按 t 比例距离取一个点并且连接 —— 会得到 N-2 个线段。
  • 在上面得到的每条线段上按 t 比例距离取一个点并且连接 —— 会得到 N-3 个线段,以此类推……
  • 直到我们得到一个点。得到的这些点就形成了曲线。

CSS 动画

CSS 动画可以在不借助 Javascript 的情况下做出一些简单的动画效果。

transition

CSS 提供了四个属性来描述一个过渡:

  • transition-property
  • transition-duration
  • transition-timing-function
  • transition-delay

transition-property

在 transition-property 中我们可以列举要设置动画的所有属性,如:left、margin-left、height 和 color
不是所有的 CSS 属性都可以使用过渡动画,但是它们中的大多数都是可以的。all 表示应用在所有属性上。

transition-duration

transition-duration 允许我们指定动画持续的时间。时间的格式参照 CSS 时间格式:单位为秒 s 或者毫秒 ms

transition-delay

transition-delay 允许我们设定动画开始前的延迟时间。例如,对于 transition-delay: 1s,动画将会在属性变化发生 1 秒后开始渲染。
你也可以提供一个负值。那么动画将会从整个过渡的中间时刻开始渲染。例如,对于 transition-duration: 2s,同时把 delay 设置为 -1s,那么这个动画将会持续 1 秒钟,并且从正中间开始渲染。

负值有时可以简化js动画代码

transition-timing-function

时间函数描述了动画进程在时间上的分布。它是先慢后快还是先快后慢?
这个属性接受两种值:一个贝塞尔曲线(Bezier curve)或者阶跃函数(steps)。我们先从贝塞尔曲线开始,这也是较为常用的。

贝塞尔曲线

时间函数可以用贝塞尔曲线描述,通过设置四个满足以下条件的控制点:

  1. 第一个应为:(0,0)
  2. 最后一个应为:(1,1)
  3. 对于中间值,x 必须位于 0..1 之间,y 可以为任意值。
    CSS 中设置一贝塞尔曲线的语法为:cubic-bezier(x2, y2, x3, y3)。这里我们只需要设置第二个和第三个值,因为第一个点固定为 (0,0),第四个点固定为 (1,1)
    时间函数描述了动画进行的快慢。
  • x 轴表示时间:0 —— 开始时刻,1 —— transition-duration的结束时刻。
  • y 轴表示过程的完成度:0 —— 属性的起始值,1 —— 属性的最终值。
    CSS 提供几条内建的曲线:lineareaseease-inease-out 和 ease-in-out
    贝塞尔曲线可以使动画『超出』其原本的范围。
    曲线上的控制点的 y 值可以使任意的:不管是负值还是一个很大的值。如此,贝塞尔曲线就会变得很低或者很高,让动画超出其正常的范围。
    但是,如何针对特定的任务寻找到合适的贝塞尔曲线呢?事实上,有很多工具可以帮到你。比方说,我们可以利用这个网站:http://cubic-bezier.com/

阶跃函数

时间函数 steps(number of steps[, start/end]) 允许你让动画分段进行,number of steps 表示需要拆分为多少段。
steps 的第一个参数表示段数。时间间隔也会以同样的方式被拆分
第二个参数可以取 start 或 end 两者其一。
start 表示在动画开始时,我们需要立即开始第一段的动画。
另一个值 end 表示:改变不应该在最开始的时候发生,而是发生在每一段的最后时刻。
另外还有一些简写值:

  • step-start —— 等同于 steps(1, start)。即:动画立刻开始,并且只有一段。也就是说,会立刻开始,紧接着就结束了,宛如没有动画一样。
  • step-end —— 等同于 steps(1, end)。即:在 transition-duration 结束时生成一段动画。

transitionend 事件

CSS 动画完成后,会触发 transitionend 事件。
这被广泛用于在动画结束后执行某种操作。我们也可以用它来串联动画。
动画通过 go 函数初始化,并且在每次动画完成后都会重复执行,并转变方向:

boat.onclick = function() {
  //...
  let times = 1;

  function go() {
    if (times % 2) {
      // 向右移动
      boat.classList.remove('back');
      boat.style.marginLeft = 100 * times + 200 + 'px';
    } else {
      // 向左移动
      boat.classList.add('back');
      boat.style.marginLeft = 100 * times - 200 + 'px';
    }

  }

  go();

  boat.addEventListener('transitionend', function() {
    times++;
    go();
  });
};

transitionend 的事件对象有几个特定的属性:
event.propertyName :当前完成动画的属性,这在我们同时为多个属性加上动画时会很有用。
event.elapsedTime :动画完成的时间(按秒计算),不包括 transition-delay

关键帧动画

我们可以通过 CSS 提供的 @keyframes 规则整合多个简单的动画。
它会指定某个动画的名称以及相应的规则:哪个属性,何时以及何地渲染动画。然后使用 animation 属性把动画绑定到相应的元素上,并为其添加额外的参数。

JavaScript 动画

JavaScript 动画可以处理 CSS 无法处理的事情。

使用 setInterval

从 HTML/CSS 的角度来看,动画是 style 属性的逐渐变化。例如,将 style.left 从 0px 变化到 100px 可以移动元素。
如果我们用 setInterval 每秒做 50 次小变化,看起来会更流畅。电影也是这样的原理:每秒 24 帧或更多帧足以使其看起来流畅。

使用 requestAnimationFrame

假设我们有几个同时运行的动画。
如果我们单独运行它们,每个都有自己的 setInterval(..., 20),那么浏览器必须以比 20ms 更频繁的速度重绘。
每个 setInterval 每 20ms 触发一次,但它们相互独立,因此 20ms 内将有多个独立运行的重绘。
这几个独立的重绘应该组合在一起,以使浏览器更加容易处理。
换句话说,像下面这样:

setInterval(function() {
  animate1();
  animate2();
  animate3();
}, 20)

……比这样更好:

setInterval(animate1, 20);
setInterval(animate2, 20);
setInterval(animate3, 20);

还有一件事需要记住。有时当 CPU 过载时,或者有其他原因需要降低重绘频率。例如,如果浏览器选项卡被隐藏,那么绘图完全没有意义。
有一个标准动画时序提供了 requestAnimationFrame 函数。
它解决了所有这些问题,甚至更多其它的问题。
语法:

let requestId = requestAnimationFrame(callback);

这会让 callback 函数在浏览器每次重绘的最近时间运行。
如果我们对 callback 中的元素进行变化,这些变化将与其他 requestAnimationFrame 回调和 CSS 动画组合在一起。因此,只会有一次几何重新计算和重绘,而不是多次。
返回值 requestId 可用来取消回调:

// 取消回调的周期执行
cancelAnimationFrame(requestId);

callback 得到一个参数 —— 从页面加载开始经过的毫秒数。这个时间也可通过调用 performance.now() 得到。
通常 callback 很快就会运行,除非 CPU 过载或笔记本电量消耗殆尽,或者其他原因。
下面的代码显示了 requestAnimationFrame 的前 10 次运行之间的时间间隔。通常是 10-20ms:

<script>
  let prev = performance.now();
  let times = 0;

  requestAnimationFrame(function measure(time) {
    document.body.insertAdjacentHTML("beforeEnd", Math.floor(time - prev) + " ");
    prev = time;

    if (times++ < 10) requestAnimationFrame(measure);
  });
</script>

结构化动画

现在我们可以在 requestAnimationFrame 基础上创建一个更通用的动画函数:

function animate({timing, draw, duration}) {

  let start = performance.now();

  requestAnimationFrame(function animate(time) {
    // timeFraction 从 0 增加到 1
    let timeFraction = (time - start) / duration;
    if (timeFraction > 1) timeFraction = 1;

    // 计算当前动画状态
    let progress = timing(timeFraction);

    draw(progress); // 绘制

    if (timeFraction < 1) {
      requestAnimationFrame(animate);
    }

  });
}

animate 函数接受 3 个描述动画的基本参数:
duration
动画总时间,比如 1000
timing(timeFraction)
时序函数,类似 CSS 属性 transition-timing-function,传入一个已过去的时间与总时间之比的小数(0 代表开始,1 代表结束),返回动画完成度(类似 Bezier 曲线中的 y)。
draw(progress)
获取动画完成状态并绘制的函数。值 progress = 0 表示开始动画状态,progress = 1 表示结束状态。

animate({
  duration: 1000,
  timing(timeFraction) {
    return timeFraction;
  },
  draw(progress) {
    elem.style.width = progress * 100 + '%';
  }
});

时序函数

上文我们看到了最简单的线性时序函数。

n 次幂

function quad(timeFraction) {
  return Math.pow(timeFraction, 2)
}

增大幂会让动画加速得更快。

圆弧

function circ(timeFraction) {
  return 1 - Math.sin(Math.acos(timeFraction));
}

反弹:弓箭射击

与以前的函数不同,它取决于附加参数 x,即“弹性系数”。“拉弓弦”的距离由它定义。

function back(x, timeFraction) {
  return Math.pow(timeFraction, 2) * ((x + 1) * timeFraction - x);
}

弹跳

想象一下,我们正在抛球。球落下之后,弹跳几次然后停下来。

function bounce(timeFraction) {
  for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
    if (timeFraction >= (7 - 4 * a) / 11) {
      return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
    }
  }
}

伸缩动画

另一个“伸缩”函数接受附加参数 x 作为“初始范围”。

function elastic(x, timeFraction) {
  return Math.pow(2, 10 * (timeFraction - 1)) * Math.cos(20 * Math.PI * x / 3 * timeFraction)
}

逆转 ease*

我们有一组时序函数。它们的直接应用称为“easeIn”。
有时我们需要以相反的顺序显示动画。这是通过“easeOut”变换完成的。

easeOut

在“easeOut”模式中,我们将 timing 函数封装到 timingEaseOut中:

timingEaseOut(timeFraction) = 1 - timing(1 - timeFraction);

换句话说,我们有一个“变换”函数 makeEaseOut,它接受一个“常规”时序函数 timing 并返回一个封装器,里面封装了 timing 函数:

// 接受时序函数,返回变换后的变体
function makeEaseOut(timing) {
  return function(timeFraction) {
    return 1 - timing(1 - timeFraction);
  }
}

easeInOut

我们还可以在动画的开头和结尾都显示效果。该变换称为“easeInOut”。
给定时序函数,我们按下面的方式计算动画状态:

if (timeFraction <= 0.5) { // 动画前半部分
  return timing(2 * timeFraction) / 2;
} else { // 动画后半部分
  return (2 - timing(2 * (1 - timeFraction))) / 2;
}

封装器代码:

function makeEaseInOut(timing) {
  return function(timeFraction) {
    if (timeFraction < .5)
      return timing(2 * timeFraction) / 2;
    else
      return (2 - timing(2 * (1 - timeFraction))) / 2;
  }
}

bounceEaseInOut = makeEaseInOut(bounce);

更有趣的 "draw"

除了移动元素,我们还可以做其他事情。我们所需要的只是写出合适的 draw

啊哈,这里是小尾巴~
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇