Update: I’ve released my reactive router and the (complete) transitioner class. Enjoy!
Note: dear readers from the future!: at the time of writing the released meteor version is 0.3.7; as Meteor is a rapidly changing framework, it is likely this post is out of date!
I’d like to briefly walk through the process that we used to implement page->page transitions in the League project [1].
Meteor apps are single page, client side Javascript applications, but this doesn’t mean you can’t have more than one screen. Smartphone users are more than familiar with the sense of ‘space’ created by having pages slide into one another. In this article I’ll describe how I used a Transitioner
class in meteor to achieve page transitions.
HTML Setup
Our main index.html
looks like:
<div class="container current_page {{current_page}}"> {{#if_with current_page}}{{> one_screen}}{{/if_with}} </div> <div class="container next_page {{next_page}}"> {{#if_with next_page}}{{> one_screen}}{{/if_with}} </div>
The one_screen
template as you may guess, draws one screen, much like:
<template name="one_screen"> {{#if_equals "teams" this}}{{> teams }}{{/if_equals}} {{#if_equals "players" this}}{{> players }}{{/if_equals}} ... </template>
The two variables current_page
and next_page
represent the two pages in the current transition. So if we are transitioning from the teams to the players page, we simply render the teams page into .current_page
and players into .next_page
and trigger a transition which animates .current_page
out to the left and .next_page
in from the right:
Here’s an action shot of it happening live:
Meteor Setup
So where do these variables current_page
and next_page
come from? The Transitioner
. Let’s start this story at the Router
. It’s actually a little bit complex, but the short story is that the router sets up a reactive variable representing the current page. Here’s some abbreviated code which uses deps-extensions:
AuthenticatedRouter = Backbone.Router.extend({ initialize: function() { Meteor.deps.add_reactive_variable(this, ‘page’, ‘loading’); }, goto: function(page) { this.page.set(page); } });
Route functions call goto()
to inform the rest of the application of which page should be visible. The Transitioner
basically wraps Router.page()
with a delay:
Transitioner = function(Router) { Meteor.deps.add_reactive_variable(this, ‘current_page’, Router.page()); Meteor.deps.add_reactive_variable(this, ‘next_page’); Meteor.deps.await(function() { return Router.page() != this.current_page(true); }, function() { this.transition_to(Router.page()); }); }; Transitioner.prototype.transition_to = function(page) { var self = this; self.next_page.set(page); // we defer so that the next page has rendered + is attached before we add the class Meteor.defer(function()) { $(‘body’).addClass(‘transitioning’) .one(‘transitionend’, function() { self.transition_end(); }); }); }; Transitioner.prototype.transition_end = function() { var self = this; self.current_page.set(self.next_page(true)); self.next_page.set(null); $(‘body’).removeClass(‘transitioning’); });
The magic of reactive programming huh? Simple [2]. I like it.
CSS Setup
This bit isn’t really meteor specific, but you might want to know how to achieve a sliding transition. It’s quite easy if you always want to go left to right:
.current_page, .next_page { position: relative; @include transition( all 500ms cubic-bezier(0.51, 0, 0.6, 1) 0ms); } .current_page { left: 0; } .next_page { left: 100%; } .transitioning { .current_page { left: -100%; } .next_page { left: 0; } }
Things are more complex when you want different page transitions to have different animations, check out _transition.scss
if you want to see how we achieved League’s other animations.
Let me know what you think about my techniques and if you have a better way to do anything!
- currently in alpha testing at http://beta.getleague.com; check it out if you like but please be kind [↩]
- actually it’s a bit more complex than this as some of my CSS transitions also require the
<body>
to have the current and next page set as classes. Also there are edge cases to deal with. But the core idea is there. [↩]
Hey. I realize this is kind of an old post, but would you happen to know the status of this with either 0.5.1 or 0.5.2? I’m getting undefined and null for current and next respectively, and am seeing no errors or warnings in either the client or server side consoles.
Hi Justin,
Are you using the transitioner smart package? As far as I know it works fine. If you are seeing a reproducible problem can you please open a bug against it on github (https://github.com/tmeasday/meteor-transitioner/issues)? Thanks!
Hi,
I tried to follow your example at github but it’s not working for me.
It always shows me the foo template even if I try localhost:3000/bar or some other url.
My JS:[code]if (Meteor.is_client) {
MyRouter = ReactiveRouter.extend({
routes : {
'' : 'foo',
'bar': 'bar'
},
foo : function() {
this.goto('foo');
},
bar: function() {
this.goto('bar');
}
})
Router = new MyRouter();
Meteor.startup(function() {
Backbone.history.start();
})
}[/code]
And template [code]
shopTest
{{{render currentPage}}}
Template FOOOO
Template BAAR
[/code]
Hi Chris,
Maybe open up a bug on github? It’s a bit hard to read here on WP, code formatting isn’t really up to scratch.
Thanks for sharing these useful bits. Just implemented it in my project.