ECサイト等で使われているサムネイルをクリックするとメイン画像が切り替わる画像ビューアの作り方です。
画像ビューアとは、Amazonの商品詳細ページにあるようなものです。
DEMO
始めに完成したものを以下に挙げます。
こういったものを作ります。
画像ビューアの骨組みを作る
始めに以下のHTMLを書いて画像ビューアの骨組みを作ります。
HTML
<div class="overlay"></div>
<div class="modal-window"></div>
<div class="image-viewer">
<div class="main-image">
</div>
<ul class="nav">
</ul>
</div>
HTMLはオーバーレイ.overlay
・モーダルウィンドウ.modal-window
・画像ビューア全体.image-viewer
・メイン画像.main-image
・ナビゲーション.nav
で構成されています。それから後でJavaScriptで.modal-window
と.main-image
と.nav
内にそれぞれ要素を生成して挿入するため今は空にしています。
CSS(SCSS)で画像ビューアの見た目を作る
次に以下のCSSでそれぞれの要素の配置等、画像ビューアの見た目を作ります。併せてレスポンシブ対応も行います。
CSS(SCSS)
img {
height: auto;
width: 100%;
}
.image-viewer {
background: #dfdfdf;
margin: 0 auto;
width: 40%;
@media screen and (max-width: 768px) {
width: 100%;
}
}
.main-image {
background: #777;
padding-bottom: 56.25%;
margin-bottom: 10px;
overflow: hidden;
position: relative;
> img {
//opacity: 0;
position: absolute;
top: 0;
left: 0;
transition: 0.5s;
//visibility: hidden;
/*
&.active {
opacity: 1;
visibility: visible;
z-index: 100;
}
*/
}
}
.nav {
font-size: 0;
list-style: none;
margin: 0;
padding: 0;
&-item {
background: #ccc;
display: inline-block;
overflow: hidden;
position: relative;
padding-bottom: 14.0625%;
width: 25%;
/*
@media screen and (max-width: 768px) {
padding-bottom: 28.125%;
width: 50%;
}
*/
> img {
position: absolute;
top: 0;
left: 0;
}
}
}
.overlay {
background: #000;
height: 100%;
opacity: 0;
position: fixed;
top: 0;
left: 0;
transition: 0.5s;
-webkit-visibility: hidden;
visibility: hidden;
width: 100%;
z-index: 1000;
&.active {
opacity: 0.6;
visibility: visible;
}
}
.modal-window {
opacity: 0;
position: fixed;
top: 50%;
left: 50%;
transition: 0.5s;
transform: translate(-50%, -50%);
visibility: hidden;
z-index: 2000;
&.active {
opacity: 1;
visibility: visible;
}
}
レスポンシブ対応
始めにimg
のスタイルを以下のようにしてフルードイメージにします。
img {
height: auto;
width: 100%;
}
続けてメディアクエリを使って.image-viewer
をウィンドウの幅が768px以下では画面いっぱいに表示させ、それより大きければ40%にします。
.image-viewer {
width: 40%;
@media screen and (max-width: 768px) {
width: 100%;
}
}
最後に.main-image
と.nav-item
とそれらの直近の子要素であるimg
を以下のようにしてウィンドウ幅の変化に応じて常に16:9の比率を維持したまま伸縮させるようにします。
.main-imageとそのimg
.main-image {
padding-bottom: 56.25%;
overflow: hidden;
position: relative;
> img {
position: absolute;
top: 0;
left: 0;
}
}
.nav-itemとそのimg
.nav {
&-item {
display: inline-block;
overflow: hidden;
position: relative;
padding-bottom: 14.0625%;
width: 25%;
> img {
position: absolute;
top: 0;
left: 0;
}
}
}
オーバーレイとモーダルウィンドウの初期状態
オーバーレイとモーダルウィンドウはJavaScriptで表示させるため、初めは以下のようにして非表示にします。
.overlay
.overlay {
opacity: 0;
visibility: hidden;
}
.modal-window
.modal-window {
opacity: 0;
visibility: hidden;
}
次にJavaScriptでオーバーレイとモーダルウィンドウを表示するために必要な.active
クラスを作成します。
.overlay
.overlay {
&.active {
opacity: 0.6;
visibility: visible;
}
}
.modal-window
.modal-window {
&.active {
opacity: 1;
visibility: visible;
}
}
モーダルウィンドウは完全に表示しますが、オーバーレイはopacityを0.6にして半透明で表示します。
JavaScriptで画像ビューアに機能を実装する
最後に以下のJavaScriptを書いてHTMLとCSSで作った画像ビューアに機能を実装します。
JavaScript
(() => {
//画像に関するクラス
class Asset {
constructor() {
this.urls = [
"#",
"#",
"#",
"#"
];
this._images = [];
}
get images() {
return this._images;
}
//初期化するメソッド
init() {
this.createImage();
}
//画像を生成するメソッド
createImage() {
for (let i = 0; i < this.urls.length; i++) {
const image = new Image();
image.src = this.urls[i];
this._images[i] = image;
}
}
}
//ナビゲーションに関するクラス
class Nav {
constructor() {
this.elem = document.querySelector(".nav");
this.navItems = null;
}
//ナビゲーションをクリックできる状態にするメソッド
setupListener(mainImage) {
this.navItems.forEach((navItem, index) =>
navItem.addEventListener("click", e => {
e.preventDefault();
//クリックされたナビの画像
const target = e.target;
//複製
const clone = target.cloneNode(true);
//表示されている画像を削除
mainImage.removeImage();
//複製した画像を挿入
mainImage.addImage(clone);
})
);
}
//ナビゲーションにサムネイルを挿入するメソッド
insertThumbnail(images) {
//.navに画像を挿入(画像が親要素を持たなければouterHTMLはエラーを発生する)
images.forEach(image => this.elem.appendChild(image));
//画像を.nav-itemで包む
for (let i = 0; i < images.length; i++) {
images[i].outerHTML = `<li class="nav-item">${images[i].outerHTML}</li>`;
}
this.navItems = document.querySelectorAll(".nav-item");
//this.navItems.forEach(navItem => this.elem.appendChild(navItem));
}
}
//メイン画像に関するクラス
class MainImage {
constructor() {
this.elem = document.querySelector(".main-image");
}
//初期化するメソッド
init(nav) {
//先頭のサムネイル画像を複製
const clone = nav.navItems[0].firstChild.cloneNode(true);
//複製したサムネイル画像を表示
this.addImage(clone);
}
//メイン画像をクリックできる状態にするメソッド
setupListener(modalWindow, overlay) {
this.elem.addEventListener("click", e => {
e.preventDefault();
const target = e.target;
//console.log(target);
//メイン画像を複製
const clone = target.cloneNode(true);
//それまで挿入されていたモーダルウィンドウの画像を削除
modalWindow.removeImage();
//複製したメイン画像をモーダルウィンドウに挿入
modalWindow.addImage(clone);
//オーバーレイを表示
overlay.show();
});
}
//.main-imageにメイン画像を挿入するメソッド
addImage(image) {
this.elem.appendChild(image);
//image.classList.add("active");
}
//.main-imageからメイン画像を削除するメソッド
removeImage() {
this.elem.removeChild(this.elem.firstChild);
//this.elem.firstChild.classList.remove("active");
}
}
//モーダルウィンドウに関するクラス
class ModalWindow {
constructor() {
this.elem = document.querySelector(".modal-window");
}
//モーダルウィンドウを表示するメソッド
show() {
this.elem.classList.add("active");
}
//モーダルウィンドウを非表示にするメソッド
hide() {
this.elem.classList.remove("active");
}
//.modal-windowに画像を挿入するメソッド
addImage(image) {
this.elem.appendChild(image);
this.elem.classList.add("active");
}
//.modal-windowから画像を削除するメソッド
removeImage() {
//モーダルウィンドウ内に画像があれば(初回は無いためそのまま書くとエラーが発生する)
if (this.elem.firstChild) {
this.elem.removeChild(this.elem.firstChild);
this.elem.classList.remove("active");
}
}
}
//オーバーレイに関するクラス
class Overlay {
constructor() {
this.elem = document.querySelector(".overlay");
}
//オーバーレイをクリックできる状態にするメソッド
setupListener(modalWindow) {
this.elem.addEventListener("click", e => {
e.preventDefault();
//オーバーレイを非表示
this.hide();
//モーダルウィンドウを非表示
modalWindow.hide();
//モーダルウィンドウ内の画像を削除
modalWindow.removeImage(modalWindow.elem.firstChild);
});
}
//オーバーレイを表示するメソッド
show() {
this.elem.classList.add("active");
}
//オーバーレイを非表示にするメソッド
hide() {
this.elem.classList.remove("active");
}
}
//インスタンス化
const asset = new Asset();
const nav = new Nav();
const mainImage = new MainImage();
const modalWindow = new ModalWindow();
const overlay = new Overlay();
//全体を初期化する関数
function init() {
asset.init();
nav.insertThumbnail(asset.images);
mainImage.init(nav);
setupListeners();
}
//画像ビューアを操作できる状態にする関数
function setupListeners() {
nav.setupListener(mainImage);
mainImage.setupListener(modalWindow, overlay);
overlay.setupListener(modalWindow);
}
//起動
init();
})();
事前準備
機能を実装する前に以下の準備をする必要があります。
- 画像を生成する。
- ナビゲーション内にサムネイルを挿入する。
- メインパネル内にメイン画像を挿入する。
画像を生成する
Asset
クラスのコンストラクタでURLを格納した配列urls
を定義します。今回は4枚の画像を生成するため4つのURLを格納しています。
this.urls = [
"http://tsukulog.net/wp-content/uploads/2014/09/https-www.pakutaso.com-assets_c-2015-05-PED_narandaapple2-thumb-1000xauto-13287.jpg",
"http://tsukulog.net/wp-content/uploads/2015/07/IMG_44521.jpg",
"http://tsukulog.net/wp-content/uploads/2015/11/bsHIR96_boxneko.jpg",
"http://tsukulog.net/wp-content/uploads/2016/04/shared-img-thumb-SHIO16032459IMG_6994_TP_V.jpg"
];
そしてcreateImage
メソッドで格納されているURLの数だけ画像を生成します。
//画像を生成するメソッド
createImage() {
for (let i = 0; i < this.urls.length; i++) {
const image = new Image();
image.src = this.urls[i];
this._images[i] = image;
}
}
ナビゲーション内にサムネイルを挿入する
Nav
クラスのinsertThumbnail
メソッドでナビゲーション内にサムネイルを挿入します。
//ナビゲーションにサムネイルを挿入するメソッド
insertThumbnail(images) {
//.navに画像を挿入(画像が親要素を持たなければouterHTMLはエラーを発生する)
images.forEach(image => this.elem.appendChild(image));
//画像を.nav-itemで包む
for (let i = 0; i < images.length; i++) {
images[i].outerHTML = `<li class="nav-item">${images[i].outerHTML}</li>`;
}
this.navItems = document.querySelectorAll(".nav-item");
//this.navItems.forEach(navItem => this.elem.appendChild(navItem));
}
サムネイルを挿入するまでの過程は次の通りです。
forEach
メソッドでimages
内の画像を一つずつ取り出して.nav
内に挿入します。images
内の画像をfor文とouterHTML
で一つずつ.nav-item
要素で包みます。
メインパネル内にメイン画像を挿入する
メインパネルにはクリックされたサムネイルの画像が挿入されますが、初期状態では先頭のサムネイルの画像を表示させます。これをMainImage
クラスのinit
メソッドで実現します。
//初期化するメソッド
init(nav) {
//先頭のサムネイル画像を複製
const clone = nav.navItems[0].firstChild.cloneNode(true);
//複製したサムネイル画像を表示
this.addImage(clone);
}
nav.navItems[0].firstChild.cloneNode(true);
で先頭の.nav-item
内の画像を複製し、複製した画像clone
をaddImage
メソッドに渡して.main-image
内に挿入しています。
addImageメソッド
//.main-imageにメイン画像を挿入するメソッド
addImage(image) {
this.elem.appendChild(image);
}
実装する機能
画像ビューアに実装する機能は以下の通りです。
- クリックしたサムネイルと同じ画像をメインパネルに表示する
- メイン画像をクリックするとオーバーレイとモーダルウィンドウを表示する。
- オーバーレイをクリックするとオーバーレイとモーダルウィンドウを非表示にする。
クリックしたサムネイルと同じ画像をメインパネルに表示する
サムネイルをクリックするとクリックしたサムネイルと同じ画像をメイン画像として.main-image
内に挿入します。この処理はNav
クラスのsetupListener
メソッドで実現します。
//ナビゲーションをクリックできる状態にするメソッド
setupListener(mainImage) {
this.navItems.forEach((navItem, index) =>
navItem.addEventListener("click", e => {
e.preventDefault();
//クリックされたナビの画像
const target = e.target;
//複製
const clone = target.cloneNode(true);
//表示されている画像を削除
mainImage.removeImage();
//複製した画像を挿入
mainImage.addImage(clone);
})
);
}
仕組みは以下の通りです。
- クリックされたサムネイル画像
target
を複製する。 - 一旦
.main-image
内の画像を削除 mainImage
オブジェクトのaddImage
メソッドで.main-image
内に1.で複製した画像clone
を挿入する。
メイン画像をクリックするとオーバーレイとモーダルウィンドウを表示する
メイン画像をクリックするとMainImage
クラスのsetupListener
メソッドを実行してオーバーレイとモーダルウィンドウをフェードイン表示するようにします。
//メイン画像をクリックできる状態にするメソッド
setupListener(modalWindow, overlay) {
this.elem.addEventListener("click", e => {
e.preventDefault();
const target = e.target;
//メイン画像を複製
const clone = target.cloneNode(true);
//それまで挿入されていたモーダルウィンドウの画像を削除
modalWindow.removeImage();
//複製したメイン画像をモーダルウィンドウに挿入
modalWindow.addImage(clone);
//オーバーレイを表示
overlay.show();
});
}
仕組みは以下の通りです。
- クリックしたメイン画像
target
をcloneNode
で複製する。 modalWindow
のremoveImage
メソッドで直前まで表示されていたモーダルウィンドウの画像を削除する。modalWindow
のaddImage
メソッドでモーダルウィンドウに1.で複製した画像clone
を挿入する。overlay
のshow
メソッドでオーバーレイを表示する。
オーバーレイをクリックするとオーバーレイとモーダルウィンドウを非表示にする
オーバーレイをクリックするとOverlay
クラスのsetupListener
メソッドでオーバーレイ自身とモーダルウィンドウをフェードアウトで非表示にします。
//オーバーレイをクリックできる状態にするメソッド
setupListener(modalWindow) {
this.elem.addEventListener("click", e => {
e.preventDefault();
//オーバーレイを非表示
this.hide();
//モーダルウィンドウを非表示
modalWindow.hide();
//モーダルウィンドウ内の画像を削除
modalWindow.removeImage(modalWindow.elem.firstChild);
});
}
setupListener
メソッドでは始めにOverlay
クラスのhide
メソッドでオーバーレイを非表示にします。
//オーバーレイを非表示にするメソッド
hide() {
this.elem.classList.remove("active");
}
次にModalWindow
クラスのhide
メソッドでモーダルウィンドウを非表示にします。
//モーダルウィンドウを非表示にするメソッド
hide() {
this.elem.classList.remove("active");
}
最後にModalWindow
クラスのremoveImage
メソッドでモーダルウィンドウ内の画像を削除する。
//.modal-windowから画像を削除するメソッド
removeImage() {
//モーダルウィンドウ内に画像があれば(初回は無いためそのまま書くとエラーが発生する)
if (this.elem.firstChild) {
this.elem.removeChild(this.elem.firstChild);
this.elem.classList.remove("active");
}
}
画像ビューアを完成させる
最後に以下のメイン処理で実装した機能を働かせて画像ビューアを完成させます。
インスタンス化
作成した全てのクラスをインスタンス化して実装した機能を使えるようにします。
//インスタンス化
const asset = new Asset();
const nav = new Nav();
const mainImage = new MainImage();
const modalWindow = new ModalWindow();
const overlay = new Overlay();
画像ビューアを操作できる状態にする関数を定義
setupListeners
関数を定義してナビゲーション・メイン画像・オーバーレイをクリックできる状態にします。
//画像ビューアを操作できる状態にする関数
function setupListeners() {
nav.setupListener(mainImage);
mainImage.setupListener(modalWindow, overlay);
overlay.setupListener(modalWindow);
}
やっていることはnav
、mainImage
、overlay
オブジェクトのsetupListener
メソッドを呼び出しているだけです。
全体を初期化する関数を定義
init
関数を定義して画像ビューア全体を初期化します。
//全体を初期化する関数
function init() {
asset.init();
nav.insertThumbnail(asset.images);
mainImage.init(nav);
setupListeners();
}
init
関数でやっていることは
asset
オブジェクトのinit
メソッドで画像を生成する。nav
オブジェクトのinsertThumbnail
メソッドで.nav
内にサムネイルを挿入する。mainImage
オブジェクトのinit
メソッドで.main-image
内にメイン画像を挿入する。setupListeners
関数で画像ビューアを操作できる状態にする。 です。
起動する
init
関数を実行して画像ビューアを起動します。
//起動
init();
さいごに
以上でレスポンシブな画像ビューアの作り方を終わります。