持续创作,加速成长!这是我参与「编程 · 10 月更文挑战」的第N天,点击查看活动详情
前言
今天笔者在逛 CodePen 的时候,突然发现了一个用 JS 实现的动画效果,仔细一看又发现了一个以前没有见过的东西 ———— Web Animations API。
翻了翻 MDN 文档才发现这部分功能已经出来5年多了!这怎么能忍,于是赶紧 fork 学习了一波。
Web Animations API
MDN 定义是:允许同步和定时更改网页的呈现,即 DOM 元素的动画。它通过组合两个模型来实现:时序模型和动画模型。
目前该部分功能包含以下几个 类(class) 和一些 元素的扩展方法/属性。
类 Classes:
- Animation:提供播放控制、动画节点或源的时间轴。控制方法有四个:finish 终止、pause 暂停、play 开始/恢复动画执行、reverse 反转动画,再加上一个清除动画的方法 cancel;另外还有两个监听事件用来配置对应的回调:oncancel 动画被取消、onfinish 动画执行结束,这两个属性直接读取时是获取对应的事件回调函数,进行赋值操作时才是设置对应的事件回调,并且都支持 Promise 方式使用
- KeyframeEffect:用来创建动画的关键帧,然后提供给 Animation 的动画使用
- AnimationTimeLine:动画执行的时间轴,提供一个 currentTime 属性,但本身并不能使用
- DocumentTimeLine:用来定义一个动画的执行时间线,可以在实例化多个 Animation 时共用同一个 timeline 实例来控制一组动画
方法 Functions:
- document.getAnimations:返回文档流中所有的 Animation 实例数组
- element.getAnimations:返回该动画对应的 Animation 实例数组
- element.animate:为一个元素创建一个 Animation 实例 的便捷方法,每次调用都会返回一个新的 Animation 实例。
属性 Properties:
- document.timeline:一个只读属性,用来获取当前文档的时间轴,在网页加载时创建
当然,一般来说我们 常常使用的也只有 Animation 和 KeyframeEffect,以及 element.animate。
1. Class KeyframeEffect
因为在创建一个动画之前,肯定要先定义一个动画的关键帧,所以我们从 KeyframeEffect 关键帧定义 开始。
构造函数参数和说明:
该类可以通过下面三种方式进行实例化:
new KeyframeEffect(target, keyframes, options)
new KeyframeEffect(target, keyframes)
new KeyframeEffect(sourceKeyFrames)
其中:
-
target 为需要执行动画的元素,可以为 null
-
keyframes 则是一个动画帧定义的 对象数组,也可以是 null
-
options 可以是一个 number,也可以是一个配置对象
- 是数字时表示动画的 执行总时间
- 是对象时可以配置 delay 延迟、duration 动画时间、easing 动画运动曲线 等
-
sourceKeyFrames 是一个通过 KeyframeEffect 实例化后的动画定义,这么使用会 复制 传入的动画定义来创建一个新的动画帧定义实例
一般情况下,都是通过 第一种方式并且指定 target 为 null 来定义动画,可以增加动画的复用性。
使用:
假设我们现在要定义一个元素向下移动的动画(transform)
const downKeyframes = new KeyframeEffect(
null,
[
{ transform: 'translateY(0%)' },
{ transform: 'translateY(100%)' }
],
{ duration: 3000 }
);
这个实例的代表的动画效果就是:在 3s 内元素会通过 transformY 向下偏移整个元素的高度。
2. Class Animation
上面介绍过,这个类就是创建一个用来控制动画以及这种动画状态监听/读取的实例对象。
构造函数参数和说明:
该类的实例化方式只有一种:
const animation = new Animation(effect, timeline);
其中:
- effect:就是上面通过 KeyframeEffect 实例化的动画帧定义
- timeline:指定与动画相关联的时间轴,当前阶段还没有相关功能实现,默认是 document.timeline;使用时可以省略
使用:
现在我们就使用上面定义的那个下移动画(当然,此时上面的动画帧定义实例需要绑定动画的目标元素):
const el = document.getElementById("active")
downKeyframes.target = el;
const downAnimation = new Animation(downKeyframes, document.timeline);
效果如下:
然后,我们还可以%20通过调用该实例的相关方法来控制这个动画的执行:
const%20btn1%20=%20document.getElementById("button1")
const%20btn2%20=%20document.getElementById("button2")
btn1.addEventListener('click',%20()%20=>%20{
%20%20if%20(downAnimation.playState%20===%20'running')%20{
%20%20%20%20downAnimation.pause()
%20%20}%20else%20{
%20%20%20%20downAnimation.play()
%20%20}
})
btn2.addEventListener('click',%20()%20=>%20{
%20%20downAnimation.reverse()
})
这里介绍一下%20playState%20属性:用来获取该动画的%20执行状态,是一个%20枚举值。具体值有以下几个:
- idle:此时动画的事件还无法解析,并且队列里也没有处于等待执行的动画任务
- pending:还处于等待过程中,需要等待其他任务执行完毕
- running:正处于动画过程中
- paused:动画被暂停
- finished:动画已经执行结束
3.%20element.animate()
当然,上面这种方式对实际使用来说还是有点繁琐,所以又有一种比较快捷的方式来创建一个%20Animation%20实例,也就是上面提到的%20element.animate()。并且,该方式创建的动画%20将直接作用于元素并开始执行动画过程。
用法和参数说明:
在使用时,和一般的函数使用一样:
const%20animation%20=%20element.animate(keyframes,%20options);
其中的参数:
- keyframes:与%20KeyframeEffect%20中的%20keyframes%20参数类似,都是用来定义动画执行过程中的关键帧,只是这里是一个%20对象%20形式
- options:与%20KeyframeEffect%20中的%20options%20参数一致,可以是数字或者对象;但是这里%20多了一个%20id%20配置,用来作为该动画的唯一标识
当然,MDN%20中的文档还表示未来可能会增加composite、spacing%20等多个配置项,不过目前还不是所有浏览器都支持
使用:
此时假设我们还要实现上面的那个下移效果的话,就可以这么写:
const%20el%20=%20document.getElementById("active")
const%20animation%20=%20el.animate(
%20%20{%20transform:%20'translateY(100%)'%20},
%20%203000
)
页面加载完成时该动画就会直接执行
假设我们要加上相关的一些控制的话,也可以和控制%20Animation%20实例一样:
const%20btn1%20=%20document.getElementById("button1")
const%20btn2%20=%20document.getElementById("button2")
btn1.addEventListener('click',%20()%20=>%20{
%20%20if%20(animation.playState%20===%20'running')%20{
%20%20%20%20animation.pause()
%20%20}%20else%20{
%20%20%20%20animation.play()
%20%20}
})
btn2.addEventListener('click',%20()%20=>%20{
%20%20animation.reverse()
})
效果如下:
借助%20animate()%20实现鼠标跟随
首先上效果:
1.%20布局
整个界面包含%20一个外层的限制元素、一个尺寸大于外层元素的内层元素、以及一系列图片显示的元素
<div%20class="animation-box"%20id="box">
%20%20<div%20id="gallery">
%20%20%20%20<div%20class="tile">
%20%20%20%20%20%20<img
%20%20%20%20%20%20%20%20%20%20src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/515258c213064463b1fac843363ece92~tplv-k3u1fbpfcp-no-mark:480:400:0:0.awebp?"
%20%20%20%20%20%20%20%20/>
%20%20%20%20</div>
%20%20%20%20<div%20class="tile">
%20%20%20%20%20%20<img
%20%20%20%20%20%20%20%20%20%20src="https://img01.yzcdn.cn/upload_files/2022/03/15/FpS314YGk0tJ6CKFGF1CO49ql1Wn.jpg!280x280.jpg"
/>
</div>
...
</div>
</div>
2. 样式
为了增加一个交互效果,给图片添加了一个 带颜色的遮罩层,并设置透明,在鼠标 Hover 时在显示图片并稍微放大。
body {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
box-sizing: border-box;
}
.animation-box {
background-color: rgb(10, 10, 10);
height: 100%;
margin: 0;
overflow: hidden;
position: relative;
}
#gallery {
height: 120vmax;
width: 120vmax;
position: absolute;
}
.tile {
border-radius: 1vmax;
position: absolute;
transition: transform 800ms ease;
}
.tile:hover {
transform: scale(1.1);
}
.tile:hover > img {
opacity: 1;
transform: scale(1.01);
}
.tile > img {
height: 100%;
width: 100%;
object-fit: cover;
border-radius: inherit;
opacity: 0;
transition: opacity 800ms ease, transform 800ms ease;
}
.tile:nth-child(1) {
background-color: rgb(255, 238, 88);
height: 14%;
width: 20%;
left: 5%;
top: 5%;
}
.tile:nth-child(2) {
background-color: rgb(66, 165, 245);
height: 24%;
width: 14%;
left: 42%;
top: 12%;
}
// ...
Animate 动画
这里需要实现的其实就是 计算出鼠标当前在窗口的位置,然后按比例换算成内部区域的偏移量,最后通过 animate 方法定义一个动画将内层元素移动到相应的位置
const box = document.getElementById('box')
const gallery = document.getElementById("gallery");
function animation(e) {
const mouseX = e.clientX,
mouseY = e.clientY;
const xDecimal = mouseX / box.clientWidth,
yDecimal = mouseY / box.clientHeight;
const maxX = gallery.offsetWidth - box.clientWidth,
maxY = gallery.offsetHeight - box.clientHeight;
const panX = maxX * xDecimal * -1,
panY = maxY * yDecimal * -1;
const animation = gallery.animate(
{
transform: `translate(${panX}px, ${panY}px)`
},
{
duration: 4000,
fill: "forwards",
easing: "ease"
}
);
animation.onfinish = () => animation.cancel()
}
window.addEventListener("mousemove", animation);
值得注意的是,通过 element.animate 创建的 Animation 实例每次都是新的,并且 不会自动清除,所以建议通过设置 onfinished 事件回调来清除掉原来的动画实例。
最后
本文也只是大致说明了一下这几个 API 的功能和使用方式,但是具体还有哪些坑或者彩蛋还需要大家去实际体验一下才知道。
总的来说,这个功能的出现对于我们前端来说 可以更加方便且准确的实现和控制页面的动画内容,但是部分属性与 CSS 的动画配置还是有些区别,大家需要多多注意呀
如果本文对您有帮助,还请三连哟~~
往期精彩
Bpmn.js 进阶指南
Vue 2 源码阅读理解
一行指令实现大屏元素分辨率适配(Vue)
基于 Vue 2 与 高德地图 2.0 的“线面编辑器”
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!