ProximityEffect CSS3とjQueryでサムネイルをカッコよくイメージ拡大

本日はjQueryを使ってこざっぱりと綺麗にまとめられたサムネイルのプロキシミティ効果(接近効果)の作り方をご紹介したいと思います。これはマウスを画像にホバーすると自動拡大され、周辺画像もそれに合わせて適切なサイズに自動拡大されるというアイデアです。また同時に説明文も表示されるようになっています。

これは元々http://porscheeveryday.com/というサイトの効果をjQueryを使って実現出来るのかという読者からのリクエストに基づいており、この効果を私たちなりの方法で作り直したものです。

今回はJames Padolseyさんが作ったjQuery Proximity pluginを利用しています。

デモで使用しているイラストはFlorian Nicolleさんによるもので、Creative Commons Attribution-NonCommercial 3.0 Unported Licenseにライセンスされています。

この記事は、http://tympanus.net/codrops/ の許可を得て、翻訳しています。一部変更して翻訳している部分もある場合があります。オリジナルの記事はここよりご覧いただけます。

HTMLマークアップ

今回サムネイルにはULリストを使用し、各画像の説明文用にDIVタグを追加しています。(デモ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>

それではこちらにスタイル設定を加えましょう。

CSSスタイリング

ULリストはページ中央にセンタリングし、サムネイルの透明度が低いので「透けて見える」ような背景画像を追加します。

.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);
}

また疑似要素を使ってRGBA背景色を加えることにより、背景画像の色合いを増やしています。

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

リストアイテムは左側にフロートさせ、アンカーと画像のポジションは相対位置で設定しています。

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

サムネイルそれぞれは、幅100ピクセル、透明度0.2で設定します。

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

説明部分はデフォルトでは非表示にし、JavaScriptを使って表示されるようにします。

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

最後に説明部分のDIVタグの設定をしましょう。

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

これでスタイル設定は完成です!つぎにJavaScriptを見てみましょう。



JavaScript

まず大切なのが、サムネイル画像をホバーした際の説明部分のポジションとサイズを計算することです。勿論サムネイル画像がメインラッパーのどこに配置されているかによって、またサムネイル画像が最大化した際のサイズによって異なってくるので注意が必要です。例えばサムネイル画像が右端に近かった場合、説明部分はサムネイル画像の左側に配置したいわけです。

それからプロキシミティイベントを画像にバインドする必要があります。マウスの位置によって画像を上下にスケーリングするということです。画像が最大化されたら、そのz-indexを高いまま維持します。そうすれば説明文をきちんと表示させることが出来ます。

    // 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
};

デモ一覧

デモを3種類ご用意しましたので、どうぞご覧ください。

  1. デモ1: サイズ変更のみ
  2. デモ2: サイズ変更と説明文
  3. デモ3: 小さ目のサイズ変更と説明文