jquery swapImageGallery（表示領域固定）

スワップイメージ（画像切替）の画像ギャラリーです。
（hoverで動作、表示領域width/height固定、自動画像リサイズ、ライトボックス対応）
サムネイル画像リンクをマウスオーバーするとリンクパスを取得し、メイン画像を差し替えます（メインとサムネイルは共通画像ファイルの使用を想定）。
画像アスペクト比を保ちながら指定画像エリアの縦横サイズに収まるようブラウザ表示サイズを自動リサイズします（短辺サイズ計算はブラウザ依存）。
サムネイルクリックでlightbox表示を想定。lightbox無しでも画像切り替えは動作します。

jquery1.9.1/1.11.1で動作を確認。

```ruby:jquery.swapImageGallery
(function($) {
  $.fn.swapImageGallery = function(options) {
    return this.each(function() {

      var defaults = {
        maxWidth: 500,           // メイン画像の最大横幅
        maxHeight: 500,          // メイン画像の最大縦幅
        thumbWidth: 150,         // サムネイル画像の最大横幅
        thumbHeight: 150,        // サムネイル画像の最大縦幅
        targetImg: '.mainimage', // メイン画像ラッパーid/class
        list: '.thumbnails',     // サムネイル画像リスト
        mask: 'mask',            // フェード用マスクclass名
        fadeTime: 'fast'         // フェード In/Out 時間：slow/normal/fast/数値（ミリ秒）
      };
      var settings = $.extend(defaults, options);
      var elm = $(this).find(settings.targetImg);
      var listElm = $(this).find(settings.list);
      var newImg = new Image();

      //縦横比・サイズ計算
      var gcm = gcd(settings.maxWidth, settings.maxHeight);
      var gcmWidth = (settings.maxWidth/gcm);
      var gcmHeight = (settings.maxHeight/gcm);
      var gcmThumb = gcd(settings.thumbWidth, settings.thumbHeight);
      var gcmThumbWidth = (settings.thumbWidth/gcmThumb);
      var gcmThumbHeight = (settings.thumbHeight/gcmThumb);

      // ロード時
      $(window).load(function() {
        elm.children('img').css('visibility','hidden');
        listElm.find('img').each(function() {
          $(this).css('visibility','hidden').before($('<div class=\"' + settings.mask +'\"></div>'));
        });

        // メイン画像リサイズ
        elm.find('img').each(function() {
          elm.append($('<div class=\"'+settings.mask+'\"></div>').fadeOut());//フェード用マスク要素
          var changeSrc  = $(this).attr('src');
          changeImg(changeSrc);
          $(this).css('visibility','visible');
        });

        // サムネイル画像リサイズ
        listElm.find('img').each(function() {
          var w = ($(this).width()) *gcmThumbHeight;
          var h = ($(this).height()) *gcmThumbWidth;
          if (w >= h) {
            $(this).removeAttr('height').attr({width : settings.thumbWidth});
          } else {
            $(this).removeAttr('width').attr({height : settings.thumbHeight});
          }
          $(this).css('visibility','visible').prev('.'+settings.mask).fadeOut('normal');
        });
      });

      // サムネイル マウスオーバー時
      listElm.find('a').hover(function() {
        var targetSrc  = elm.find('img').attr('src').match(".+/(.+?)$")[1];
        var changeSrc  = $(this).attr("href");
        if(targetSrc!=(changeSrc.match(".+/(.+?)$")[1])){
          changeImg(changeSrc);
        }
        return false;
      });

      // メイン画像差し替え
      function changeImg(changeSrc) {
        newImg.onload = function() { // 画像がロードされたら実行
          var w = newImg.width *gcmHeight;
          var h = newImg.height *gcmWidth;

          if (w >= h) { // 横長の画像の場合
            elm.find('.'+settings.mask).fadeIn(
              settings.fadeTime,
              function() {
                elm.find('img').removeAttr('height').attr({
                  src : newImg.src,
                  width : settings.maxWidth
                });
                $(this).stop().fadeOut(settings.fadeTime);
              }
            );
          } else { // 縦長の画像の場合
            elm.find('.'+settings.mask).fadeIn(
              settings.fadeTime,
              function() {
                elm.find('img').removeAttr('width').attr({
                  src : newImg.src,
                  height : settings.maxHeight
                });
                $(this).stop().fadeOut(settings.fadeTime);
              }
            );
          }
        };
        newImg.src = changeSrc;
      }

      // 最大公約数計算
      function gcd(x, y) {
        if (y === 0) return x;
        else return gcd(y, x % y);
      }

    });
  };
})(jQuery);

jQuery(function($) {
  $('.js-swapImageGallery').swapImageGallery({
    maxWidth: 400,
    maxHeight: 400,
    thumbWidth: 120,
    thumbHeight: 120,
  });
});
```

```ruby:html
※img要素にwidth/height指定しない

<div class="js-swapImageGallery swapimagegallery customstyle">
  <div class="mainimage"><span><img src="img01.jpg" alt="メイン画像"></span></div>
  <ul class="thumbnails">
    <li><span><a href="img01.jpg" rel="lightbox"><img src="img01.jpg" alt="サムネイル"></a></span></li>
    <li><span><a href="img02.jpg" rel="lightbox"><img src="img02.jpg" alt="サムネイル"></a></span></li>
    <li><span><a href="img03.jpg" rel="lightbox"><img src="img03.jpg" alt="サムネイル"></a></span></li>
  </ul>
</div>
```

```ruby:css
/* 基本css
   ※整形に最低限のCSSを用意しましたが、無くても動作に影響はありませんので任意のスタイルをあててください。
*/
.swapimagegallery {
  width: 502px; /* IE7縦中央揃にborderが必要なため、左右の太さ合計値を幅に加算する */
  text-align: center;
}

/*-- main image --*/
.swapimagegallery .mainimage {
  position: relative;
  overflow: hidden;
  width: 100%;
  height: 502px;
  background-color: #eeeeee;
}

.swapimagegallery .mainimage span {
  display: table-cell;
  width: 500px;
  height: 500px;
  border: 1px solid #ffffff; /* IE7縦中央揃に必須 */
  line-height: 500px;
  vertical-align: middle;
}

/*-- image mask --*/
.swapimagegallery .mainimage .mask {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 100;
  width: 100%;
  height: 100%;
  background: #ffffff url(loading.gif) no-repeat center center;
}

.swapimagegallery .thumbnails .mask {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 100;
  width: 100%;
  height: 100%;
  background: #ffffff url(loading.gif) no-repeat center center;
}

/*-- thumbnails --*/
.swapimagegallery .thumbnails {
  font-size: 0;
  line-height: 0;
}
.swapimagegallery .thumbnails li {
  position: relative;
  display: inline;
  vertical-align: top;
}

.swapimagegallery .thumbnails li span {
  overflow: hidden;
  display: inline-block;
  width: 152px;
  height: 152px;
  margin: 4px;
  background-color: #eeeeee;
}

.swapimagegallery .thumbnails a {
  position: relative;
  display: table-cell;
  width: 150px;
  height: 150px;
  line-height: 150px;
  border: 1px solid #ffffff; /*IE7縦中央揃に必須*/
  text-align: center;
  vertical-align: middle;
}

.swapimagegallery .thumbnails a:hover {
  border: 1px solid #ff0000;
}

/*-- custom style --*/
.customstyle {
  width: 402px;
}

.customstyle .mainimage {
  width: 402px;
  height: 402px;
  background-color: #eeeeee;
}

.customstyle .mainimage span {
  width: 400px;
  height: 400px;
  line-height: 400px;
}

.customstyle .thumbnails li span {
  width: 122px;
  height: 122px;
  margin: 2px;
  background-color: #eeeeee;
}

.customstyle .thumbnails a {
  width: 120px;
  height: 120px;
  line-height: 120px;
  border: 1px solid #ffffff;
}

.customstyle .thumbnails a:hover {
  border: 1px solid #ff6600;
}

.customstyle .mainimage .mask {
  background: #ffffff url(loading.gif) no-repeat center center;
}

.customstyle .thumbnails .mask {
  background: #ffffff url(loading.gif) no-repeat center center;
}
```

IE7対応時代に書いたコードなので、モダンブラウザ向けに使うには無駄なコードが多いです。
その後に多少改修を加えてIE7での動作は確認してません。IE8では動くと思います。
当初はIE7で表示エリア内縦中央揃えも処理させてたけど改修の際CSSで対応させることに。
しかし、コレ長辺を最大サイズに合わせるだけならアスペクト比計算する必要無いような。。。