Introduction

Since Famo.us v0.2.0, some new tutorials have been added to the Famo.us University . The 1st available project is a Polaroid tutorial which teaches you how to create your own app and widget with Famo.us . Just play this little video to check how incredible the animations are with plain simple HTML5 technologies. Note that, this example is completely responsive and I've tested it on smartphones, tablets and desktops.

This example project is not just beautiful. It works very well in iOS, Chrome on Android, Chrome, Safari and Opera. There are little flickers on Firefox but it seems more due to the browser than to the framework. Though, it is completely useable. As ever, the stock Android browser is definitely a knightmare. Meaning that, if you plan on encapsulating your WebApp in a native browser, Apache Cordova is the way to go for iOS but you will need to encapsulate Chromium if you plan on shipping your WebApp for Android. This can be achieve with projects such as Crosswalk , for instance.





Though, the tutorial is incredibly nice and goes smoothly over every difficulties that you may encounter (one of the best tutorial, that I've seen so far), I didn't choose the same way as described. Actually, JavaScript's inheritance model is such a pain to write, that I prefer avoiding it as much as I can. Thus, I've recreated it using another set of tools and it runs as smoothly as the original:

CoffeeScript as a replacement of JavaScript.

Jade as a replacement of HTML.

Stylus as a replacement of CSS.

Meteor as a replacement of LiveReload, Express and Grunt. The result is a very small set of code that produces the same WebApp in a fraction of the necessary code and installation steps.



Configuring your Meteor project

meteor create Polaroid cd Polaroid

mkdir -p client/lib client/models client/startup client/stylesheets client/views lib packages public/img

rm -rf Polaroid.*

meteor add coffeescript meteor add stylus

mrt add jade mrt add famono

Get the unique required asset from Famo.us

public/img/camera.png

Create your style file

client/stylesheets/app.styl

@import 'nib' html background: #404040 body -webkit-touch-callout: none user-select: none font-family: 'AvenirNext-Medium'

Create your HTML file with Jade

client/index.jade

head title Famo.us Polaroid body +index template(name='index')

template

Create some namespaces

One is dedicated to the WebApp in client/lib/polaroid.coffee : # Declare Polaroid namespace window.Polaroid ?= {}

: The other one is dedicated to Famo.us in client/lib/famous.coffee : # Declare Famo.us namespace window.Famous ?= {}

lib

The model, a Picasa album

client/models/slidedata.coffee

Polaroid.SlideData = userId: "109813050055185479846" albumId: "6013105701911614529" picasaUrl: "https://picasaweb.google.com/data/feed/api/user/" queryParams: "?alt=json&hl=en_US&access=visible&fields=entry(id,media:group(media:content,media:description,media:keywords,media:title))" defaultImage: "https://lh4.googleusercontent.com/-HbYp2q1BZfQ/U3LXxmWoy7I/AAAAAAAAAJk/VqI5bGooDaA/s1178-no/1.jpg" getUrl: -> @picasaUrl + @userId + "/albumid/" + @albumId + @queryParams parse: (data) -> urls = [] data = JSON.parse(data) entries = data.feed.entry i = 0 while i < entries.length media = entries[i].media$group urls.push media.media$content[0].url i++ urls

Requiring the Famo.us libraries

client/startup/famous.coffee

# Import famous.css require 'famous/core/famous' # Adds the famo.us dependencies require 'famous-polyfills' # Wait for document ready $(document).ready -> # Load Famo.us libraries Famous.Engine = require 'famous/core/Engine' Famous.View = require 'famous/core/View' Famous.Transform = require 'famous/core/Transform' Famous.Surface = require 'famous/core/Surface' Famous.StateModifier = require 'famous/modifiers/StateModifier' Famous.Timer = require 'famous/utilities/Timer' Famous.ImageSurface = require 'famous/surfaces/ImageSurface' Famous.ContainerSurface = require 'famous/surfaces/ContainerSurface' Famous.Lightbox = require 'famous/views/Lightbox' Famous.Utility = require 'famous/utilities/Utility' Famous.Easing = require 'famous/transitions/Easing' Famous.ContainerSurface = require 'famous/surfaces/ContainerSurface' Famous.Transitionable = require 'famous/transitions/Transitionable' Famous.SpringTransition = require 'famous/transitions/SpringTransition' # Register transitions Famous.Transitionable.registerMethod 'spring', Famous.SpringTransition # Create main context Polaroid.mainCtx = Famous.Engine.createContext()

Instantiate the main template

client/index.coffee

Template.index.rendered = -> # Get request to Picasa API Famous.Utility.loadURL Polaroid.SlideData.getUrl(), (data) -> data = Polaroid.SlideData.parse data # Instantiate the AppView with our URL's data Polaroid.appView = new Polaroid.AppView data: data Polaroid.mainCtx.add Polaroid.appView

Creating the views

client/views/appview.coffee $(document).ready -> class Polaroid.AppView extends Famous.View DEFAULT_OPTIONS: data: undefined cameraWidth: 0.6 * window.innerHeight constructor: (@options) -> @DEFAULT_OPTIONS.slideWidth = 0.8 * @DEFAULT_OPTIONS.cameraWidth @DEFAULT_OPTIONS.slideHeight = @DEFAULT_OPTIONS.slideWidth + 40 @DEFAULT_OPTIONS.slidePosition = 0.77 * @DEFAULT_OPTIONS.cameraWidth @constructor.DEFAULT_OPTIONS = @DEFAULT_OPTIONS super @options @createCamera() @createSlideshow() createCamera: -> camera = new Famous.ImageSurface size: [@options.cameraWidth, true] content: 'img/camera.png' properties: width: '100%' cameraModifier = new Famous.StateModifier origin: [0.5, 0] align: [0.5, 0] transform: Famous.Transform.behind @add(cameraModifier).add camera createSlideshow: -> slideshowView = new Polaroid.SlideshowView size: [@options.slideWidth, @options.slideHeight] data: @options.data slideshowModifier = new Famous.StateModifier origin: [0.5, 0] align: [0.5, 0] transform: Famous.Transform.translate 0, @options.slidePosition, 0 slideshowContainer = new Famous.ContainerSurface properties: overflow: 'hidden' @add(slideshowModifier).add slideshowContainer slideshowContainer.add slideshowView slideshowContainer.context.setPerspective 1000

client/views/slideshowview.coffee $(document).ready -> class Polaroid.SlideshowView extends Famous.View DEFAULT_OPTIONS: size: [450, 500] data: undefined lightboxOpts: inOpacity: 1 outOpacity: 0 inOrigin: [0, 0] outOrigin: [0, 0] showOrigin: [0, 0] inTransform: Famous.Transform.thenMove Famous.Transform.rotateX(0.9), [0, -300, -300] outTransform: Famous.Transform.thenMove Famous.Transform.rotateZ(0.7), [0, window.innerHeight, -1000] inTransition: duration: 500, curve: Famous.Easing.outBack outTransition: duration: 350, curve: Famous.Easing.inQuad constructor: (@options) -> @constructor.DEFAULT_OPTIONS = @DEFAULT_OPTIONS super @options @rootModifier = new Famous.StateModifier size: @options.size origin: [0.5, 0] align: [0.5, 0] @mainNode = @add @rootModifier @createLightbox() @createSlides() createLightbox: -> @lightbox = new Famous.Lightbox @options.lightboxOpts @mainNode.add @lightbox createSlides: => @slides = [] @currentIndex = 0 console.log @options.data for url in @options.data slide = new Polaroid.SlideView size: @options.size photoUrl: url @slides.push slide slide.on 'click', @showNexSlide @showCurrentSlide() showCurrentSlide: -> @ready = false slide = @slides[@currentIndex] @lightbox.show slide, => @ready = true slide.fadeIn() showNexSlide: => return if @ready isnt true @currentIndex++ if @currentIndex is @slides.length @currentIndex = 0 @showCurrentSlide()

client/views/slideview.coffee $(document).ready -> class Polaroid.SlideView extends Famous.View DEFAULT_OPTIONS: size: [400, 450] filmBorder: 15 photoBorder: 3 photoUrl: Polaroid.SlideData.defaultImage angle: -0.5 constructor: (@options) -> @constructor.DEFAULT_OPTIONS = @DEFAULT_OPTIONS super @options @rootModifier = new Famous.StateModifier size: @options.size @mainNode = @add @rootModifier @createBackground() @createFilm() @createPhoto() createBackground: -> background = new Famous.Surface properties: backgroundColor: '#fffff5' boxShadow: '0 10px 20px -5px rgba(0, 0, 0, 0.5)' cursor: 'pointer' @mainNode.add background background.on 'click', => @_eventOutput.emit 'click' createFilm: -> @options.filmSize = @options.size[0] - 2 * @options.filmBorder film = new Famous.Surface size: [@options.filmSize, @options.filmSize] properties: backgroundColor: '#222' zIndex: 1 # Make surface invisible to pointer events pointerEvents: 'none' filmModifier = new Famous.StateModifier origin: [0.5, 0] align: [0.5, 0] transform: Famous.Transform.translate 0, @options.filmBorder, 1 @mainNode .add filmModifier .add film createPhoto: -> photoSize = @options.filmSize - 2 * @options.photoBorder photo = new Famous.ImageSurface size: [photoSize, photoSize] content: @options.photoUrl properties: zIndex: 2 # Make surface invisible to pointer events pointerEvents: 'none' @photoModifier = new Famous.StateModifier origin: [0.5, 0] align: [0.5, 0] transform: Famous.Transform.translate 0, @options.filmBorder + @options.photoBorder, 2 opacity: 0.01 @mainNode .add @photoModifier .add photo fadeIn: => @photoModifier.setOpacity 1, {duration: 1500, curve: 'easeIn'} @shake() shake: -> @rootModifier.halt() @rootModifier.setTransform Famous.Transform.rotateX(@options.angle), {duration: 200, curve: 'easeOut'} @rootModifier.setTransform Famous.Transform.identity, {method: 'spring', period: 600, dampingRatio: 0.15}

Further words

Note that the following tutorial is not a replacement of the one from the Famo.us University . It is a complement to show how this framework is easily integrable with other powerful HTML5 technologies.Create your Meteor project with the dead simple following command:Now create a common fullstack JS directory structure only targeted for a client WebApp without server side integration:Remove the automatically created files:Add the following package with Meteor And this one with Meteorite Easy as pie.This example WebApp needs a simplethat you'll find in the Zip file that Famo.us provides in their download section The style fileis kept as its minimum as most of the CSS rules are handled by Famo.us As before for the style file, the main HTML fileis kept as its minimum as most of the tags are handled by Famo.us That's it, a simpleloaded by Meteor I like to isolate my code from the code that I import. Thus, I create 2 namespaces in 2 separate files.Thedirectory is used as it is loaded first by Meteor With this example, we do not leverage the power of the full JS stack that Meteor provides. We only use its features of live reloading the code and its easy to use build capability. The model is the same as the one provided in the Famo.us Zip file that you've downloaded except that it is created as a CoffeeScript file namedA simple dictionary with 2 methods.I simply load all the Famo.us libraries in a single location. This drastically reduces the amount of code. I use the same fileto load the polyfills and to create the Famo.us's singleton so that if I enhance this WebApp with multiple page loaded with a router, there will be no additional loadings or instantiations.The content of the main template is set under thethat goes along with our Jade file:The rest of this tutorial is pretty close to the one from the Famo.us University except that it uses the object model provided by CoffeeScript instead of the one from JavaScript. You should fill this 3 files while following the tutorial from the Famo.us University so that you get the nice explanations that they provided us.The views are composed of 3 files:My integration is not as proper as I would like it to be. Each class declaration relies on a ready event that I did not succeed in removing. Identically, my class constructors call their parent's one explicitly. Feel free to post some comments if you have a better integration.