从零开发一款动画自动生成器
背景:可视化编辑器中我们用组件拖拽出页面,但是组件中有时候会有些通用的动画特效,如何添加呢?举个例子给一个小手图片增加动效一般新手指引中会经常看到
目标:如何给可视化配置平台中的组件配置灵活的动画或帧动画或连续动画?就像PPT中那样的添加动画效果一样呢?
演进目标:如何实现一个可灵活配置的动画插件库,通过配置相关信息直接生成目标动画效果
21年12月梳理完的,今天突然发现,这个竟然忘记发出来了。。。
一、动画知识点须知
我们先来复习一下css3动画属性多动画流水线设计所涉及的知识点
CSS3动画属性:
developer.mozilla.org/zh-CN/docs/…
CSS animation 属性是 animation-name
,animation-duration
, animation-timing-function
,animation-delay
,animation-iteration-count
,animation-direction
,animation-fill-mode
和 animation-play-state
属性的一个简写属性形式。
animation:[[ animation-name ] || [ animation-duration ] || [ animation-timing-function ] || [ animation-delay ] || [ animation-iteration-count ] || [ animation-direction ]] [ , [ animation-name ] || [ animation-duration ] || [ animation-timing-function ] || [ animation-delay ] || [ animation-iteration-count ] || [ animation-direction ]]
animation属性值
- [ animation-name ]:检索或设置对象所应用的动画名称
- [ animation-duration ]:检索或设置对象动画的持续时间
- [ animation-timing-function ]:检索或设置对象动画的过渡类型
- [ animation-delay ]:检索或设置对象动画延迟的时间
- [ animation-iteration-count ]:检索或设置对象动画的循环次数
- [ animation-direction ]:检索或设置对象动画在循环中是否反向运动
- [ animation-play-state ]:检索或设置对象动画的状态。w3c正考虑是否将该属性移除,因为动画的状态可以通过其它的方式实现,比如重设样式
- [animation-fill-mode]: 设置CSS动画在执行之前和之后如何将样式应用于其目标 developer.mozilla.org/zh-CN/docs/…
贝塞尔曲线cubic-bezier()运动动画
再学习下贝塞尔曲线cubic-bezier(),应用于animation-timing-function中
- cubic-bezier() 函数定义了一个贝塞尔曲线(Cubic Bezier)cubic-bezier() 可用于 animation-timing-function 和 transition-timing-function 属性。cubic-bezier(x1,y1,x2,y2)
- 贝塞尔曲线曲线由四个点 P0,P1,P2 和 P3 定义。P0 和 P3 是曲线的起点和终点。P0是(0,0)并且表示初始时间和初始状态,P3是(1,1)并且表示最终时间和最终状态。
- P0:默认值 (0, 0);P1:动态取值 (x1, y1);P2:动态取值 (x2, y2);P3:默认值 (1, 1)
- x1,y1,x2,y2 必需。数字值,x1 和 x2 需要是 0 到 1 的数字。
**曲线值调整可用这个在线网站调整完参数复制过来:**cubic-bezier.com/#.19,.61,.7…
steps实现动画播放
再学习下steps 实现动画播放,应用于animation-timing-function中
steps 指定了一个阶梯函数,包含两个参数:
-
第一个参数指定了函数中的间隔数量(必须是正整数);
-
第二个参数可选,指定在每个间隔的起点或是终点发生阶跃变化,接受 start 和 end 两个值,默认为 end。
-
通过 W3C 中的这张图片来理解 steps 的工作机制:
step-start 与 step-end
除了 steps 函数,animation-timing-function 还有两个与逐帧动画相关的属性值 step-start 与 step-end:
- step-start 等同于 steps(1,start):动画执行时以开始端点为开始;
- step-end 等同于 steps(1,end):动画执行时以结尾端点为开始。
二、animate.css 动画库
官网地址:animate.style/
背景:业务中有很多动画的场景,配置化平台想通过配置给组件灵活添加动画
场景一:仅支持animate.css 里的动画,通过选择动画属性展示不同的动画效果即可
通过配置如下属性去加载相应的动画即可
此时选择不同的动画效果只是动画样式的不同而已,于是引用这个库animate.style/
再根据动画效果选中的不同值去给相应的组件添加class名称即可
举个例子?:
<div class="animate__animated animate__bounce animate__faster">Example</div>
<div class="animate__animated animate__bounce animate__delay-2s">Example</div>
<div class="animate__animated animate__bounce animate__repeat-2">Example</div>
复制代码
通过配置灵活获取后渲染:
<script>
const { props } = childConfig;
const {
addAnimation,
animateSpeed,
animateDelay,
animateDuration,
animateRepeat,
} = props;
const classAddAnimation =
addAnimation != "null"
? `animate__animated animate__${addAnimation} animate__${animateSpeed} animate__duration-${animateDuration} animate__delay-${animateDelay} animate__repeat-${animateRepeat} `
: "";
$: titleVarStyles = object2Style({
...props,
});
</script>
<div
use:usebindEventing
style={titleVarStyles}
class="box-center margin-auto absolute mt-vr-poster__title bg-size-100 bg-no-repeat {classAddAnimation}"
{...$$restProps}
/>
复制代码
可以看到其实就是通过配置不同的动画名称,给组件添加不用的动画名字
// 上方图片画框处所配置的动画属性
const {
addAnimation,
animateSpeed,
animateDelay,
animateDuration,
animateRepeat,
} = props;
const classAddAnimation =
addAnimation != "null"
? `animate__animated animate__${addAnimation} animate__${animateSpeed} animate__duration-${animateDuration} animate__delay-${animateDelay} animate__repeat-${animateRepeat} `
: "";
复制代码
但是仅仅通过animate.css动画库并不能满足,运动动画、组合动画(帧动画+运动动画+其他动画的组合连续运动的)、帧动画等场景,所以不能满足所有的业务动画场景。
下面分别看看如何实现逐帧动画和运动动画
三、如何支持逐帧动画,导图序列帧
首先我们要知道什么是逐帧动画?
定格动画,又名逐帧动画,是一种动画技术,其原理即将每帧不同的图像连续播放,从而产生动画效果。
简而言之,实现逐帧动画需要两个条件:(1)相关联的不同图像,即动画帧;(2)连续播放。
我们儿时的记忆,手翻书,他所实现的就是逐帧动画:(此图来自网上)
在细聊 css3 逐帧动画之前,我们先大致了解下前端实现逐帧动画有哪些方案。
其实不外乎三种技术(视频可以实现所有类型的动画,暂不纳入):gif、JavaScript、CSS3 Animation。
前文提到,实现逐帧动画需要两个条件:(1)动画帧;(2)连续播放。
下面我们仔细自己分析下这三种技术是怎么实现上述条件的:
1. gif
在触屏页中,gif 常被用来做加载动画
**gif 可以有多个动画帧,连续播放是其自身属性,是否循环也是由其本身决定的。**它往往用来实现小细节动画,成本较低、使用方便。
但其缺点也是很明显的:
- 画质上,gif 支持颜色少(最大256色)、Alpha 透明度支持差,图像锯齿毛边比较严重;
- 交互上,不能直接控制播放、暂停、播放次数,灵活性差;
- 性能上,gif 会引起页面周期性的 paint ,性能较差。
2. JavaScript
JS 与 CSS3,一般是将动画帧放到背景图中。
不同的是, JS 是使用脚本来控制动画的连续播放的:
- 可以直接改变元素的
background-image
- 也可以将动画帧合并成雪碧图,通过改变
background-position
来实现
使用 JS 的优点是兼容性佳,交互灵活。
3. CSS3 Animation
CSS3 实际上是使用 animation-timing-function
的阶梯函数 steps(number_of_steps, direction)
来实现逐帧动画的连续播放的。
在移动端,CSS3 Animation 兼容性良好,相对于 JS,CSS3 逐帧动画使用简单,且效率更高,因为许多优化都在浏览器底层完成。
因此在触屏页面中 CSS3 逐帧动画使用广泛,下文将对其进行详细介绍。
3.1 CSS3 Animation逐帧动画的实现
(1)将动画帧合并为雪碧图
在触屏页面中,动画往往承担页面样式实现的角色(即不需要替换),因此我们会将图片放到元素的背景中(background-image
)。
逐帧动画有不同的动画帧,我们可以通过更改 background-image
的值实现帧的切换,但多张图片会带来多个 HTTP 请求,且不利于文件的管理。
比较合适的做法,是将所有的动画帧合并成一张雪碧图(sprite),通过改变 background-position
的值来实现动画帧切换。因此,逐帧动画也被称为“精灵动画(sprite animation)”。
(2)使用 steps 实现动画播放
steps 指定了一个阶梯函数,包含两个参数:
-
第一个参数指定了函数中的间隔数量(必须是正整数);
-
第二个参数可选,指定在每个间隔的起点或是终点发生阶跃变化,接受 start 和 end 两个值,默认为 end。
-
通过 W3C 中的这张图片来理解 steps 的工作机制:
回到上述的例子,我们在 keyframes 中定义好每个动画帧:
@-webkit-keyframes p8{ 0%{background-position: 0 0;} 33.33%{background-position: 0 -586px;} 66.66%{background-position: 0 -1172px;} 100%{background-position: 0 -1758px;} } 复制代码
然后,给他加上
animation
:.p8 .page_key{ -webkit-animation: p8 steps(1,end) 1.5s infinite; } 复制代码
为什么第一个参数是1?
前文中提到,steps 是
animation-timing-function
的一个属性值,在 W3C 中有如下说明:For a keyframed animation, the ‘animation-timing-function’ applies between keyframes, not over the entire animation.
也就是说,
animation-timing-function
应该于两个 keyframes 之间,而非整个动画。在上面的 keyframes 中,我们已经把每个帧都写出来了,所以两个 keyframes 之间的间隔是1。更加简便的写法?
既然说 steps 第一个参数是指函数的间隔数量,那么我们就可以把 keyframes 的计算直接交给 steps 来完成。
.p8 .page_key{ -webkit-animation: p8 steps(3,end) 1.5s infinite; } @-webkit-keyframes p8 { 100% {background-position: 0 -1758px;} } 复制代码
以上两种写法效果是等同的。
3.2 CSS3 逐帧动画使用技巧
(1)step-start 与 step-end
CSS3 animation属性中的steps功能符深入介绍 www.zhangxinxu.com/wordpress/2…
除了 steps
函数,animation-timing-function
还有两个与逐帧动画相关的属性值 step-start
与 step-end
:
step-start
等同于steps(1,start)
:动画执行时以开始端点为开始;step-end
等同于steps(1,end)
:动画执行时以结尾端点为开始。
(2)动画帧的计算:
$spriteWidth: 140px; // 精灵宽度
@keyframes ani {
100% {
background-position: -($spriteWidth * 12) 0; // 12帧
}
}
复制代码
(3)适配方案:rem+scale
我们知道,rem 的计算会存在误差,因此使用雪碧图时我们并不推荐用 rem。如果是逐帧动画的话,由于计算的误差,会出现抖动的情况。
那么在触屏页中,如何实现页面的适配?
- 非逐帧动画部分,使用
rem
做单位; - 逐帧动画部分,使用
px
做单位,再结合js
对动画部分使用scale
进行缩放。
3.3 js操作@keyframes解决方案
// js创建@keyframes,ball从定位在(10,10)的位置运动到(100,100) 的位置
const runkeyframes =` @keyframes ball-run{
0%{
left: 10px;
top: 10px;
}
100%{
left: 100px;
top: 100px;
}
}`
// 创建style标签
const style = document.createElement('style');
// 设置style属性
style.type = 'text/css';
// 将 keyframes样式写入style内
style.innerHTML = runkeyframes;
// 将style样式存放到head标签
document.getElementByTagName('head')[0].appendChild(style);
<style>
.ball{
width:10px;
height:10px;
border-radius:50%;
background-color:#fff
}
</style>
<!-- 这是ball的标签和样式 -->
<div id="ball" class="ball" style="animaition: ball-run 10s infinite;"></div>
复制代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>「CSS3」ImageMagick - 从gif建立雪碧图动画 - Sprite Sheet Animation</title>
<style>
/* body {
font-size: 12px;
}
section {
width: 500px;
text-align: center;
}
section img {
box-shadow: 0 0 5px 0 #939393;
} */
.ssa {
width: 60px;
height: 60px;
/* box-shadow: 0 0 5px 0 #939393; */
margin: 10px auto;
background: url(https://raw.githubusercontent.com/JasonKid/fezone/master/CSS/CSS3/ImageMagick/stars.png);
}
@keyframes star {
from {background-position: 0;}
to {background-position: -1740px;}
}
#css3 {
animation: star 1000ms steps(29, end) 0ms infinite normal;
}
</style>
</head>
<body>
<section>
<div class="ssa" id="css"></div>
</section>
<script>
let css = document.getElementById('css');
let xPos = 0;
function cssAnimate() {
css.style.backgroundPosition = (xPos += 60) % 1800 + 'px';
}
let timer = setInterval(cssAnimate, 40);
</script>
</body>
</html>
复制代码
以上demo只是改变了background-position位置,不会修改节点的大小和位置,自然不会触发重布局,但是节点内部的渲染效果进行了改变,所以只需要重绘就可以了.
关于帧动画的demo地址有三个实例:github.com/dingxiaoxue…
四、如何实现运动的动画,贝塞尔曲线
在线调整贝塞尔曲线值:cubic-bezier.com/#.48,.49,.6…
MDN:developer.mozilla.org/zh-CN/docs/…
讲解一、动画知识点须知中已介绍:www.runoob.com/cssref/func…
更细节的解读附上一个张鑫旭的文章:www.zhangxinxu.com/wordpress/2…
直接上曲线运动动画实例demo:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>贝塞尔曲线运动动画实例</title>
<style>
.xAxis {
width: 20px;
height: 20px;
background: rgb(200, 17, 17);
animation: xAxis 2.5s infinite cubic-bezier(0.02, 0.01, 0.21, 1);
}
.yAxis {
width: 20px;
height: 20px;
background: #333;
animation: yAxis 2.5s infinite cubic-bezier(0.3, 0.27, 0.07, 1.64);
}
@keyframes yAxis {
50% {
animation-timing-function: cubic-bezier(0.02, 0.01, 0.21, 1);
transform: translateY(-100px);
}
}
@keyframes xAxis {
50% {
animation-timing-function: cubic-bezier(0.3, 0.27, 0.07, 1.64);
transform: translateX(100px);
}
}
.animation {
width: 50px;
height: 50px;
background-color: #ed3;
animation: yAxis 2.5s infinite cubic-bezier(0.3, 0.27, 0.07, 1.64);
-webkit-transition: all 2s cubic-bezier(.17, .86, .73, .14);
-o-transition: all 2s cubic-bezier(.17, .86, .73, .14);
transition: all 2s cubic-bezier(.17, .86, .73, .14);
}
.animation:hover {
-webkit-transform: translateX(100px);
-ms-transform: translateX(100px);
-o-transform: translateX(100px);
transform: translateX(100px);
}
</style>
</head>
<body>
<h1>实现一个元素从一个位置移动到另外一个位置的动画效果</h1>
<h2>我们先来复习一下CSS3动画属性</h2>
<p>animation:[[ animation-name ] || [ animation-duration ] || [ animation-timing-function ] || [ animation-delay ] || [ animation-iteration-count ] || [ animation-direction ]] [ , [ animation-name ] || [ animation-duration ] || [ animation-timing-function ] || [ animation-delay ] || [ animation-iteration-count ] || [ animation-direction ]]*</p>
<ol>
<li>animation属性值</li>
<li>[ animation-name ]:检索或设置对象所应用的动画名称</li>
<li>[ animation-duration ]:检索或设置对象动画的持续时间</li>
<li>[ animation-timing-function ]:检索或设置对象动画的过渡类型</li>
<li>[ animation-delay ]:检索或设置对象动画延迟的时间</li>
<li>[ animation-iteration-count ]:检索或设置对象动画的循环次数</li>
<li>[ animation-direction ]:检索或设置对象动画在循环中是否反向运动</li>
<li>[ animation-play-state ]:检索或设置对象动画的状态。w3c正考虑是否将该属性移除,因为动画的状态可以通过其它的方式实现,比如重设样式</li>
<li>[animation-fill-mode]: 设置CSS动画在执行之前和之后如何将样式应用于其目标 https://developer.mozilla.org/zh-CN/docs/Web/CSS/animation-fill-mode</li>
</ol>
<h2>再学习下贝塞尔曲线cubic-bezier(),应用于animation-timing-function中</h2>
<p> 曲线值调整可用这个在线网站调整完参数复制过来:https://cubic-bezier.com/#.19,.61,.72,.52</p>
<ol>
<li>cubic-bezier() 函数定义了一个贝塞尔曲线(Cubic Bezier)cubic-bezier() 可用于 animation-timing-function 和 transition-timing-function 属性。cubic-bezier(x1,y1,x2,y2)</li>
<li>贝塞尔曲线曲线由四个点 P0,P1,P2 和 P3 定义。P0 和 P3 是曲线的起点和终点。P0是(0,0)并且表示初始时间和初始状态,P3是(1,1)并且表示最终时间和最终状态。</li>
<li>P0:默认值 (0, 0);P1:动态取值 (x1, y1);P2:动态取值 (x2, y2);P3:默认值 (1, 1)</li>
<li>x1,y1,x2,y2 必需。数字值,x1 和 x2 需要是 0 到 1 的数字。</li>
</ol>
<h2>需求:用CSS3实现一个正方形的曲线运动</h2>
<h2>1.贝塞尔曲线运动例子1</h2>
<div class="xAxis">
<div class="yAxis"></div>
</div>
<h2>1.贝塞尔曲线运动例子2</h2>
<div class="animation"></div>
</body>
</html>
复制代码
五、如何实现连续动画(帧动画+运动动画+CSS动画)
5.1、常见的动画类型
一种已经提过,再简单列举一下
动画类型 | 实际场景 | 运动描述 | demo地址 |
---|---|---|---|
1.cssAnimation动画 | animate.style/ | 抖动从左侧出来从右侧出来从小变大… | animate.style/ |
2.逐帧动画 | ![]() ![]() ![]() |
手指/圆点/星星运动根据移动背景图片位置来切换动画运动起来 | github.com/dingxiaoxue… |
3.运动动画 | ![]() |
从一个位置沿着路线运动到另一个位置也就是贝塞尔曲线运动动画 | github.com/dingxiaoxue… |
4.以上三种动画的自由组合 | ![]() |
运动动画+帧动画的结合 如小球运动到终点后又开始闪动 (此demo图不太对,没录制demo的gif图直接网上搜的) | 如下 |
5.2、通用的动画组件设计
下图是组合动画流水线(通用动画)协议描述设计流程和实现思路:针对基础动画、帧动画、运动动画三种类型分别做各个场景的实现,最终形成组合动画的设计
a.每种类型组件都写一个demo
b.找他们的共同点
c.最后把多个连续的动画通过css的animation属性中可以连续增加多个动画的特性,来组合多个连续的动画
d.组合连续多个动画的同时保证无论几个动画哪种类型的都能够兼容处理
5.3、通用动画config schema协议描述
由上我们得出一份通用的配置协议
{
"background-image": 背景图片,
"background-size":背景大小,
"background-position": 背景位置,
"width": 元素宽,
"height": 元素高,
"animation": [{
"animation-property": 动画运动属性["background-position","left","top","opacity","default"],default:直接用https://animate.style/中属性
"start": 动画运动开始属性值,
"end": 动画运动结束的属性值,
"animation-duration": 对象动画的持续时间,
"animation-delay": 动画延迟的时间,
"animation-iteration-count": 动画的循环次数,
"frame-count":动画的背景帧数,
"animation-timing-function": 检索或设置对象动画的过渡类型 动画函数:"cubic-bezier(.48,.49,.51,.69)"|"steps(29, end)" |"linear",https://developer.mozilla.org/zh-CN/docs/Web/CSS/animation-timing-function
"animation-direction":动画在循环中是否反向运动,
"animation-fill-mode": 设置CSS动画在执行之前和之后如何将样式应用于其目标"forwards"
}
]
}
复制代码
5.4、动画渲染逻辑
所有类型动画实例都通过后,处理动画渲染逻辑:采用基础属性+多个animation叠加顺序展示,执行的详细方案如下:
a.动画样式循环遍历拼接好
b.添加到容器组件上与元素本身通过slot进行隔离
c.与事件模块联通,来判断动画的触发方式和何时展示
d.整个容器组件执行完毕销毁从body中移除动画样式和事件操作
5.5、多动画流水线属性可视化配置
以上单独实现或者结合在一起去实现都还好,但是如何通过配置实现以上任意一种类型的动画效果?这就要考虑到通用性、细节的考量、技术选型三方面
1.通用性方面主要考虑如何通过数据渲染并且数据格式统一通用
动画的通用基本属性
"background-image": 背景图片,
"background-size":背景大小,
"background-position": 背景位置,
"width": 元素宽,
"height": 元素高,
复制代码
动画的通用运动属性
{
"animation-property": 动画运动属性["background-position","left","top","opacity","default"],default:直接用https://animate.style/中属性
"start": 动画运动开始属性值,
"end": 动画运动结束的属性值,
"animation-duration": 对象动画的持续时间,
"animation-delay": 动画延迟的时间,
"animation-iteration-count": 动画的循环次数,
"frame-count":动画的背景帧数,
"animation-timing-function": 检索或设置对象动画的过渡类型 动画函数:"cubic-bezier(.48,.49,.51,.69)"|"steps(29, end)" |"linear",https://developer.mozilla.org/zh-CN/docs/Web/CSS/animation-timing-function
"animation-direction":动画在循环中是否反向运动,
"animation-fill-mode": 设置CSS动画在执行之前和之后如何将样式应用于其目标"forwards"
}
复制代码
通过多个以上四种动画类型实现方式找出通用的部分且这些可以实现多种类型的动画
2.细节主要是兼容处理、字段空处理、校验、异常处理等
- 属性不存在直接设定默认值
- 格式解析错误处理
- 动画兼容处理[“webkit”, “moz”, “MS”, “o”, “”]
3.技术选型就是动画实现方式有多种,选用哪种更实用
- gif
- js
- css:
- canvas
代码实现
function obj2str(obj) {
return Object.keys(obj).filter(item => obj[item]).reduce((prev, cur) => prev += (`${cur}:${obj[cur]};`), '')
}
export function renderingAnimationCSS(continuAnimation, id, animationId) {
// console.log('进入css3动画设置', continuAnimation)
let obj = {
width: continuAnimation?.width,
height:continuAnimation?.height,
'background-image': continuAnimation["background-image"] ?`url('${continuAnimation["background-image"]}')` : "",
'background-size': continuAnimation["background-size"],
position: 'absolute',
'background-position': continuAnimation["background-position"],
};
let continuekeyframes = '';
let animationData = '';
let keyframes = '';
continuAnimation?.animation?.map((item, index) => {
// console.log('进入css3动画设置当前行item', item)
//CSS animation 属性是 animation-name,animation-duration, animation-timing-function,animation-delay,animation-iteration-count,animation-direction,animation-fill-mode 和 animation-play-state 属性的一个简写属性形式。
if (item["animation-property"] != "default") {
animationData += `keyframe-${animationId}-${index} ${item["animation-duration"]} ${item["animation-timing-function"] || 'linear'} ${item["animation-delay"]} ${item["animation-iteration-count"]} ${item["animation-direction"] || 'normal'} ${item["animation-fill-mode"]} ${index + 1 < continuAnimation?.animation.length ? ',' : ''}`
// console.log('进入css3动画设置animationData', animationData)
keyframes +=
` @keyframes keyframe-${animationId}-${index} {
0% {
${item["animation-property"]}:${item.start};
}
100% {
${item["animation-property"]}: ${item.end};
}
}
`
// console.log('进入css3动画设置keyframes', keyframes)
} else {
animationData +=`${item["animation-name"] || 'bounce'} ${item["animation-duration"]} ${item["animation-timing-function"] || 'linear'} ${item["animation-delay"]} ${item["animation-iteration-count"]} ${item["animation-direction"] || 'normal'} ${item["animation-fill-mode"]} ${index + 1 < continuAnimation?.animation.length ? ',' : ''}`
}
});
obj = { ...obj, animation: animationData }
continuekeyframes += `.a-${animationId}{${obj2str(obj)}}${keyframes}`;
// console.log("css3动画设置结果2",continuekeyframes)
const style = document.createElement("style");
style.id = `animation-${id}`;
style.setAttribute("type", "text/css");
style.innerHTML = continuekeyframes;
document.head.appendChild(style);
}
复制代码
<script lang="ts">
import ditto from "ditto-core";
import { jsonString2Style, object2Style } from "ditto-tools";
import { onDestroy, onMount } from "svelte";
import { renderingAnimationCSS } from './animation';
import "./index.css";
const usebindEventing = ditto.$event.usebindEventing;
export let childConfig;
const isEditor = window.isEditor;
const { props, id, childId } = childConfig || {};
const animationId = childId && childId.replace(".", "-");
let animationClass = "";
let i = 0;
// 相互转化 single[0]= addAnimation
const {
aEvent = {0: "direct"},
continuAnimation,
// singleAnimation,
...stylePropsObj
} = props || {};
// let classAddAnimation = "";
let isAnimating = false;
// console.log("stylePropsObj==", stylePropsObj);
$: style = object2Style(stylePropsObj);
let el;
let continuAnimations = [];
try {
if(continuAnimation) {
continuAnimations = JSON.parse(continuAnimation);
// console.log("continuAnimations===", continuAnimations);
}
} catch(e) {
console.log("解析错误", e);
}
let pfx = ["webkit", "moz", "MS", "o", ""];
function prefixedEventListener(element, type, callback, addOrRemove) {
for (var p = 0; p < pfx.length; p++) {
if (!pfx[p]) type = type.toLowerCase();
element[addOrRemove](pfx[p] + type, callback, false);
}
}
function handleAniation(isEvent) {
// console.log("动画回调!!!!", i);
if(!isEvent && animationClass) {
return;
}
if(continuAnimations?.animation?.length === i) {
// console.log("重置!!!!",i)
animationClass = "";
// 动画全部处理完成, 可以触发事件
i = 0;
ditto.$event.triggerAllEvent(childId);
return;
}
if (continuAnimations?.animation?.length >= 1 && i < (continuAnimations?.animation?.length)) {
animationClass = `a-${animationId}`;
i++;
}
}
let bindGlobalState;
onMount(() => {
// 生成
renderingAnimationCSS(continuAnimations, childId, animationId);
prefixedEventListener(
el,
"animationend",
handleAniation,
"addEventListener"
);
if(continuAnimations.length === 0) {
// 如果没有动画就不监听,不触发
return;
}
if(aEvent && aEvent[0]) {
// 当配置存在,并且如果是直接的形式则直接触发
if(aEvent[0] === 'direct') {
handleAniation(true);
// classAddAnimation = singleName;
} else if(aEvent[0] === 'click') {
bindGlobalState = ditto?.globalState?.subscribe(state => {
// console.log("state===", state, childId);
if(state[`ae-${childId}`] === '1') {
// console.log("触发!!!!");
handleAniation(false);
// classAddAnimation = singleName;
}
})
}
}
});
onDestroy(() => {
// 清除 handleAniation
// 清除 style
if(bindGlobalState) {
bindGlobalState();
}
var styleTag = document.getElementById(`animation-${childId}`);
if(styleTag) {
document.head.removeChild(styleTag);
}
prefixedEventListener(
el,
"animationend",
handleAniation,
"removeEventListener"
);
});
</script>
<div {style} class={`absolute w-28 h-28 ${animationClass}`} bind:this={el} use:usebindEventing {id}>
<slot>
<div>我是组件容器</div>
</slot>
</div>
复制代码
不过做到这里,还有瑕疵,就是通用动画config schema协议描述还没有可视化,用户配置很难看懂,下面对属性config schema做一个可视化的设计
六、通用动画config schema协议可视化设计
从右向左:依次的转换流程和设计如图
- 动画渲染是用的1中的数据
- 动画的属性渲染用的是3中的数据格式
- 所以属性渲染和动画渲染之间通讯需要有一层中间转换
七、动画性能优化
developer.mozilla.org/zh-CN/docs/…
www.coderbridge.com/series/dd90…
Animating with javascript: from setInterval to requestAnimationFrame
八、关于动画相关知识点链接
如何实现贝塞尔曲线动画:www.runoob.com/cssref/func…
动画属性表:caibaojian.com/css3/proper…
使用 HTML5 Canvas 实现移动平台动画(游戏)的一些痛点和思路:www.4k8k.xyz/article/wei…
canvas动画包教不包会:移动物体:www.w3cschool.cn/xjmuw/xjmuw…
基于canvas实现物理运动效果与动画效果(一):www.shuzhiduo.com/A/E35paeWAd…
zhuanlan.zhihu.com/p/160254522
revealjs.com/auto-slide/ github.com/hakimel/rev…
如何使用Tween.js各类原生动画运动缓动算法:www.zhangxinxu.com/wordpress/2…
CSS3–通过Animation实现简单的手指点击动画blog.csdn.net/sinat_36263…
CSS动画指南 – 补间动画&逐帧动画 :github.com/SIPC115/Pop…
CSS ANIMATION动画效果实现精灵图、雪碧图动画,多动画应用:www.freesion.com/article/405…
caibaojian.com/animationen… animationend事件监听CSS动画结束及兼容代码
jishuin.proginn.com/p/763bfbd35… 10个CSS3动画工具,值得你收藏!
九、感谢阅读
「❤️关注+点赞+收藏+评论+转发❤️」,创作不易,鼓励笔者创作更好的文章
关注公众号小圆脸儿
,一个专注于web前端基础、工程化、面试的前端公众号,偶尔也会发发生活