15158846557 在线咨询 在线咨询
15158846557 在线咨询
所在位置: 首页 > 营销资讯 > 网站运营 > 时间之美 —— SpriteJS 的原创时间轴设计

时间之美 —— SpriteJS 的原创时间轴设计

时间:2023-07-21 20:21:01 | 来源:网站运营

时间:2023-07-21 20:21:01 来源:网站运营

时间之美 —— SpriteJS 的原创时间轴设计:

SpriteJS提供与标准的Web Animation API相一致的动画API,使得熟悉css3 animation和Web Animation API的同学能快速上手实现复杂的动画。

SpriteJS动画

SpriteJS的动画是基于Timeline的,改变Layer的Timeline能够批量改变精灵的动画行为,包括改变速度、方向和暂停动画,或者跳转到特定时间。

批量改变精灵元素的动画

手动控制动画的时间轴

SpriteJS动画这种灵活“控制时间”的能力是由一个底层库赋予的,这个底层库是sprite-timeline

sprite-timeline

sprite-timeline是一个控制时间的底层库,在之前我写过一篇文章简单介绍过它。今天我们再详细挖掘一下这个库的设计思路和用法。

sprite-timeline可以单独使用

sprite-timeline可以单独使用

sprite-timeline可以单独使用

重要的事情说三遍。

说sprite-timeline前,我们先来看一下不使用timeline的原生JS动画怎么实现:

例子

let startTime = Date.now(), T = 2000requestAnimationFrame(function update(){ let p = (Date.now() - startTime) / T ball.style.transform = `rotate(${360 * p}deg)` requestAnimationFrame(update)})上面的代码我们实现一个小球围绕圆心做匀速圆周运动运动的动画,这是一个基于时间的动画,我们根据当前时间改变元素的transform样式,通过requestAnimationFram来更新,这样就实现了。

在这个例子中,如果我们要把小球的速度提高一倍,那么我们要做的也很简单,就是把周期修改为原先的一半,即把T=2000改成T=1000

例子

let startTime = Date.now(), T = 1000requestAnimationFrame(function update(){ let p = (Date.now() - startTime) / T ball.style.transform = `rotate(${360 * p}deg)` requestAnimationFrame(update)})小球运动方向是顺时针旋转,如果我们要让小球逆时针旋转,我们可以改变rotate的符号:

例子

let startTime = Date.now(), T = 1000requestAnimationFrame(function update(){ let p = (Date.now() - startTime) / T ball.style.transform = `rotate(${-360 * p}deg)` requestAnimationFrame(update)})如果页面上还有其他动画对象,我们同样可以添加进来,比如我们在小球旁边添加一个颜色从红色渐变到蓝色的方块:

例子

const startTime = Date.now()requestAnimationFrame(function update(){ let p = (Date.now() - startTime) / 2000 ball.style.transform = `rotate(${-360 * p}deg)` requestAnimationFrame(update)})requestAnimationFrame(function update(){ let p = (Date.now() - startTime) / 5000 p = Math.min(p, 1.0) let red = Math.round(255 * (1 - p)), blue = Math.round(255 * p) block.style.backgroundColor = `rgb(${red},0,${blue})` if(p < 1.0) { requestAnimationFrame(update) }})现在我们要改变动画的速度或者方向就稍微麻烦一点点了,因为我们有两个独立的动画要修改参数。但是如果有类似于将所有动画速度提升到原来的2倍这样的需求时,我们有一个最简单的方法,那就是修改时间流动的速度

有一些动画库可以实现上面的要求,改变动画的时间轴,而sprite-timeline应该是其中最简单的库之一,而且sprite-timeline只是纯粹地控制时间,不关心动画的其他部分,也因此,sprite-timeline的使用场景不仅限于动画。

让我们先用sprite-timeline改写上面的例子:

例子

const timeline = new Timeline()requestAnimationFrame(function update(){ let p = timeline.currentTime / 2000 ball.style.transform = `rotate(${-360 * p}deg)` requestAnimationFrame(update)})requestAnimationFrame(function update(){ let p = timeline.currentTime / 5000 p = Math.min(p, 1.0) let red = Math.round(255 * (1 - p)), blue = Math.round(255 * p) block.style.backgroundColor = `rgb(${red},0,${blue})` if(p < 1.0) { requestAnimationFrame(update) }})改写后的代码跟之前的代码相比,几乎没有什么不同,唯一的不同之处是原先的Data.now() - startTime被更加简单的timeline.currentTime所取代了。

创建 Timeline 对象

Timeline对象的构造器有两个参数,分别是playbackRate和originTime,它们的默认值分别为1.0和0。playbackRate决定时间流逝的方向,正数是正向,负数是负向,0表示时间停止。originTime定义了时间的原点位置,比如originTime = 10000,表示时间的原点在当前时间之后10秒钟,那么该Timeline创建时的currentTime就是-originTime,即-10秒。

比如,我们创建一个Timeline,originTime是2000,playbackRate是0.5,那么:

例子

const timeline = new Timeline({playbackRate: 0.5, originTime: 2000})console.log(Math.round(timeline.currentTime / 100) / 10)setInterval(() => { console.log(Math.round(timeline.currentTime / 100) / 10)}, 1000)我们会看到实际上每隔1秒钟,timeline的currentTime变化0.5秒,而且时间从-2秒开始变化。

我们稍微改一下上面的那个动画的例子:

例子

const timeline = new Timeline({playbackRate: 0.5, originTime: 1500})requestAnimationFrame(function update(){ let p = timeline.currentTime / 2000 ball.style.transform = `rotate(${-360 * p}deg)` requestAnimationFrame(update)})requestAnimationFrame(function update(){ let p = timeline.currentTime / 5000 p = Math.min(p, 1.0) let red = Math.round(255 * (1 - p)), blue = Math.round(255 * p) if(p > 0) { block.style.backgroundColor = `rgb(${red},0,${blue})` } if(p < 1.0) { requestAnimationFrame(update) }})我们会看到,经过这样的修改之后,两个元素动画的速度都减半,而且方块在3秒钟(真实世界的3秒,timeline世界的1.5秒)之后才开始改变颜色。

timeline的奇妙之处还在于我们可以动态改变它的currentTime和playbackRate,从而立即改变动画的行为,比如我们可以不改变p,仅通过改变currentTime来控制动画循环播放:

例子

const timeline = new Timeline({playbackRate: 2.0, originTime: 1500})requestAnimationFrame(function update(){ let p = timeline.currentTime / 2000 ball.style.transform = `rotate(${-360 * p}deg)` requestAnimationFrame(update)})requestAnimationFrame(function update(){ let p = timeline.currentTime / 6000 p = Math.min(p, 1.0) let red = Math.round(255 * (1 - p)), blue = Math.round(255 * p) if(p > 0) { block.style.backgroundColor = `rgb(${red},0,${blue})` } if(p >= 1.0) { timeline.currentTime = 0 } requestAnimationFrame(update)})我们可以改变playbackRate,动画速度(和方向)会立即改变,比如下面这个例子,我们把鼠标移入右边的方块时,动画的速度和方向都会立即改变:

例子

const timeline = new Timeline({playbackRate: 1.0, originTime: 1500})requestAnimationFrame(function update(){ let p = timeline.currentTime / 2000 ball.style.transform = `rotate(${-360 * p}deg)` requestAnimationFrame(update)})requestAnimationFrame(function update(){ let p = timeline.currentTime / 6000 p = Math.min(p, 1.0) let red = Math.round(255 * (1 - p)), blue = Math.round(255 * p) if(p > 0) { block.style.backgroundColor = `rgb(${red},0,${blue})` } if(p >= 1.0) { timeline.currentTime = 0 } else if(p < 0) { timeline.currentTime = 6000 } requestAnimationFrame(update)})block.addEventListener('mouseenter', (evt) => { timeline.playbackRate = -2.0})block.addEventListener('mouseleave', (evt) => { timeline.playbackRate = 1.0})

timeline timer

除了控制动画,我们可以直接给timeline设置timer,timeline自身支持setTimeout和setInterval,例如:

例子

const timeline = new Timeline({playbackRate: 2.0})setInterval(() => { globalTime.innerHTML = parseInt(globalTime.innerHTML, 10) + 1}, 1000)timeline.setInterval(() => { timelineTime.innerHTML = parseInt(timelineTime.innerHTML, 10) + 1}, 1000)这个例子timeline的setInterval实际触发时间是500毫秒(1000毫秒/2.0)

与global的timer不同的是,timeline的Timer是有方向的,比如:

例子

const t1 = new Timeline({playbackRate: 1.0}), t2 = new Timeline({playbackRate: -1.0})t1.setTimeout(() => { console.log(t1.currentTime)}, 1000)t2.setTimeout(() => { console.log(t2.currentTime)}, -1000)t1和t2的timer几乎同时触发,t1时间方向是正向,t2时间方向是负向。

如果我们把t1和t2的方向交换:

例子

const t1 = new Timeline({playbackRate: -1.0}), t2 = new Timeline({playbackRate: 1.0})t1.setTimeout(() => { console.log(t1.currentTime)}, 1000)t2.setTimeout(() => { console.log(t2.currentTime)}, -1000)两个timer都立即触发,因为它们的当前时间都在timer的时间之后

有时候,我们希望在设置定时器的时候不触发在currentTime之前的那些定时器,我们可以给timer添加一个heading:false的配置:

例子

const t1 = new Timeline({playbackRate: -1.0}), t2 = new Timeline({playbackRate: 1.0})t1.setTimeout(() => { console.log(t1.currentTime)}, {delay: 1000, heading: false})t2.setTimeout(() => { console.log(t2.currentTime)}, -1000)上面的例子t1的timer不会触发,t2的timer会触发,因为t1设置了heading:false。

我们可以动态改变t1的playbackRate让时间方向回头,这样还能够触发t1:

例子

const t1 = new Timeline({playbackRate: -1.0}), t2 = new Timeline({playbackRate: 1.0})t1.setTimeout(() => { console.log(t1.currentTime)}, {delay: 1000, heading: false})t2.setTimeout(() => { console.log(t2.currentTime) t1.playbackRate = 1.0}, 1000)

timeline fork

timeline 还有一个神奇的功能是可以fork

例子

const timeline = new Timeline(), forkedTimeline1 = timeline.fork(), forkedTimeline2 = timeline.fork()const startTime = Date.now()const globalTime = document.getElementById('globalTime'), localTime = document.getElementById('localTime'), forkedTime1 = document.getElementById('forkedTime1'), forkedTime2 = document.getElementById('forkedTime2')forkedTimeline1.playbackRate = 2.0forkedTimeline2.playbackRate = -2.0setInterval(() => { globalTime.innerHTML = `${Math.round((Date.now() - startTime) / 1000)}s` localTime.innerHTML = `${Math.round(timeline.currentTime / 1000)}s` forkedTime1.innerHTML = `${Math.round(forkedTimeline1.currentTime / 1000)}s` forkedTime2.innerHTML = `${Math.round(forkedTimeline2.currentTime / 1000)}s`}, 100)上面的例子里,forkedTimeline1和forkedTimeline2都是从localTimeline分支出来的,所以我们改变localTime,就能同时改变forkedTimeline1和forkedTimeline2,比如现在forkedTimeline1的时间线是正向,forkedTimeline2的时间线是负向,我们只要把localTimeline的playbackRate设置为-1,那么forkedTimeline1时间线变成负向,forkedTimeline2的时间线则变成正向。

再看一个例子:

例子

let timeline = new Timeline()function count(el, timeline, p = Infinity) { timeline.setInterval(() => { el.innerHTML = Math.round(timeline.currentTime / 1000) % p }, 1000)}count(ball0, timeline)count(ball1, timeline.fork({playbackRate: 10}), 10)count(ball2, timeline.fork({playbackRate: 100}), 10)

timeline与sprite animation

因为SpriteJS的Layer有自己的timeline,而Layer上的sprite在运行动画的时候,会fork Layer的timeline,所以这样就实现了layer动画的统一控制。

具体再看一下这个例子,因为代码较长,就不全贴出来了。

关键代码

const [speedupBtn, slowdownBtn, pauseBtn, playBtn] = ['Speed up', 'Slow down', 'Pause', 'Play'].map((type, i) => { const button = new Button(type) button.attr({ anchor: [0.5, 0.5], pos: [1300, 400 + 150 * i], size: [170, 50], font: '36px Arial', lineHeight: 50, textAlign: 'center', color: '#00e15e', border: [3, '#00e15e'], borderRadius: 25, padding: 30, }) bglayer.appendChild(button) return button }) speedupBtn.on('click', (evt) => { fglayer.timeline.playbackRate += 0.2 }) slowdownBtn.on('click', (evt) => { fglayer.timeline.playbackRate -= 0.2 }) pauseBtn.on('click', (evt) => { fglayer.timeline.playbackRate = 0 }) playBtn.on('click', (evt) => { fglayer.timeline.playbackRate = 1 })以上就是关于sprite-timeline的关键内容,其他还有一些有趣的功能,在这里没有列出,比如timeline的“熵”(entropy),以及entropy相关的用法,有兴趣的同学可以关注GitHub repo自行研究。

关键词:设计

74
73
25
news

版权所有© 亿企邦 1997-2025 保留一切法律许可权利。

为了最佳展示效果,本站不支持IE9及以下版本的浏览器,建议您使用谷歌Chrome浏览器。 点击下载Chrome浏览器
关闭