早一两个月前看到了这个页面某公司招人页面,觉得挺炫,一直想临摹一遍,自己又懒,这周六把它实现了一遍,代码已经上传到我的github上Css3AnimationExample,算是个小战果吧

需求分析




静态图,效果看不大出。但基本也就是手势swipe up; swipe down切换页面。但仔细观察会发现:

  1. 每个页面都可归纳出四种动画:swipe up隐藏,swipe up显示,swipe down隐藏,swipe down显示;
  2. 四种动画中页面中元素的动画顺序不固定,速度不固定,动画甚至也不是简单的两种状态(添加和删除同一个class)

尝试

我进行了第一个尝试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="page page1 active" style="z-index:5;">
<div class="circle-icon" data-sort="1" has-next="true">
<div class="circle" data-sort="2"></div>
<div class="icon" data-sort="3" ></div>
</div>
<div class="slogan" data-sort="4">
</div>
<div class="car-run" data-sort="5">
<div class="car-suite">
<div class="cdj" data-sort="6"></div>
<div class="bmw" data-sort="7"></div>
<div class="bm320" data-sort="8"></div>
<div class="car"></div>
<div class="wheel1"></div>
<div class="wheel2"></div>
</div>
<div class="ground"></div>
</div>
</div>

在html元素中自定义属性标示出四种动画的元素顺序,之后依次为这些元素加上on或者next( on为动画一级状态,next为动画二级状态,用多个类逐渐添加来解决动画有多个类的问题)
但是对于有的元素在第三步时是addClass(‘next’); 有的则是removeClass(‘on’);这个问题怎么办?只有再继续添加标志性的自定义元素has-next了
好,怎么控制不同的启动速度,当然也可以继续添加自定义属性,比如什么data-next-time
上面问题都解决了,但是很不优雅,也不一目了然。


我们可以总结出两点:

  1. css3动画其实就是类的叠加
  2. 我们需要记录动画信息,并能通用的解析出来,提高扩展性

既然html不行,就换成js吧

解决方案

先上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
var pages = [
{ //page1
'upIn': ['.logo|on|add', '.circle|on|add', ['.circle-icon|on|add','.icon|on|add'], '.slogan|on|add', '.car-run|on|add|200', '.cdj|on|add|200', '.bmw|on|add|200', '.bm320|on|add'],
'upOut': ['.circle-icon|next|add', '.slogan|next|add|200', '.cdj|on|remove|200', '.bmw|on|remove|200', '.bm320|on|remove', '.car-run|next|add'],
'downIn': ['.car-run|next|remove|100', '.bm320|on|add|100', '.bmw|on|add|100', '.cdj|on|add|100', '.slogan|next|remove', '.circle-icon|next|remove'],
'downOut': []
},
{ //page2
'upIn': [ '.niu-ren|on|add', 'body|zoom-out|add', '.mail-icon|on|add', '.words|on|add', '.money|on|add', 'body|zoom-out|add' ],
'upOut': ['.niu-ren|next|add', '.mail-icon|next|add', '.words|next|add'],
'downIn': ['.words|next|remove', '.mail-icon|next|remove', '.niu-ren|next|remove'],
'downOut': ['.words|on|remove', '.mail-icon|on|remove', '.niu-ren|on|remove']
},
{ //page3
'upIn': ['.p3-title|on|add', '.recommend|on|add|100', '.recommend-name|on|add|100', '.recommend-tel|on|add|100', '.recommend-mail|on|add|100', '.be-recommend|on|add|100', '.be-recommend-name|on|add|100', '.be-recommend-job|on|add|100', '.be-recommend-resume|on|add|100'],
'upOut': ['.p3-title|next|add|200', '.recommend|next|add|200', ['.recommend-name|next|add|200', '.recommend-tel|next|add|200', '.recommend-mail|next|add|200'], '.be-recommend|next|add|200', ['.be-recommend-name|next|add|200', '.be-recommend-job|next|add|200', '.be-recommend-resume|next|add|200']],
'downIn': ['.be-recommend-resume|next|remove|200', '.be-recommend-job|next|remove|200', '.be-recommend-name|next|remove|200', '.be-recommend|next|remove|200', '.recommend-mail|next|remove|200', '.recommend-tel|next|remove|200', '.recommend-name|next|remove|200', '.recommend|next|remove|200', '.p3-title|next|remove|200'],
'downOut': ['.be-recommend-resume|on|remove|200', '.be-recommend-job|on|remove|200', '.be-recommend-name|on|remove|200', '.be-recommend|on|remove|200', '.recommend-mail|on|remove|200', '.recommend-tel|on|remove|200', '.recommend-name|on|remove|200', '.recommend|on|remove|200', '.p3-title|on|remove|200']
},
{ //page4
'upIn': ['.p4-title|on|add', '.java|on|add|100', '.android|on|add|100', '.ios|on|add|100', '.view-design|on|add|100', '.marketing|on|add|100', '.product|on|add|100', '.query-btn|on|add', '.email|on|add'],
'upOut': [],
'downIn': [],
'downOut': ['.email|on|remove|100', '.query-btn|on|remove|100', '.product|on|remove|100', '.marketing|on|remove|100', '.view-design|on|remove|100', '.ios|on|remove|100', '.android|on|remove|100', '.java|on|remove|100', '.p4-title|on|remove']
}
];
/**
* css动画就是添加和删除class
* @param information
* @param container
*/
function handleClass(information, container){
var infos = information.split('|');
//如果元素不在该page中,全documen查找
var target = container.find(infos[0]);
if(target.length == 0){
infos[2] == 'add' ? $(infos[0]).addClass(infos[1]) : $(infos[0]).removeClass(infos[1]) ;
} else {
infos[2] == 'add' ? target.addClass(infos[1]) : target.removeClass(infos[1]) ;
}
}
var prevTimer;
/**
* 显示动画
* @param container page容器
* @param aniArr 动画信息数组
* @param index 递归标记
*/
function showAnimation(container, aniArr, index, endCall){
clearTimeout(prevTimer);
if(index >= aniArr.length){ //递归结束条件
endCall = endCall || function(){};
endCall();
return;
}
var information = aniArr[index];
var tmpl; //标记自定义时间,数组中的默认第一个元素
if( typeof information == 'string'){
handleClass(information, container);
tmpl = information.split('|')[3];
} else {
//infos是数组
for(var j= 0,jLen=information.length; j<jLen; j++){
handleClass(information[j], container);
}
tmpl = information.length > 0 ? information[0].split('|')[3] : undefined;
}
prevTimer = setTimeout(function(){
showAnimation(container, aniArr, ++index, endCall);
}, !!tmpl&&tmpl.trim().length>0 ? parseInt(tmpl) : 500); //没有自定义时间
}
/**
* 初次载入动画
*/
showAnimation($('.page1'), pages[0]['upIn'], 0);

看到数据结构自然就懂了,“选择器|类|操作(add或者remove)|自定义下个动画时间(可以不写默认500ms)”,对于有多个同时触发的,采用一个子数组出现。
这样剩下的就是解析字符串了,当时我还在想,字符串还好,如果是子数组,里面又有子数组怎么办?
这tm岂不是一个多维数组遍历问题,肯定要用递归,递归也行。关键这里是采用的setTimeout,一个异步回调+递归将是一个难以解决的蛋疼问题。
不对。。。。这tm不是多维的最多是二维,明显同一个时间里面就那几个,不能同一个时间里面还有第三维吧,好吧,这是物理问题。
这样问题就好办了,通过setTimeout动态改变间隔时间来调整速度,每次取消上次的计时器,再根据当前参数新建新的计时器,递归解决,就是这样。

这应该算是比较完美解决连续多个css3动画不同速度不同方向不同顺序播放的问题吧:css+js