2017 京东集团开年大会在 2 月 10 日终于开始了,当然,也结束了。
微信朋友圈里纷纷表示:
成功保持工作几年来从来没有中过奖的记录。
完美错过所有大奖 ,新的一年大家工作顺利。
一如既往,成功避开所有奖项
虽然没有中奖,但是大家在年会等待开奖期间还是有不少好玩的东西可以消遣的。下面就为大家介绍两个基于 CSS3 做的动画效果:一个是在朋友圈广为扩散、略显逼格的「京彩 AR」,一个是严肃认真、指导场内人员的「员工参会电子手册」。
先睹为快吧:
要做好一个 CSS3 动画,既要关注性能,流畅不卡顿,适配各种分辨率的手机屏幕,还需要关注作品是否自然,动画过渡流畅生动。在接到项目需求时,我们的内心是崩溃的,不过幸好有友爱的设计师和强力的前端指导,最终完成了所有动画效果。
设计师「 陈秀峰」美女给的设计稿如下,包括初始加载动画、仿微信锁屏消息通知、仿微信聊天界面以及一个加载动画的 gif 演示图。
页面中存在宇航员 Joy、星球、消息块以及若干修饰元素。根据页面所需动画效果的粒度和页面结构,我们将设计稿按下面指示切出素材。
宇航员 Joy 部分的动画最为复杂,它的元素很多,组织好各个元素的出场,在场及退场的时间和顺序是一个极具挑战的。首先我们将元素分布画出来:
我们将距离屏幕最近的左下角星球作为太空运行中心,其余的星球和流星围绕中心运转。对于星球、流星、火箭等这里基本采用的是直线运动,主要是控制运动的速度和直线的角度,还有就是元素前后关系。两点决定一个直线的的轨迹,所以设定各个元素的起始点、终止点以及帧动画运动的时间,例如流星位移利用
更改 X 和 Y 的坐标值,将元素移出视窗外。
- translate3d
- /*流星*/
- @mixin MeteorRun($name,$t,$delayTime,$mode,$distanceX,$distanceY){
- animation:$name $t $mode;
- animation-fill-mode:forwards;
- animation-delay:$delayTime;
- @keyframes #{$name}{
- 0%{
- opacity: 0;
- transform: translate3d(0,0,0);
- }
- 5%{
- opacity: 1;
- transform: translate3d(0,0,0);
- }
- 100%{
- opacity: 1;
- transform: translate3d($distanceX*2,$distanceY*2,0);
- }
- }
- }
而宇航员 Joy 脚下的火焰,则利用了精灵动画来处理,首先切一个完整的精灵图,然后用背景图的位移
来控制动画效果。
- background-position
- .joy-fire{
- display: inline-block;
- height:1.28rem;
- width:1.42rem;
- background:url(i/fires.png) 0 0 no-repeat;
- background-size:7.1rem 1.28rem;
- @include joyFire(fire1,1.42rem);
- }
- @mixin joyFire($name,$width){
- animation:$name 1s steps(5) infinite;
- @keyframes #{$name}{
- 0%{
- background-position: 0 0;
- }
- 100%{
- background-position: -($width*5) 0;
- }
- }
- }
我们利用
属性的
- animation
函数做关键帧的划分,这个函数和接下来要讲的贝塞尔函数是
- steps
的两个可选函数,实际使用中我们经常会忽略
- animation-timing-function
函数的使用。
- steps
函数是一个阶跃函数,它对
- steps
里的两两关键帧之间进行等分。
- @keyframes
对 0% 和 100% 两个关键帧进行 5 等分切分,每个等分时间间隔内的动画保持不变。可以这么理解,
- steps(5)
函数是在
- steps
时,均匀取了 5 个点的动画状态,5 个动画状态持续时间各占整个帧转换时间的 1/5。
- animation-timing-function: linear
需要注意的是,如果
里定义了 3 个或更多的关键帧,那么
- @keyframes
是对所有前后关键帧进行切分。
- steps
关于 steps 更详细的介绍请参考 和 。
锁屏消息通知模拟的是 WeChat6.5.4 在 iPhone 下的锁屏通知效果,消息的提示框由中心往外层弹出,它是一个 3D 动画,我们利用
属性的
- transform
方法,编写了一组关键帧动画。
- scale3d
- animation:infoTwo 1s 1 cubic-bezier(0.4, 0, 0, 1);
- @keyframes infoTwo{
- 0% {
- transform: scale3d(0.5,0.5,0.5);
- opacity: 0;
- }
- 100%{
- transform: scale3d(1,1,1);
- opacity: 1;
- }
- }
要贴近 iPhone 的动画效果,我们就需要对动画的速度曲线有良好的模拟。
属性的速度曲线函数属性是
- animation
,它可以接受一个三次贝塞尔(Cubic Bezier)函数。
- animation-timing-function
用一个定义在二维平面的贝塞尔曲线的斜率来描述动画的速度,由于贝塞尔曲线很平滑,那么速度的变化就很平滑。下面是弹出消息的贝塞尔曲线。
这条曲线结合
关键帧信息,可以得到消息通知弹出动画的具体效果:
- @keyframes infoTwo
这样,动画显示出来不显得突兀,同时又能给人一种冲击感,最后缓和下来,不让动画干扰用户提前阅读消息。
微信锁屏消息通知的动画场景转换不多,接下来的聊天界面却充满了各种场景转换,我们将在这一节详细介绍如何进行场景转换的。
聊天界面场景转换需要考虑每条信息弹出时的间隔,信息超过屏幕高度时自动上移显示最新一条消息。下面是场景和时间线的规划:
场景动画需要注意的问题是动画性能。大家都知道重排(reflow)比重绘(repaint)更好性能,那么如何处理微信对话场景的元素出入问题呢?我们在首次加载时就把所有元素绘制在页面中,然后利用
和
- opacity
两个属性来处理元素的显现和移动。
- transform
- @mixin FadeInt($t,$delayTime,$mode){
- animation:fadeIn $t $mode;
- animation-fill-mode:forwards;
- animation-delay:$delayTime;
- @keyframes fadeIn{
- from{
- opacity: 0;
- }
- to{
- opacity: 1;
- }
- }
- }
- .wx-5{
- @include FadeInt(0s,9.6s,linear);
- }
- .wx-6{
- @include FadeInt(0s,11.6s,linear);
- }
- var oldDistance = 0;
- function setTimedistance(el){
- //大于等于7步,将计算距离平移
- var distance = el.offset().top - (windowHeight - wxFooter);
- $('.weixin .content').animate({
- translate3d:'0,-'+(distance+oldDistance)+'px,0'
- });
- oldDistance += distance;
- }
我们使用
方法来处理转场中的一些细节动画,包括微信提示音、头部文字切换。CSS 动画时间和 JS 动画的时间线保持同步成为关键。JavaScript 的事件轮询机制不能保证
- setTimeout
方法准确按预期时间延迟,所以,最严格的解决方案就是监听
- setTimeout
动画结束事件。
- animation
首先,我们对动画结束触发事件方法
做一个兼容处理:
- animationend
- /*动画结束监听*/
- function whichAnimationEvent(){
- var t,
- el = document.createElement("fakeelement");
- var animations = {
- "animation" : "animationend",
- "OAnimation" : "oAnimationEnd",
- "MozAnimation" : "animationend",
- "webkitAnimation": "webkitAnimationEnd"
- };
- for (t in animations) {
- if (el.style[t] !== undefined) {
- return animations[t];
- }
- }
- }
- var animationEvent = whichAnimationEvent();
然后,监听各个场景动画结束事件,比如监听场景 6 的结束事件:
- $('.weixin.curr .wx-6').one(animationEvent,
- function(event) {
- setTimeout(function(){
- audioMessage.currentTime = 0;
- audioMessage.play();
- },2500);
- });
微信对话是在手机一屏展示的,七、八、九场景是超出去屏幕的。当动画到场景六时候,就需要计算场景七的高度,采用
的
- jQuery
函数来处理上移的动画,八九场景也是一样的处理方法。这样就能完美的贴近真实的微信对话了。
- animate
整个京彩 AR 的动画演示:
是不是很赞呢?
在以上的「京彩 AR」动画中看到 CSS3 动画的能力,作为利器, 也可以配合 JS 实现更丰富的交互。长长的开年大会电子手册内容导航组件就是 CSS3 动画配合 JS 相得益彰的绝佳例子。
电子手册的导航菜单类似于一个转盘,当滑动页面到某一个章节时,导航菜单也会转到相应的菜单,并且高亮。
导航菜单可以分为三部分,底部是转盘背景,它是固定不动的。上层分为图标 icon 和章节标题部分。在点击章节图标时,转盘转动。不对背景进行旋转的原因是背景的高光暗含转盘指针功能,因此保持背景不动。
很自然的,利用
的
- transform
属性来进行旋转,利用
- rotate
属性响应
- transition
属性变化。
- transform
- <divclass="menu-wheel">
- <divclass="wheel-layer">
- <divwheel="1" class="wheel wheel-bright">
- </div>
- <divwheel="2" class="wheel">
- </div>
- <divwheel="3" class="wheel">
- </div>
- <divwheel="4" class="wheel">
- </div>
- <divwheel="5" class="wheel">
- </div>
- <divwheel="6" class="wheel">
- </div>
- </div>
- <divclass="wheel-name">
- 会议流程
- </div>
- </div>
代表一个图标 icon,每个图标 icon 按顺序分配一个
- div.wheel
属性,用于关联章节,比如
- wheel
时,对应的是第二章节。
- wheel="2"
和
- .wheel
的
- .wheel-bright
是普通状态和高亮状态的图标 sprite 图,用
- background-image
选择器调整它的
- [wheel='i']
显示不同的章节图标 icon。
- background-position
为图标 icon 布局层,每个图标 icon 相对于
- .wheel-layer
定义的
- .wheel-layer
变换中心旋转。静态情况下,图标 icon 如设计稿一样均匀分布在转盘圆周上,点击时,用 JS 改变
- transform-origin
的
- .wheel-layer
属性,
- transform
就会按照定义的动画效果相对于
- .wheel-layer
旋转,所有的图标 icon 也就随之旋转。
- .wheel-menu
关键 sass 代码如下:
- $w: 252px;
- $ww: 36px;
- .wheel-layer{
- position: relative;
- transform-origin: $w/2 $w/2;
- transition: transform .4s;
- }
- .wheel{
- position: absolute;
- background: url(i/bg-wheel-dark.png) no-repeat 0 0;
- background-size: 270px $ww;
- transform-origin: 16px $ww+$bt;
- }
- .wheel-bright{
- background: url(i/bg-wheel-bright.png) no-repeat 0 0;
- }
- div[wheel='5']{
- background-position: -$wwbp*4 0;
- transform: rotate(-120deg);
- }
得到的分层结构如图:
在页面滚动和点击导航菜单时,如何确定菜单的旋转方向是一个难题!在渲染菜单时,我们已获取当前转盘的状态,因此,可以直接在 JS 里计算转动方向。
- // 获取偏转因子
- var factor = 0;
- if ((fromIdx + 1) % 6 === toIdx % 6) {
- factor = -1;
- } else if ((fromIdx + 2) % 6 === toIdx % 6) {
- factor = -2;
- } else if ((fromIdx + 3) % 6 === toIdx % 6) {
- factor = -3;
- } else if ((fromIdx + 4) % 6 === toIdx % 6) {
- factor = 2;
- } else if ((fromIdx + 5) % 6 === toIdx % 6) {
- factor = 1;
- }
- // 得到最终旋转角度
- var transDeg = currentDeg + factor * 60;
- wheelLayer.style.transform = 'rotate(' + transDeg + 'deg)';
其中
是当前高亮的章节图标的
- fromIdx
属性值,
- wheel
是点击的章节图标
- toIdx
属性值或者滚动到的章节。将这个值赋给
- wheel
的
- .wheel-layer
属性后,动画工作就交给
- transform
属性吧。
- transition
测试时发现,有些浏览器转盘并未按预期转动,比如在某款小米手机上,必须加上
属性,否则菜单会无法转动。当然了,我们也可以像给
- wheelLayer.style.webkitTransform
事件做兼容处理一样,给这个
- animationend
属性做一个统一兼容处理。
- transform
- wheelLayer.style.transform = 'rotate(' + transDeg + 'deg)';
- wheelLayer.style.webkitTransform = 'rotate(' + transDeg + 'deg)';
最终效果:
CSS3 中的
(变换)、
- transform
(过渡)、
- transition
(动画)属性能帮助我们实现很多不可思议的动画效果,减轻了我们的工作量。在 2017 京东集团开年年会项目中充分利用了这三个属性来完成各种动画交互,取得了不错的效果。
- animation
虽然 CSS3 能帮我们完成很多工作,但要保证动画能在各种设备上流畅自然,离不开对细节的不断孜孜追求。
京彩 AR:汪楠
员工参会电子手册:刘佳
在线贝塞尔曲线函数编辑及效果演示:
CSS 动画帧数计算器:
很火的动画库:
更加丰富的 CSS 动画库 – Effeckt.css:
animation-timing-function W3C 规范:
来源: http://www.tuicool.com/articles/Vz6JZni