Follow along → 2011.beercamp.com/colophon

Source available on GitHub → github.com/nclud/2011.beercamp.com

git clone https://github.com/nclud/2011.beercamp.com.git

BeerCamp 2011 Colophon

About BeerCamp

The idea

Zoom

  • Section 1
  • Section 2
  • Section 3

3D

  • Section 1
  • Section 2
  • Section 3

CSS scale transforms for zoom

/* level 2 -> scale = 1 / ( 3^1 ) = 1/3 */
#idea-examples .zoom .section2 {
  -webkit-transform: scale(0.333);
     -moz-transform: scale(0.333);
      -ms-transform: scale(0.333);
       -o-transform: scale(0.333);
          transform: scale(0.333);
}

/* level 3 -> scale = 1 / ( 3^2 ) = 1/9 */
#idea-examples .zoom .section3 {
  -webkit-transform: scale(0.111);
     -moz-transform: scale(0.111);
      -ms-transform: scale(0.111);
       -o-transform: scale(0.111);
          transform: scale(0.111);
}
/* scale = 3^1 = 3 */
#idea-examples.view2 .zoom ul {
  -webkit-transform: scale(3);
     -moz-transform: scale(3);
      -ms-transform: scale(3);
       -o-transform: scale(3);
          transform: scale(3);
}

/* scale = 3^2 = 9 */
#idea-examples.view3 .zoom ul {
  -webkit-transform: scale(9);
     -moz-transform: scale(9);
      -ms-transform: scale(9);
       -o-transform: scale(9);
          transform: scale(9);
}

Version 1: It works but…

Finding delight

Design Nobility Pyramid by Frank Chimero

Designspeaks Portland by frank-chimero, on Flickr

nikebetterworld.com by Ian Coyle & Duane King

Now then…

Implementing scroll CSS

See css/style.css

All content is position: fixed.

.csstransforms #wrap { position: fixed; }

Transparent proxy element is the only thing that scrolls.

.csstransforms.no-touch #scroll-proxy { height: 5400px; }

Starting JavaScript

See js/scripts.js

function Beercamper() {
  // properties
  this.scrolled = 0;
  this.currentLevel = 0;
  this.levels = 7;
  this.distance3d = 1000;
  this.levelGuide = {
    '#intro' : 0,
    '#featuring' : 1,
    '#sponsorz' : 2,
    '#flip-cup' : 3,
    '#teams' : 4
  };
  
  // which method should be used to return CSS transform styles
  this.getScrollTransform = Modernizr.csstransforms3d ? 
    this.getScroll3DTransform : this.getScroll2DTransform;
  
  // bind constructor to window.scroll event
  if ( Modernizr.csstransforms ) {
    window.addEventListener( 'scroll', this, false);
  }
  
}

// enables constructor to be used within event listener
// like obj.addEventListener( eventName, this, false )
Beercamper.prototype.handleEvent = function( event ) {
  if ( this[event.type] ) {
    this[event.type](event);
  }
};

handleEvent

Rather than bind inline or add anonymous functions to make our object call context preserved, we can simply add an handleEvent method to whatever object and pass it as EventListener.

Scrollin’ on dubs

  1. Any scrolling now triggers Beercamper.scroll
  2. Scroll position this.scrolled is calculated as decimal (0 to 1)
  3. this.scrolled value is used within transformScroll method
Beercamper.prototype.scroll = function( event ) {

  // normalize scroll value from 0 to 1
  this.scrolled = this.$window.scrollTop() / 
    ( this.$document.height() - this.$window.height() );

  this.transformScroll( this.scrolled );

  // change current selection on nav
  this.currentLevel = Math.round( this.scrolled * (this.levels-1) );
  
  if ( this.currentLevel !== this.previousLevel && this.$nav ) {
    this.$nav.find('.current').removeClass('current');
    if ( this.currentLevel < 5 ) {
      this.$nav.children().eq( this.currentLevel ).addClass('current');
    }
    this.previousLevel = this.currentLevel;
  }
  
};

// where the magic happens
// applies transform to content from position of scroll
Beercamper.prototype.transformScroll = function( scroll ) {
  this.$content.css( this.getScrollTransform( scroll ) );
};

transformScroll

Back in initial Beercamper constructor expression, getScrollTransform method was set to either getScroll3DTransform or getScroll2DTransform, depending on browser’s CSS 3D transform support.

Both methods return the transform style that will be applied to the content’s container – this.$content

Beercamper.prototype.getScroll2DTransform = function( scroll ) {
  // 2D scale is exponential
  var scale = Math.pow( 3, scroll * (this.levels - 1) ),
      prop = 'scale(' + scale + ')',
      style = {
        WebkitTransform : prop,
        MozTransform : prop,
        OTransform : prop,
        transform : prop
      };
  return style;
};

Beercamper.prototype.getScroll3DTransform = function( scroll ) {
  var z = ( scroll * (this.levels - 1) * this.distance3d ),
      style;
  
  style = {
    WebkitTransform : 'translate3d( 0, 0, ' + z + 'px )'
  };
  return style;
};

Initalize / doc ready

All that’s left is to initialize the constructor.

// BeerCamp '11 Global object
// initialize Beercamper
var BCXI = new Beercamper();

// check if browser is iOS -> iPhone / iPad / iPod Touch
BCXI.isIOS = !!('createTouch' in document);

$(function(){
  
  BCXI.$content = $('#content');
  BCXI.$nav = $('#nav');
  
  var $body = $('body'),
      iOSclass = BCXI.isIOS ? 'ios' : 'no-ios';

  $body.addClass( iOSclass );

});

Resolving iOS

Solution: Have iOS users use buttons for navigation

Disable proxy scroll in CSS

.csstransforms.no-touch .sign-up #scroll-proxy { height: 0; }

Binding click event

if ( Modernizr.csstransforms ) {
  $('.page-nav').each(function(){
    this.addEventListener( 'click', BCXI, false );
  });
}

Click action

Meanwhile, back in Beercamper

// handle click events
Beercamper.prototype.click = function( event ) {
  //  get scroll based on href of clicked nav item
  var hash = event.target.hash || event.target.parentNode.hash,
      targetLevel = this.levelGuide[ hash ],
      scroll = targetLevel / (this.levels-1);

  // turn on transitions and add event listeners for its end
  if ( Modernizr.csstransitions ) {
    this.$content.addClass('transitions-on');
    this.$content[0].addEventListener( 'webkitTransitionEnd', this, false );
    this.$content[0].addEventListener( 'oTransitionEnd', this, false );
    this.$content[0].addEventListener( 'transitionend', this, false );
  }

  // set scrollbar position
  // this will trigger window scroll event -> Beercamper.prototype.scroll
  this.$window.scrollTop( scroll * ( this.$document.height() - this.$window.height() ) );

  // iOS doesn't have scrollbar, so we have to manually trigger it
  if ( this.isIOS ) {
    this.transformScroll( scroll );
  }

  event.preventDefault();
  
};

Related CSS

.csstransforms #content.transitions-on {
  -webkit-transition: -webkit-transform 1s;
     -moz-transition:    -moz-transform 1s;
       -o-transition:      -o-transform 1s;
          transition:         transform 1s;
}

ending CSS transitions

Beercamper.prototype.webkitTransitionEnd = function( event ) {
  this.transitionEnded( event );
};

Beercamper.prototype.transitionend = function( event ) {
  this.transitionEnded( event );
};

Beercamper.prototype.oTransitionEnd = function( event ) {
  this.transitionEnded( event );
};

// disables transition after nav click
Beercamper.prototype.transitionEnded = function( event ) {
  this.$content.removeClass('transitions-on');
  this.$content[0].removeEventListener( 'webkitTransitionEnd', this, false );
  this.$content[0].removeEventListener( 'transitionend', this, false );
  this.$content[0].removeEventListener( 'oTransitionEnd', this, false );
};

Successes-ess-es

Failures

GitHub Pages

Also used for this Colophon

Other tidbits