背景

在做消费者安全中心这个项目的时候,有个想法是在页面切换的时候做些转场动画,使得更像原生体验。这个项目是基于Backbone来做的,熟悉Backbone的都知道,框架本身没有提供什么转场动画,市面上也没看到有人分享过什么插件实现这个效果,后来和小伙伴们研究出一个实现办法,分享出来给大家。

效果

最终实现效果如下

fdsfdsafa.gif

可在手机淘宝“我的淘宝->设置->账号与安全->安全中心”打开或使用手机淘宝扫下面二维码

实现

要想实现转场,首先考虑在哪个时机进行?一开始我想到肯定是在View切换的时候,我来看看路由如何控制View切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export default Backbone.Router.extend({
currentView: null,
routes: {
'': 'home',
'list': 'list'
},
changeView(view) {
if (this.currentView !== null) {
this.currentView.undelegateEvents();
}
this.currentView = view;
this.currentView.render();
},
home() {
const homeView = new HomeView();
this.changeView(homeView);
},
list() {
const listView = new ListView();
this.changeView(listView);
}
});

一开始我是这样想的,changeView函数里有viewcurrentViewcurrentView是当前页面,view是即将出来的页面,有了这两个元素,进行转场岂不是太简单了!于是想拿到这两个元素,打印出来看看

粘贴图片(3).png

发现viewcurrentViewel 属性都是指向app,也就是页面的根元素。这时我懂了,Backbone所有View的共用一个div,只要有一个View执行render,那么根元素里面的内容就被替换,所以同一时刻只有一个View的内容存在

粘贴图片(8).png

那么解决方案就是,我们需要保证在转场过程中两个View的内容同时存在,转场结束后把上一个View的内容清空。

Backbone.View里有个 el 属性,就是设置页面的根元素,我们要保证转场过程中两个View的内容同时存在,那一个根元素是不够的,需要两个!

粘贴图片(8).png

所以我们在View的构造函数里动态选页面择根元素

1
2
3
4
5
6
7
8
9
10
11
12
13
constructor(options) {
// 为了配合转场动画,view创建的时候会动态选择根元素
this.el = this.selectViewRoot();
$(this.el).removeClass('app-disabled').addClass('app-active');
},
selectViewRoot() {
if (!$('.app-content').hasClass('app-active')) {
return '.app-content';
} else if (!$('.app-content-back').hasClass('app-active')) {
return '.app-content-back';
}
return null;
}

这里 app-contentapp-content-back 是两个页面根元素,当一个View创建的时候动态选择其中一个。比如View1当前使用的根元素是app-content,从View1切换到View2的时候,View2则使用app-content-back,View2切换到View3的时候,View3则会使用app-content,如此类推。

这时,我们就可以保证在View切换的时候,两个View的内容是同时存在的。那在这个时候进行动画太简单了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
changeView(view) {
if (this.currentView !== null) {
this.currentView.undelegateEvents();
Scene.changeView(this.currentView.$el, view.$el);
}
this.currentView = view;
this.currentView.render();
},
// CSS3动画转场,这里可自由选择
Scene.changeView(currentView, view) {
if (appback) {
// 页面返回 转场
currentView.addClass('slideOutRight');
view.addClass('slideInLeft');
} else {
// 页面进入 转场
currentView.addClass('slideOutLeft');
view.addClass('slideInRight');
}
}

什么时候清空掉上一个View的内容呢?那就是转场动画结束后。

1
2
3
4
5
6
7
8
$('.app-content').on('webkitAnimationEnd', () => {
// 清除动画
$('.app-content').removeClass(...);
// 清除上一个View
if ($('.app-content').hasClass('app-disabled')) {
$('.app-content').html('');
}
});

到这里,转场动画已经全部实现了,上面贴的只是核心代码,详细可以自己体会。下面一张图展示这个流程。
粘贴图片(11).png

有人会问,这个每个人都需要实现一遍吗,当然不是,这些逻辑都是与业务逻辑独立的,可以在框架层面上作封装,只需要扩展Backbone的View和Router即可,使用者完全无感知。

感想

最近看了React router,能够提供接入中间件实现转场,可见最根本的办法就是改造Backbone的Router和View,不要只限制每个View公用一个根元素,同时暴露页面进入前、页面进入后的回调给使用者,这才能进行更好的扩展。