Monday, September 17, 2012

Thumbnail Proximity Effect with JQuery and CSS3

Thumbnail Proximity Effect with JQuery and CSS3

Today we want to show you how to create a neat thumbnail proximity effect with jQuery. The idea is to scale thumbnails when hovering over them and also scale their neighbouring thumbnails proportionally to their distance. We’ll also make a description appear.

This is based on a request we once got from a reader who asked if the effect on Porsche Everyday could be achieved with jQuery. This is our attempt to recreate the basics of that effect.

We will be using the jQuery Proximity plugin by James Padolsey.

The illustations in the demos are by Florian Nicolle and they are licensed under the Creative Commons Attribution-NonCommercial 3.0 Unported License.

The Markup

We’ll use an unordered list for the thumbnails and add a division for the description of each image (for this tutorial, we will be going through demo 2):

<ul id="pe-thumbs" class="pe-thumbs">
    <li>
        <a href="#">
            <img src="images/thumbs/1.jpg" />
            <div class="pe-description">
                <h3>Time</h3>
                <p>Since time, and his predestinated end</p>
            </div></a>
    </li>
    <li><!-- ... --></li>
</ul>

The CSS

The unordered list will be centered on the page and we’ll add a background image to "shine through" the thumbnails that will have a low opacity:

.pe-thumbs{
    width: 900px; height: 400px;
    margin: 20px auto;
    position: relative;
    background: transparent url(../images/3.jpg) top center;
    border: 5px solid #fff;
    box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}

We’ll also add a bit of tint to the background image by adding a pseudo element with a rgba background color:

.pe-thumbs:before {
    content: "";
    display: block;
    position: absolute;
    top: 0; left: 0;
    width: 100%; height: 100%;
    background: rgba(255,102,0,0.2);
}

The list items will float left and we’ll set the position of the anchors and images to relative:

.pe-thumbs li{
    float: left;
    position: relative;
}
.pe-thumbs li a, .pe-thumbs li a img{
    display: block;
    position: relative;
}

Each thumbnail will have an initial width of 100 pixels and an opacity of 0.2:

.pe-thumbs li a img{
    width: 100px;
    opacity: 0.2;
}

The discription will be hidden by default and then we will make it appear using JavaScript:

.pe-thumbs li a div.pe-description{
    width: 200px; height: 100px;
    background: rgba(0,0,0,0.8);
    position: absolute;
    top: 0; left: -200px;
    color: white;
    display: none;
    z-index: 1001;
    text-align: left;
}

And finally, let’s style the content of the description division:

.pe-description h3{
    margin: 0; padding: 10px 10px 0px 10px;
    line-height: 20px;
    font-family: 'BebasNeueRegular', 'Arial Narrow', Arial, sans-serif;
    font-size: 22px;
}
.pe-description p{
    margin: 10px; padding: 10px 0;
    font-size: 11px; font-style: italic;
    border-top: 1px solid rgba(255,255,255,0.3);
}

The Javascript

The main idea is to first of all calculate the description container size and position when we hover over a thumbnail. This depends of course on the maximum scale of the thumbnail and where the thumbnail is located in the main wrapper. For example, when the thumbnail is close to the right edge, we’ll want to make the description box appear on the left side of the thumbnail.

Then we’ll need to bind the proximity event to the images. The idea is to scale the images up or down according to the position of the mouse. Once the image scales to the maximum value, we set its z-index very high, so that it stays on top, and we show the respective description:

// list of thumbs
var $list       = $('#pe-thumbs'),
    // list's width and offset left.
    // this will be used to know the position of the description container
    listW       = $list.width(),
    listL       = $list.offset().left,
    // the images
    $elems      = $list.find('img'),
    // the description containers
    $descrp     = $list.find('div.pe-description'),
    // maxScale : maximum scale value the image will have
    // minOpacity / maxOpacity : minimum (set in the CSS) and maximum values for the image's opacity
    settings    = {
        maxScale    : 1.3,
        maxOpacity  : 0.9,
        minOpacity  : Number( $elems.css('opacity') )
    },
    init        = function() {
         
        // minScale will be set in the CSS
        settings.minScale = _getScaleVal() || 1;
        // preload the images (thumbs)
        _loadImages( function() {
             
            _calcDescrp();
            _initEvents();
         
        });
     
    },
    // Get Value of CSS Scale through JavaScript:
    // http://css-tricks.com/get-value-of-css-rotation-through-javascript/
    _getScaleVal= function() {
     
        var st = window.getComputedStyle($elems.get(0), null),
            tr = st.getPropertyValue("-webkit-transform") ||
                 st.getPropertyValue("-moz-transform") ||
                 st.getPropertyValue("-ms-transform") ||
                 st.getPropertyValue("-o-transform") ||
                 st.getPropertyValue("transform") ||
                 "fail...";

        if( tr !== 'none' ) {    

            var values = tr.split('(')[1].split(')')[0].split(','),
                a = values[0],
                b = values[1],
                c = values[2],
                d = values[3];

            return Math.sqrt( a * a + b * b );
         
        }
         
    },
    // calculates the style values for the description containers,
    // based on the settings variable
    _calcDescrp = function() {
         
        $descrp.each( function(i) {
         
            var $el     = $(this),
                $img    = $el.prev(),
                img_w   = $img.width(),
                img_h   = $img.height(),
                img_n_w = settings.maxScale * img_w,
                img_n_h = settings.maxScale * img_h,
                space_t = ( img_n_h - img_h ) / 2,
                space_l = ( img_n_w - img_w ) / 2;
             
            $el.data( 'space_l', space_l ).css({
                height  : settings.maxScale * $el.height(),
                top     : -space_t,
                left    : img_n_w - space_l
            });
         
        });
     
    },
    _initEvents = function() {
     
        $elems.on('proximity.Photo', { max: 80, throttle: 10, fireOutOfBounds : true }, function(event, proximity, distance) {
             
            var $el         = $(this),
                $li         = $el.closest('li'),
                $desc       = $el.next(),
                scaleVal    = proximity * ( settings.maxScale - settings.minScale ) + settings.minScale,
                scaleExp    = 'scale(' + scaleVal + ')';

            // change the z-index of the element once 
            // it reaches the maximum scale value
            // also, show the description container
            if( scaleVal === settings.maxScale ) {
                 
                $li.css( 'z-index', 1000 );
                 
                if( $desc.offset().left + $desc.width() > listL + listW ) {
                     
                    $desc.css( 'left', -$desc.width() - $desc.data( 'space_l' ) );
                 
                }
                 
                $desc.fadeIn( 800 );
                 
            }   
            else {
                 
                $li.css( 'z-index', 1 );
             
                $desc.stop(true,true).hide();
             
            }   
             
            $el.css({
                '-webkit-transform' : scaleExp,
                '-moz-transform'    : scaleExp,
                '-o-transform'      : scaleExp,
                '-ms-transform'     : scaleExp,
                'transform'         : scaleExp,
                'opacity'           : ( proximity * ( settings.maxOpacity - settings.minOpacity ) + settings.minOpacity )
            });

        });
     
    },
    _loadImages = function( callback ) {
         
        var loaded  = 0,
            total   = $elems.length;
         
        $elems.each( function(i) {
             
            var $el = $(this);
             
            $('<img>').load( function() {
                 
                ++loaded;
                if( loaded === total )
                    callback.call();
                 
            }).attr( 'src', $el.attr('src') );
         
        });
     
    };

return {
    init    : init
};

View Demo

via Codrops - by