需求:制作酷炫的动画效果
在做一些大屏可视化项目中,经常会看到一些很酷炫的,具有科技风的动画
当然如果可以通过CSS或者JS来实现当然是最佳的选择,但奈何这些高级的动画,纯前端实现起来太困难了,所以介绍一些,另一种实现方式,那就是通过序列帧的方式,只需要加载后期处理好的序列帧
实现思路:通过请求动画帧requestAnimationFrame
在前端我们可以通过setInterval和requestAnimationFrame这些方式制作动画效果
setInterval和requestAnimationFrame区别
执行时机:
setInterval
在指定的时间间隔内重复执行代码。它不考虑浏览器的刷新率,因此在一些高刷新率的屏幕上可能执行得比较频繁。requestAnimationFrame
会在浏览器每次绘制新的帧时执行回调函数。它会在浏览器的刷新周期内执行,通常为每秒 60 次,但浏览器可以根据屏幕的刷新率进行优化。 性能:
requestAnimationFrame
更加高效。由于它与浏览器的渲染周期同步,可以避免不必要的重绘,减少了浏览器的工作负担。setInterval
不会被浏览器优化,可能会在不同的环境下产生不一致的效果,尤其在性能较差的设备上。 暂停和恢复:
使用clearInterval
可以很容易地暂停和取消 setInterval
。对于 requestAnimationFrame
,要实现类似的暂停和取消,需要维护一个标志来控制循环的执行。 函数参数:
setInterval
接受两个参数,第一个是要执行的函数,第二个是时间间隔(以毫秒为单位)。requestAnimationFrame
接受一个回调函数作为参数,回调函数会在浏览器准备好渲染下一帧时执行。 适用场景:
setInterval
适用于需要在固定时间间隔内执行的任务,例如定时器、轮播图等。requestAnimationFrame
更适用于动画和需要与浏览器的渲染同步的任务,避免掉帧和提高性能。 总的来说,如果涉及到动画或者需要与浏览器渲染同步的任务,推荐使用 requestAnimationFrame
。如果只是简单的定时任务,可以考虑使用 setInterval
。在一些场景下,两者也可以结合使用,根据实际需求选择合适的方法
requestAnimationFrame简直就是为动画而生,用three.js的开发者或者在前端渲染大量数据的时候对这个函数应该多少有点熟悉吧
上面也说到了requestAnimationFrame是每秒执行60次,那我们如何改变动画的执行速度呢,比如我们每秒执行30次呢?带着这个疑问接着往下看,算了还是直接给出代码吧
import { onMounted, ref } from "vue";import { onBeforeRouteLeave } from "vue-router";export default function (root: string, max: number, fps = 24) { const currentFrame = ref(root + 0 + ".png"); let index = 0; let animation: number | null = null; const frameInterval = 1000 / fps; let lastTimestamp = 0; const render = (timestamp: number) => { if (!lastTimestamp) lastTimestamp = timestamp; const elapsed = timestamp - lastTimestamp; if (elapsed >= frameInterval) { index > max ? (index = 0) : index; currentFrame.value = root + index + ".png"; index++; lastTimestamp = timestamp; } animation = requestAnimationFrame(render); }; onMounted(() => { animation = requestAnimationFrame(render); }); onBeforeRouteLeave(() => { if (animation !== null) { cancelAnimationFrame(animation); } }); return { currentFrame };}
这里是一VUE项目为例子,将其封装成一个hooks,在生命周期onMounted的时候开始,在onBeforeRouteLeave的时候卸载该动画,返回一个周期性改变的ref变量
接收三个参数,第一个root,也就是动画帧所在的根目录,第二个max,也就是动画帧的最大值,不能超出,否则取不到值,所里面index被限制在了0到max之间,最后一个就是我们的速度了
注意序列帧需要按如下命名,方便index变量拼上根目录组成的路径找到对于的序列帧
使用方法如下,很简单,先引入钩子,然后将反出来的currentFrame绑到<img />元素即可,至于为什么不用css的background-image,接着往下看
// 先引入import animate from '@/hooks/frames'const rootPath = "framesImages/parkOverview/大标题/";const {currentFrame} = animate(rootPath,27,28)// 绑定上去<img :src="currentFrame" class="title-bg" alt="" srcset="" />
使用 background-image
:
优点:
样式控制: 你可以使用 CSS 控制图像的大小、位置、重复等。适用于装饰性背景: 如果序列帧是背景效果,而不是文档的一部分,使用background-image
可能更适合。 缺点:
不易控制动画: 对于复杂的动画效果,可能需要使用 CSS 或 JavaScript 动画,这可能变得复杂。性能考虑: 对于大量的序列帧,可能会导致性能问题。.sequence { background-image: url('frame1.png'); width: 100px; height: 100px; /* ...其他样式... */ }
使用 <img>
元素:
<img src="frame1.png" alt="Frame 1" class="sequence" />
优点:
易于控制: 你可以使用 JavaScript 控制<img>
元素的显示、隐藏、大小等属性,也可以使用 CSS 进行样式控制。性能优化: 浏览器会对 <img>
元素进行更好的优化,特别是在加载大量图像时。 缺点:
样式限制: 在处理复杂的样式时,可能不如使用background-image
灵活。不适合背景: 如果你希望序列帧作为背景,使用 <img>
元素可能不是最佳选择。 性能考虑:
如果你有大量的序列帧图像,使用<img>
元素通常更好,因为浏览器可以更好地优化图像加载和缓存。对于小型的、用于装饰性背景的序列帧,使用 background-image
可能更合适,因为它可以更容易地集成到样式中。 总体来说,最好根据具体情况来选择。如果你的序列帧是装饰性背景,并且对于样式灵活性要求较高,使用 background-image
;如果你需要更好的性能和更多的控制权,使用 <img>
元素。
这就是我们用<img>
元素,来渲染动画帧的原因