Googleさんの画像検索で使われているサムネイルをクリックすると
すぐ下にアコーディオン形式で詳細エリアが広がるUI。
このUI動作を実装するには「SuperBox」というjQueryプラグイン等が有名だったりしますが
画面仕様等ちょっと理想と違う構成のところがあったり動作が少し重かったりしたので
実験がてらjQueryを使ってGoogle画像検索のUIを試しに作ってみたので紹介してみたいと思います。
jQueryでGoogle画像検索の様なUIを実装する実験(レスポンシブ対応)
レスポンシブ動作などブラウザ枠を目いっぱいで表示するタイプなので
まずは別枠表示で動作サンプルから。。
jQueryでGoogle画像検索の様なUIを実装する実験(レスポンシブ対応)
サンプルでは25枚の画像を一覧で並べて
ウィンドウサイズに合わせて、
1行に並べられる画像数等がリキッドレイアウトされます。
サムネイルをクリックすると
すぐ下に詳細エリアが展開されます。
詳細エリアにはNEXT/BACKボタンでの切り替えの他に
閉じるボタンを配置してあります。
簡単な動作仕様については以上になります。
全体構成についてまずはHTMLから。
(全て記載すると長いので一部割愛します)
◆HTML <ul class="listCover"> <li class="listItem"> <img src="img/photo1.jpg" alt=""> <div class="selfRep"> <div class="selfRepInner"> <div class="secLeft"><img src="img/photo1.jpg" alt=""></div> <div class="secRight"><p>エリア【1】のダミーテキスト。エリア【1】のダミーテキスト。エリア【1】のダミーテキスト。エリア【1】のダミーテキスト。エリア【1】のダミーテキスト。エリア【1】のダミーテキスト。</p></div> </div><!--/.selfRepInner--> </div><!--/.selfRep--> </li><!-- --><li class="listItem"> <img src="img/photo2.jpg" alt=""> <div class="selfRep"> <div class="selfRepInner"> <div class="secLeft"><img src="img/photo2.jpg" alt=""></div> <div class="secRight"><p>エリア【2】のダミーテキスト。エリア【2】のダミーテキスト。エリア【2】のダミーテキスト。エリア【2】のダミーテキスト。</p></div> </div><!--/.selfRepInner--> </div><!--/.selfRep--> </li><!-- --><li class="listItem"> <img src="img/photo3.jpg" alt=""> <div class="selfRep"> <div class="selfRepInner"> <div class="secLeft"><img src="img/photo3.jpg" alt=""></div> <div class="secRight"><p>エリア【3】のダミーテキスト。エリア【3】のダミーテキスト。エリア【3】のダミーテキスト。エリア【3】のダミーテキスト。エリア【3】のダミーテキスト。エリア【3】のダミーテキスト。エリア【3】のダミーテキスト。</p></div> </div><!--/.selfRepInner--> </div><!--/.selfRep--> </li><!-- --><li class="listItem"> <img src="img/photo4.jpg" alt=""> <div class="selfRep"> <div class="selfRepInner"> <div class="secLeft"><img src="img/photo4.jpg" alt=""></div> <div class="secRight"><p>エリア【4】のダミーテキスト。エリア【4】のダミーテキスト。エリア【4】のダミーテキスト。エリア【4】のダミーテキスト。エリア【4】のダミーテキスト。エリア【4】のダミーテキスト。</p></div> </div><!--/.selfRepInner--> </div><!--/.selfRep--> </li><!-- --><li class="listItem"> <img src="img/photo5.jpg" alt=""> <div class="selfRep"> <div class="selfRepInner"> <div class="secLeft"><img src="img/photo5.jpg" alt=""></div> <div class="secRight"><p>エリア【5】のダミーテキスト。エリア【5】のダミーテキスト。エリア【5】のダミーテキスト。エリア【5】のダミーテキスト。エリア【5】のダミーテキスト。エリア【5】のダミーテキスト。エリア【5】のダミーテキスト。エリア【5】のダミーテキスト。</p></div> </div><!--/.selfRepInner--> </div><!--/.selfRep--> </li> ・ ・ 中略 ・ ・ </ul><!--/.listCover-->
全体を<ul><li>構成にしてあり、
全体を囲う<ul>にクラス「.listCover」の名前(任意)を付け
サムネイル部分の<li>要素には「.listItem」と名前(任意)を付けてあります。
<li>「.listItem」にはサムネイルとして表示する画像の他に
「.selfRep」と名付けた<div>要素を配置してあり、(ここの名前も任意)
この<div>「.selfRep」の中に入る<div>「.selfRepInner」とセットで構成されており
これらは一覧画面では非表示となります。
サムネイルクリックで詳細エリア展開時に
この2つの<div>で内に記載された内容が表示される構成になっており
この中には適当HTMLを好きなように入れ込める仕様になっています。
全体構成上、リスト要素を「inline-block」や「inline」で制御しているので
面倒なのですが<li>ごとの改行にはコメントアウトをして
改行を削除しておく必要があります。
(もしくは最終的にHTMLソースを改行せずに全て一行にするか)
HTML構成については以上になります。
そしてCSSは以下の様に。
◆CSS
/* .listCover
------------------------- */
.listCover {
margin: 0 auto;
text-align: left;
}
/* .listItem
------------------------- */
.listCover .listItem {
padding: 10px 0;
text-align: center;
display: inline-block;
*display: inline;
*zoom: 1;
cursor: pointer;
}
.listCover .listItem img {
width: 120px;
}
.listCover .active {
background: url(../img/activeArrow.gif) no-repeat bottom center;
}
.listCover .listItem .selfRep {
display: none;
}
/* .expandField
------------------------- */
.expandField {
padding: 10px 0;
width: 100%;
text-align: left;
display: block;
background: #222;
float: left;
position: relative;
overflow: hidden;
}
.expandField .btnClose {
top: 0;
right: 0;
width: 60px;
height: 60px;
display: block;
background: url(../img/btnClose.gif) no-repeat top left;
position: absolute;
overflow: hidden;
cursor: pointer;
}
.expandField .btnPrev {
top: 120px;
left: 0;
width: 60px;
height: 60px;
display: block;
background: url(../img/btnPrev.gif) no-repeat top left;
position: absolute;
overflow: hidden;
cursor: pointer;
}
.expandField .btnNext {
top: 120px;
right: 0;
width: 60px;
height: 60px;
display: block;
background: url(../img/btnNext.gif) no-repeat top left;
position: absolute;
overflow: hidden;
cursor: pointer;
}
.expandField .selfRepInner {
padding: 50px 80px;
text-align: center;
position: relative;
}
.expandField .selfRepInner .secLeft,
.expandField .selfRepInner .secRight {
width: 50%;
float: left;
}
.expandField .selfRepInner .secLeft {
text-align: center;
}
.expandField .selfRepInner .secRight {
text-align: left;
color: #fff;
}
/* ===========================================
SizeAdjustment
=========================================== */
@media screen and (max-width: 600px) {
.expandField .selfRepInner .secLeft img {
width: 80%;
}
}
@media screen and (max-width: 480px) {
.expandField .selfRepInner .secLeft {
display: none;
}
.expandField .selfRepInner .secRight {
width: 100%;
float: none;
}
}
/* =======================================
ClearFix
======================================= */
.listCover:before,
.listCover:after,
.expandField .selfRepInner:before,
.expandField .selfRepInner:after {
content: " ";
display: table;
}
.listCover:after,
.expandField .selfRepInner:after {clear: both;}
.listCover,
.expandField .selfRepInner {*zoom: 1;}
サムネイル表示用リストとなる「.listItem」に対しては「display:inline-block」と
IE6,7用にハックで「inline」にしておく必要があり
サムネイル時に非表示要素となる「.selfRep」は
CSS側では「display:none」にしておく必要があります。
サムネイルクリック後の詳細エリアは「.expandField」となっており
「width:100%」「display:block」「float:left」は
最低限必要となっている要素ですが
その他は装飾等レイアウト調整用となっているので
見た目を変える場合はこの中を変更する必要があります。
「.btnClose」は閉じるボタン
「.btnPrev」は前へボタン
「.btnNext」は次へボタン
となっています。
詳細エリア「.expandField」の高さについては
スクリプト側で設定する様になっているので
CSSでは「height」の値は設定しません。
後半にメディアクエリーを使ってレイアウト制御が入っていますが
サンプル上のレイアウトを保つ為のものになっており
必ず必要というものではありません。
上記のCSS指定とは別に、
リキッド動作の際にウィンドウ幅を計算する上で
スクロールバーの有り無しの状態によって
全体幅の計算がおかしくなってしまうブラウザもあるので
bodyに対してスクロールバーの表示制御を入れてあります。
◆CSS
body {
overflow-y: scroll;
}
*html body {overflow-y: auto;} /* IE6 */
*:first-child+html body {overflow-y: auto;} /* IE7 */
CSSに関しては以上です。
そして実際の動作スクリプトは以下の様になります。
◆SCRIPT
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
$(function(){
var setList = $('.listCover'),
setItem = $('.listItem'),
setReplace = $('.selfRep'),
listBaseWidth = 130,
thumbOpacity = 0.8,
expandHeight = 300,
expandFadeTime = 300,
expandEasing = 'linear',
switchFadeTime = 1000,
switchEasing = 'linear';
setList.each(function(){
var targetObj = $(this);
var findItem = targetObj.find(setItem),
findElm = targetObj.find(setReplace);
// リストアイテムクリック
findItem.click(function(){
if($(this).hasClass('active')){
closeExpandActive = targetObj.find('.expandField');
closeExpandActive.stop().animate({height:'0',opacity:'0'},expandFadeTime,expandEasing,function(){
closeExpandActive.remove();
});
findItem.removeClass('active');
} else {
var setExpand = $('.expandField'),
findExpand = targetObj.find(setExpand),
thisElm = $(this).find(setReplace).html();
// 展開ボックス制御
if(0 < findExpand.size()){
findExpand.empty().html(thisElm);
$(this).after(findExpand).next().append('<span class="btnPrev"></span><span class="btnNext"></span><span class="btnClose"></span>');
var setReplaceInner = $('.selfRepInner'),
findInner = targetObj.find(setReplaceInner);
findInner.css({opacity:'0'}).stop().animate({opacity:'1'},switchFadeTime,switchEasing);
} else {
$(this).after('<li class="expandField">' + thisElm + '</li>').next().css({height:'0', opacity:'0'}).stop().animate({height:expandHeight, opacity:'1'},expandFadeTime,expandEasing).append('<span class="btnPrev"></span><span class="btnNext"></span><span class="btnClose"></span>');
}
// スクロール位置調整
var thisOffset = $(this).find('img').offset();
$('html,body').animate({scrollTop:(thisOffset.top-10)},200,'linear');
// 操作ボタン動作
function switchNext(){
var setActiveN = targetObj.find('.active');
setActiveN.each(function(){
var listLenghN = findItem.length,
listIndexN = findItem.index(this),
listCountN = listIndexN+1,
findItemFirst = findItem.first();
if(listLenghN == listCountN){
findItemFirst.click();
} else {
$(this).next().next().click();
}
});
}
function switchPrev(){
var setActiveP = targetObj.find('.active');
setActiveP.each(function(){
var listLenghP = findItem.length,
listIndexP = findItem.index(this),
listCountP = listIndexP+1,
findItemLast = findItem.last();
if(1 == listCountP){
findItemLast.click();
} else {
$(this).prev().click();
}
});
}
function switchHide(){
closeExpand = targetObj.find('.expandField');
closeExpand.stop().animate({height:'0',opacity:'0'},expandFadeTime,expandEasing,function(){
closeExpand.remove();
});
findItem.removeClass('active');
}
$(this).addClass('active').siblings().removeClass('active');
var btnPrev = targetObj.find('.btnPrev'),btnNext = targetObj.find('.btnNext'),btnClose = targetObj.find('.btnClose');
btnPrev.click(function(){switchPrev();});
btnNext.click(function(){switchNext();});
btnClose.click(function(){switchHide();});
}
});
// サムネイルロールオーバー
var agent = navigator.userAgent;
if(!(agent.search(/iPhone/) != -1 || agent.search(/iPad/) != -1 || agent.search(/iPod/) != -1 || agent.search(/Android/) != -1)){
findItem.hover(function(){
$(this).stop().animate({opacity:thumbOpacity},200);
},function(){
$(this).stop().animate({opacity:'1'},200);
});
}
// リキッド操作
function listAdjust(){
var ulWrap = targetObj.width(),
ulNum = Math.floor(ulWrap / listBaseWidth),
liFixed = Math.floor(ulWrap / ulNum);
findItem.css({width: (liFixed)});
}
$(window).resize(function(){listAdjust();}).resize();
$(window).load(function(){setTimeout(function(){listAdjust();},200);}); // for IE8
});
});
</script>
スクリプト開始部分にある設定値の内容は以下の様になっています。
| var setList = $(‘.listCover’) | 全体を囲う<ul>のクラス名(IDでも可) |
|---|---|
| setItem = $(‘.listItem’) | <li>に対するクラス名 |
| setReplace = $(‘.selfRep’) | 詳細エリアで表示する要素のクラス名(一覧では非表示部分) |
| listBaseWidth = 130 | 一覧画面でリキッド調整する際の基準値(<li>に対する基準幅) |
| thumbOpacity = 0.8 | サムネイルマウスオーバー時の透過度 |
| expandHeight = 300 | 詳細エリアの高さ |
| expandFadeTime = 300 | 詳細エリア展開時のスライドスピード |
| expandEasing = ‘linear’ | 詳細エリア展開時のイージング |
| switchFadeTime = 1000 | 詳細エリア展開後のNEXT/BACK切り替え時のフェードスピード |
| switchEasing = ‘linear’ | 詳細エリア展開後のNEXT/BACK切り替え時のイージング |
これらの設定値を変更することでもろもろ調整が可能になっています。
※非表示インナー要素の「.selfRepInner」のみ、名前を変える場合は
39行目「setReplaceInner = $(‘.selfRepInner’)」を変更してください。
一覧画面でのサムネイルを並べるリキッド動作については
「listBaseWidth = 130」で基準値をセットして
ウィンドウサイズに対してこの値を使って割って
一つ一つの<li>リストに幅を割り振るようになっています。
(サンプルでは100pxのサムネイル画像に対して少し余白をあけた130pxに設定)
スクリプト全体的にあまり汎用性のある構成ではありませんが
設定値を少し調整するだけで簡単に設置することは可能になっているかと思います。
全体をリキッドおよびレスポンシブ対応しているので
PCブラウザのみでなくiPhoneやiPadやAndroidの
スマホ及びタブレットでも動作可能です。
※いちよIE6,7でも動作するようになっています。
少々特殊なUIではありますが
ギャラリーページや制作会社などの制作実績ページなど
一覧画面からちょっとした詳細画面を表示したい場合などに使えるかと思っています。
モーダルウィンドウと違って画面全体にオーバーレイされることもないので
元の画面を生かしつつ別ウィンドウを表示できる使い勝手の良いUIになるかと。
jQueryでGoogle画像検索の様なUIを実装する際にぜひ。。。

