动画开发 --- css和javascript方式

Transition

CSS3 过渡是元素从一种样式逐渐改变为另一种的效果

描述
transition-property 指定CSS属性的name,transition效果
transition-duration transition效果需要指定多少秒或毫秒才能完成
transition-timing-function 指定transition效果的转速曲线
transition-delay 定义transition效果开始的时候

transition-property

描述
none 没有属性会获得过渡效果。
all 所有属性都将获得过渡效果。
property 定义应用过渡效果的 CSS 属性名称列表,列表以逗号分隔。

transition-timing-function

描述
linear 规定以相同速度开始至结束的过渡效果(等于 cubic-bezier(0,0,1,1))。
ease 规定慢速开始,然后变快,然后慢速结束的过渡效果(cubic-bezier(0.25,0.1,0.25,1))。
ease-in 规定以慢速开始的过渡效果(等于 cubic-bezier(0.42,0,1,1))。
ease-out 规定以慢速结束的过渡效果(等于 cubic-bezier(0,0,0.58,1))。
ease-in-out 规定以慢速开始和结束的过渡效果(等于 cubic-bezier(0.42,0,0.58,1))。
cubic-bezier(n,n,n,n) 在 cubic-bezier 函数中定义自己的值。可能的值是 0 至 1 之间的数值。

Animation

创建动画

说明
animation-name 指定要绑定到选择器的关键帧的名称
animation-duration 动画指定需要多少秒或毫秒完成
animation-timing-function 设置动画将如何完成一个周期
animation-delay 设置动画在启动前的延迟间隔。
animation-iteration-count 定义动画的播放次数。infinite指定动画应该播放无限次(永远)
animation-direction 指定是否应该轮流反向播放动画。
animation-fill-mode 规定当动画不播放时(当动画完成时,或当动画有一个延迟未开始播放时),要应用到元素的样式。
animation-play-state 指定动画是否正在运行或已暂停。

animation-timing-function

描述
linear 动画从头到尾的速度是相同的。
ease 默认。动画以低速开始,然后加快,在结束前变慢。
ease-in 动画以低速开始。
ease-out 动画以低速结束。
ease-in-out 动画以低速开始和结束。
cubic-bezier(n,n,n,n) 在 cubic-bezier 函数中自己的值。可能的值是从 0 到 1 的数值。

animation-direction

描述
normal 默认值。动画按正常播放。
reverse 动画反向播放。
alternate 动画在奇数次(1、3、5...)正向播放,在偶数次(2、4、6...)反向播放。
alternate-reverse 动画在奇数次(1、3、5...)反向播放,在偶数次(2、4、6...)正向播放。

animation-fill-mode

描述
none 默认值。动画在动画执行之前和之后不会应用任何样式到目标元素。
forwards 在动画结束后(由 animation-iteration-count 决定),动画将应用该属性值。
backwards 动画将应用在 animation-delay 定义期间启动动画的第一次迭代的关键帧中定义的属性值。这些都是 from 关键帧中的值(当 animation-direction 为 "normal" 或 "alternate" 时)或 to 关键帧中的值(当 animation-direction 为 "reverse" 或 "alternate-reverse" 时)。
both 动画遵循 forwards 和 backwards 的规则。也就是说,动画会在两个方向上扩展动画属性。

animation-play-state

描述
paused 指定暂停动画
running 指定正在运行的动画

浏览器事件循环机制

浏览器界面的背后隐藏着很多用户感知不到的事件执行机制,包括加载资源,渲染,网络请求,用户交互等多种行为,动画也是其中的一部分,因为我们无法控制行为的优先级顺序,了解这些机制对开发怎么更好使用动画有好处.

  1. 所有的任务都被放主线程上运行形成一个执行栈
  2. 同步任务直接执行并阻塞后续任务等待结束,其中遇到一些异步任务会新开线程去执行该任务然后往下执行,异步任务执行完返回结果之后就把回调事件加入到任务队列(Queue)
  3. 执行栈(execution context stack)所有任务执行完之后,会到任务队列(Queue)里提取所有的微任务队列(micro tasks)事件执行完, 如果在微任务的执行中又加入了新的微任务,也会在这一步一起执行;
  4. 一次循环结束,GUI渲染线程接管检查,检测是否有渲染机会( rendering opportunity),渲染机会根据物理环境决定(依赖机子性能,根据屏幕刷新率、页面性能、页面是否在后台运行来共同决定);

    • 浏览器会尽可能的保持帧率稳定,例如页面性能无法维持 60fps(每 16.66ms 渲染一次)的话,那么浏览器就会选择 30fps 的更新速率,而不是偶尔丢帧。
    • 如果浏览器上下文不可见,那么页面会降低到 4fps 左右甚至更低。
    • 即使满足上面条件依然可能跳过渲染

      a. 浏览器判断更新渲染不会带来视觉上的改变。

      b. 帧动画回调为空

  5. 如果本轮不渲染则推迟也不会执行下面的操作,多个任务会合并到下一轮可渲染的时候,也有利于优化它们之间状态的合并或者重合,互相抵消等而出现的渲染消耗
  6. 对于需要渲染的文档,

    • 如果窗口的大小发生了变化,执行监听的 resize 方法。
    • 如果页面发生了滚动,执行 scroll 方法。
    • 执行帧动画回调,也就是 requestAnimationFrame 的回调。
    • 执行 IntersectionObserver 的回调。
  7. 重新渲染绘制用户界面。
  8. 判断异步队列是否都为空,如果是的话,则进行 Idle 闲置周期的算法,判断是否要执行 requestIdleCallback的回调函数。

浏览器会在保持任务顺序的前提下,可能分配四分之三的优先权给用户交互行为(鼠标和键盘事件),保证用户的输入得到最高优先级的响应,而剩下的优先级交给其他 Task,并且保证不会“饿死”它们。

对于resizescroll来说,并不是到了这一步才去执行滚动和缩放,浏览器会保存一个 pending scroll event targets,等到事件循环中的 scroll这一步,去派发一个事件到对应的目标上,驱动它去执行监听的回调函数而已。resize也是同理。

requestAnimationFrame

/**
 * 你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画
 * @param callback
 * @return 请求 ID
 */
window.requestAnimationFrame(callback)
描述
callback 下一次重绘之前更新动画帧所调用的函数。该回调函数会被传入DOMHighResTimeStamp参数,该参数与performance.now()的返回值相同,它表示requestAnimationFrame()开始去执行回调函数的时刻。
返回值 一个 long 整数,请求 ID ,是回调列表中唯一的标识。是个非零值,没别的意义。你可以传这个值给 window.cancelAnimationFrame() 以取消回调函数。

回调函数执行次数通常是每秒60次,但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的iframe里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。

回调函数会被传入DOMHighResTimeStamp参数,DOMHighResTimeStamp指示当前被 requestAnimationFrame() 排序的回调函数被触发的时间。在同一个帧中的多个回调函数,它们每一个都会接受到一个相同的时间戳,即使在计算上一个回调函数的工作负载期间已经消耗了一些时间。该时间戳是一个十进制数,单位毫秒,最小精度为1ms(1000μs)。

DOMHighResTimeStamp 是一个double类型,用于存储时间值。该值可以是离散的时间点或两个离散时间点之间的时间差。T单位为毫秒 ms (milliseconds) ,应准确至5微秒 µs (microseconds)。但是,如果浏览器无法提供准确到5微秒的时间值(例如,由于硬件或软件的限制), 浏览器可以以毫秒为单位的精确到毫秒的时间表示该值。

requestAnimationFrame VS 定时器

我们分别用两个API实现同一个动画效果,看看他们表现怎么样

setTimeout

const gap = 16.6
let num = 0
let timer

function run() {
    timer = setTimeout(() => {
        num++
        document.body.style.background = num % 2 ? "red" : "blue"
        console.log(num)
        num < 30 && run()
    }, gap)
}
run()

image

requestAnimationFrame

let num = 0

function run() {
    window.requestAnimationFrame(() => {
        num++
        document.body.style.background = num % 2 ? "red" : "blue"
        console.log(num)
        num < 30 && run()
    })
}
run()

image

结论

setTimeout存在不绘制的情况,视觉上看就是掉帧的情形,而requestAnimationFrame 会规律绘制界面,原因在于一般情况下, 浏览器的帧率跟屏幕帧率一致, 基本都是60, 也就是16ms左右会刷新一次.定时器只是计时完毕把对应任务添加到处理队列,依然要等执行栈空闲才会去提取队列执行,这些造成的结果就是

  • 如果定时器时间小于帧率,在下一次渲染之前已经执行过多次定时器回调
  • 定时器回调会被主线程其他任务阻塞执行,每次时间误差会影响后续执行时间

这两个影响造成用定时器做动画会经常传画面掉帧或者速率不正常的情形.

requestAnimationFrame是在浏览器在下次重绘之前调用,即它的调用时机由系统自己把握,可以保证在闲置时间内执行,还有一个优点是当页面切换其他应用或者隐藏之后会暂停调用以提升性能和电池寿命。

还有另一个定时器setInterval也有一些问题

  • 累计效应,如果执行栈阻塞时间足够长以至于队列中已经存在多个setInterval的对应任务的情况,执行时间会远低于开发者期望的结果;
  • 部分浏览器(如Safari等)滚动过程中执行JS,容易造成卡顿和未知错误;
  • 浏览器最小化显示时setInterval会继续执行,但是对应任务会等到浏览器还原再一瞬间全部执行;

requestIdleCallback(实验中)

此功能某些浏览器尚在开发中,请参考浏览器兼容性表格以得到在不同浏览器中适合使用的前缀。由于该功能对应的标准文档可能被重新修订,所以在未来版本的浏览器中该功能的语法和行为可能随之改变。
/**
 * 将在浏览器的闲置时段内调用的函数排队
 * @param callback
 * @param options 
 */
window.requestIdleCallback(callback[, options])
描述
callback 一个在事件循环空闲时即将被调用的函数的引用。函数会接收到一个名为 IdleDeadline的参数,这个参数可以获取当前空闲时间以及回调是否在超时时间前已经执行的状态。
options(可选) timeout:如果指定了timeout并具有一个正值,并且尚未通过超时毫秒数调用回调,那么回调会在下一次空闲时期被强制执行,尽管这样很可能会对性能造成负面影响。

React实现了一套类似的时间分片渲染机制

摘要

web开发使用这个API能够协同调度后台任务,这样它们就不会给共享相同循环阶段的其他高优先级任务,例如输入处理,动画和帧合成带来延迟.用户代理能够基于它对当前调度任务的了解,垂直同步(vsync?)延迟,用户交互等等更好确定什么时候适合运行后台任务,而不会在动画和输入响应引起用户可察觉的延迟或闪烁(jank?).因此,当浏览器将处于闲置的时候使用这个API能够更适当的调度后台任务.

闲置周期

在给定帧内完成输入处理,渲染和合成之后.用户代理的主线程通常处于闲置直到下一帧开始,另一个等待任务会有资格参与运行.或者接收到用户输入,这规范提供一种方式在其他闲置时间经由requestIdleCallback API去调度执行回调

通过requestIdleCallback API回调的方式可以在用户代理定义的闲置周期执行,它会给予一个符合的截止日期对应当前闲置周期的结束时间.这决定用户代理怎么定义闲置周期的构成,但是预期它们会发生在静止期间,浏览器处于闲置周期.

一个闲置周期的例子是在动态动画处于将给定帧提交到屏幕和开始处理下一帧之间的时间里

Figure 1 Example of an inter-frame idle period

动画开发 --- css和javascript方式_第1张图片

在动态动画和屏幕更新期间,这样的闲置时间会频繁出现,但通常都很短(即,小于16ms的设备与60Hz的垂直同步周期)

注意

web开发应该仔细考虑在闲置回调期间内所有由操作完成的工作.一些操作例如promise解析或触发页面布局,可能导致随后的任务会在闲置周期完成后才调度,在这种情况下,应用程序应该在截止日期到期之前完成这些额外的工作,从而允许在下一个框架截止日期之前执行这些操作。

Figure 2 Example of an idle period when there are no pending frame updates

动画开发 --- css和javascript方式_第2张图片

另一个例子是用户代理在闲置周期内但没有屏幕更新发生,在这种情况下,用户代理可能没有可以绑定闲置期结束的后续任务,以避免在不可预测的任务中造成用户可察觉的延迟例如用户输入处理,这些闲置时间的长度应该被限制到50ms的最大值,一旦一个闲置周期已经完成后用户代理可以立马调度另一个闲置周期(如果他保持闲置),如图2所示,以使后台工作在更长闲置时间内继续发生。

在闲置周期内用户代理会以先进先出的顺序运行回调直到闲置周期结束或者没有更多的闲置回调有资格运行.就其本身而论,用户代理并不需要在一个闲置周期内运行完当前发布的所有闲置回调.任何剩余的闲置任务都可以在下一个闲置期间运行。

注意

为开发者们提供最佳性能,鼓励消除不必要的回调(例如requestAnimationFrame, setTimeout等等),他们并不执行有意义的工作,不要保持这些回调触发并等待事件的响应.取而代之的是根据需要去调度它们以便在它们可用时对事件作出反应,这种模式提高了整体效率并且允许用户代理去调度长闲置周期(直到50ms)可用于高效地执行大块的后台工作。

因为还没标准化,所以更新细节可以查看 这里

你可能感兴趣的:(javascript,前端,css3,html5)