Tuesday, July 30, 2013

easy slideshow with YUI transitions

Using CSS transitions to animate the opacity of images is a great way to implement a web slideshow, but implementing an animation-based fallback for old browsers is a pain. Fortunately - yui includes a transition module that takes care of the fallback magic; which made it easy for me to code up an Android and iOS-friendly HTML replacement for a Flash .swf banner in a project I'm helping with.

The banner's markup leverages the absolute position in a relative position container trick:

div.banner {
    position:relative;
    height:230px;
}

div.banner img {
    position: absolute;
    opacity: 0;
}

div.banner img.logo { /* overlay logo on banner */
    opacity:1;
    bottom:25px;
    right:0;
}

<div id="banner" data-anim-period-secs="5" class="yui3-u-1 banner">
    <img src="/myrwa/resources/img/banner/lichen.jpg"/>
    <img src="/myrwa/resources/img/banner/Herringrun.jpg"/>
    <img src="/myrwa/resources/img/banner/TuftsSailingTeam.jpg"/>
    <img src="/myrwa/resources/img/banner/canoe.jpg"/>
    
    <img id="logo" class="logo" src="/myrwa/resources/img/myRWA_logo_2010.gif" />
</div>

The banner's javascript module implements a simple yui view that runs a setinterval loop (Y.later wraps setinterval) that applies an opacity transition to make the current image in the banner's slideshow opaque, and the last image transparent.

/*
 * Copyright 2013 http://mysticriver.org
 *
 * The contents of this file are freely available subject to the 
 * terms of the Apache 2.0 open source license.
 */


/**
 * see http://yuiblog.com/blog/2007/06/12/module-pattern/
 * YUI doc comments: http://developer.yahoo.com/yui/yuidoc/
 * YUI extension mechanism: http://developer.yahoo.com/yui/3/yui/#yuiadd
 *
 * @module myrwa-banner
 * @namespace myrwa
 */
YUI.add( 'myrwa-banner', function(Y) {
    Y.namespace('myrwa');

    function log( msg, level ) {
        level = level || 2;
        Y.log( msg, level, "myrwa/banner.js" );
    }
    
    /**
     * View abstraction for the header banner which animates with
     * transitions between a set of gallery images.  
     * Initialize with container
     * selector for markup initialized with banner images
     * ready for progressive enhancement.
     * 
     * @class BannerView
     */
    var BannerView = Y.Base.create( 'bannerView', Y.View, [], 
        {
            initializer:function(config) {
            },
                    
            render:function() {
                // do not setup the animation than once
                if ( this.rendered ) return;
                var container = this.get( "container" );
                var logoNode = container.one( "img.logo" );
                var imageNodes = container.all( "img" 
                         ).filter( function(img) { return ! Y.Node(img).hasClass( "logo" ); } );
                
                Y.assert( "Found image nodes", imageNodes.size() > 0 );
                
                // initialize the banner to show the first image
                imageNodes.each( function(n) { n.setStyle( "opacity", 0 ); } );
                imageNodes.item(0).setStyle( "opacity", 1 );
                
                if ( imageNodes.size() == 1 ) return;  // no need to animate
                
                var animPeriod = this.get( "animPeriodSecs" );
                if ( animPeriod < 2 ) {
                    // check for an attribute on the container if period not set in js code
                    var tmp = parseInt( container.getAttribute( "data-anim-period-secs" ) );
                    if ( tmp ) { animPeriod = tmp; }
                }
                if ( animPeriod < 2 ) animPeriod = 2;
                
                var currentImageIndex = 0;
                log( "Launching banner animation ..." );
                //
                // Every animPeriod seconds ease the current node out, and the new node in
                //
                Y.later( animPeriod * 1000, this, 
                    function(){
                        var nextImageIndex = (currentImageIndex + 1) % imageNodes.size();
                        var inNode = imageNodes.item( nextImageIndex );
                        var outNode = imageNodes.item( currentImageIndex );
                        //log( "banner transition from " + currentImageIndex + " to " + nextImageIndex );

                        inNode.setStyle( "opacity", 0 );
                        outNode.setStyle( "opacity", 1 );
                        inNode.show();
                        outNode.show();

                        outNode.transition( 
                                {
                                 easing: 'ease-out',
                                 duration: 0.75, // seconds
                                 opacity: 0
                                 }, 
                             function() { outNode.hide(); } 
                         );
                         inNode.transition(
                                 {
                                  easing: 'ease-in',
                                  duration: 0.75,
                                  opacity: 1
                                 }
                             );
                         currentImageIndex = nextImageIndex;
                    }, 
                    [], true
                );

                this.rendered = true;
            }
        }, 
        {
            ATTRS: {
                animPeriodSecs: {
                    value:0
                }
            }
        } 
    );

    //---------------------------------------

    Y.myrwa.banner = {
        BannerView:BannerView
    };
}, '0.1.1' /* module version */, {
    requires: [ 'node', 'test', 'transition', 'view']
});

A page bootstraps the banner animation with code like this:

YUI().use('myrwa-banner', 'test', function(Y){
 
    Y.log( "Hello, World!", 2, "myrwa/main.js" );
    var banner = new Y.myrwa.banner.BannerView( 
            {
                container:"#banner"
            }
        );
    banner.render();
});

Anyway - I was pretty happy with how that all came together. This little banner slideshow lacks the nice controls in bootstrap's slideshow, but it was good enough for what I needed, and I avoided pulling in bootstrap's dependencies (jquery, whatever). The code is available on github, and a page I used for testing is also online for now.

Post a Comment