Home
CSS
Amazonみたいな商品の拡大プレビュー機能を実装

Amazonみたいな商品の拡大プレビュー機能を実装

Amazonみたいな商品の拡大プレビュー機能を実装

どうも!かけちまるです!

商品詳細ページなどに、次のサンプルのような拡大プレビュー機能の実装方法を解説します。

この記事では、

  • ・拡大プレビュー機能の実装方法
  • ・商品の画像が増減しても大丈夫な対応

がわかります。

拡大プレビュー機能の実装方法

上のCode Penは実践的な見た目にするためにslick.jsを使っており、余計な記述が多いのでここでは拡大プレビュー機能にだけ焦点を当てて解説します。

まず、実装する事を手順も含めまとめました。

  • ①商品詳細っぽい2カラムを作る
  • ②拡大レンズを作る
  • ③拡大プレビューエリアを作る
  • ④レンズとプレビューの表示非表示
  • ⑤カーソルに合わせてレンズとプレビューを移動させる
  • ⑥完成
1

商品詳細っぽい2カラムを作る

商品詳細にありがちな2カラムでコーディングします。
左に商品画像、右に商品説明の想定です。

商品詳細っぽい2カラム
HTML
コピー
<section class="section"> <div class="left"> <div class="img_wrap"> <img class="zoom_img" src="https://drive.google.com/uc?export=view&id=14YGpCmQOBeQzB3PWoyewmlfiR8E83zki"> </div> </div> <div class="right"> <p class="text">ダミーテキストダミーテキストダミーテキストダミーテキストダミーテキストダミーテキストダミーテキストダミーテキスト</p> </div> </section>
CSS
コピー
.section{ display: flex; justify-content: space-between; margin-left: auto; margin-right: auto; margin-top: 100px; width: 90%; max-width: 1000px; } .left{ width: 50%; } .zoom_img{ display: block; width: 100%; } .right{ background-color: #f0f0f0; width: 45%; }
2

拡大レンズを作る

.img_wrapが、起点となるようにposition: absolute;で配置します。
位置は後でJavaScriptで変更するのでなんでもいいです。

拡大レンズを作る
HTML
コピー
<section class="section"> <div class="left"> <div class="img_wrap"> <img class="zoom_img" src="https://drive.google.com/uc?export=view&id=14YGpCmQOBeQzB3PWoyewmlfiR8E83zki"> <!-- 拡大レンズの要素 --> <div class="lens"></div> </div> </div> <div class="right"> <p class="text">ダミーテキストダミーテキストダミーテキストダミーテキストダミーテキストダミーテキストダミーテキストダミーテキスト</p> </div> </section>
CSS
コピー
.section{ display: flex; justify-content: space-between; margin-left: auto; margin-right: auto; margin-top: 100px; width: 90%; max-width: 1000px; } .left{ width: 50%; } .img_wrap{ position: relative; } .zoom_img{ display: block; width: 100%; } /* 拡大レンズの要素のスタイル */ .lens{ background-color: rgba(0, 0, 0, 0.3); position: absolute; top: 25px; /* あとでJSで値を設定する */ left: 25px; /* あとでJSで値を設定する */ height: 150px; width: 150px; } .right{ background-color: #f0f0f0; width: 45%; }
3

拡大プレビューエリアを作る

画像エリアの右横に拡大プレビューエリアを作ります。
position: absolute;で配置します。
仮で画像を入れておくとコーディングしやすいと思います。

ポイント
  • overflow: hidden;ではみ出したら非表示なるようにする
  • ・ネガティブmarginで画像の位置を調節
  • ・画像のwidthは多めにとっておく

ネガティブmarginと画像のwidthは後でJavaScriptで値を変更しますが、この2点の値を変えてみる事でJavaScriptでどのような処理を書けばいいか理解しやすくなると思います。

拡大プレビューエリア
HTML
コピー
<section class="section"> <!-- 拡大プレビューエリアの要素 --> <div class="preview"> <img class="preview_img" src="https://drive.google.com/uc?export=view&id=14YGpCmQOBeQzB3PWoyewmlfiR8E83zki"> </div> <div class="left"> <div class="img_wrap"> <img class="zoom_img" src="https://drive.google.com/uc?export=view&id=14YGpCmQOBeQzB3PWoyewmlfiR8E83zki"> <!-- 拡大レンズの要素 --> <div class="lens"></div> </div> </div> <div class="right"> <p class="text">ダミーテキストダミーテキストダミーテキストダミーテキストダミーテキストダミーテキストダミーテキストダミーテキスト</p> </div> </section>
CSS
コピー
.section{ display: flex; justify-content: space-between; margin-left: auto; margin-right: auto; margin-top: 100px; width: 90%; max-width: 1000px; position: relative; } /* 拡大プレビューエリアのスタイル */ .preview{ border: 1px solid #000; overflow: hidden; position: absolute; top: 0; right: 0; height: 400px; width: 400px; } .preview_img{ display: block; width: 1300px; /* あとでJSで値を設定する */ margin-top: -100px; /* あとでJSで値を設定する */ margin-left: -250px; /* あとでJSで値を設定する */ } .left{ width: 50%; } .img_wrap{ position: relative; } /* 拡大レンズの要素のスタイル */ .lens{ background-color: rgba(0, 0, 0, 0.3); position: absolute; top: 25px; /* あとでJSで値を設定する */ left: 25px; /* あとでJSで値を設定する */ height: 150px; width: 150px; } .right{ background-color: #f0f0f0; width: 45%; }
4

レンズとプレビューの表示非表示

レンズ(.lens)とプレビュー(.preview)は基本、非表示にしておきたいのでvisibility: hidden;にしておきます。
レンズ(.lens)は.img_wraphoverしたらvisibility: visible;、プレビュー(.preview)は.activeがついたらvisibility: visible;になるようにJavaScriptで制御します。

CSS
コピー
.section{ display: flex; justify-content: space-between; margin-left: auto; margin-right: auto; margin-top: 100px; width: 90%; max-width: 1000px; position: relative; } /* 拡大プレビューエリアのスタイル */ .preview{ border: 1px solid #000; overflow: hidden; position: absolute; top: 0; right: 0; height: 400px; width: 400px; visibility: hidden; } .preview.active{ visibility: visible; } .preview_img{ display: block; width: 1300px; /* あとでJSで値を設定する */ margin-top: -100px; /* あとでJSで値を設定する */ margin-left: -250px; /* あとでJSで値を設定する */ } .left{ width: 50%; position: relative; } .left{ width: 50%; } .img_wrap{ position: relative; } /* 拡大レンズの要素のスタイル */ .lens{ background-color: rgba(0, 0, 0, 0.3); position: absolute; top: 25px; /* あとでJSで値を設定する */ left: 25px; /* あとでJSで値を設定する */ height: 150px; width: 150px; visibility: hidden; } .left:hover .lens{ visibility: visible; } .right{ background-color: #f0f0f0; width: 45%; }

JavaScriptで.previewエリアの表示非表示を制御します。

ポイント
  • ・複数要素に対応するため、.img_wrapquerySelectorAllで取得しfor文でまわす。
  • mouseenterイベントで.img_wraphoverしたときに.activeをつけて.previewを表示
  • mouseleaveイベントで.img_wraphoverが外れたときに.activeをとって.previewを非表示
JavaScript
コピー
// 拡大プレビュー let previewDOM = document.querySelector('.preview'); let imgWrapDOMs = document.querySelectorAll('.img_wrap'); for(let imgWrapDOM of imgWrapDOMs){ imgWrapDOM.addEventListener('mouseenter', () => { previewDOM.classList.add('active'); }); imgWrapDOM.addEventListener('mouseleave', () => { previewDOM.classList.remove('active'); }); };
5

カーソルに合わせてレンズとプレビューを移動させる

コードが長いですが、機能によって切り分けて考えるとわかりやすいです。

処理としては、

  • .img_wraphoverした時の処理
  • .img_wraphoverが外れた時の処理
  • .zoom_imgが読み込まれた時の処理
  • .img_wrapの範囲でマウスが移動した時の処理

に切り分けられます。

JavaScript
コピー
// 拡大プレビュー let previewDOM = document.querySelector('.preview'); let previewImgDOM = previewDOM.querySelector('.preview_img'); let lensDOM = document.querySelector('.lens'); let imgWrapDOMs = document.querySelectorAll('.img_wrap'); let LensSize = lensDOM.clientWidth; let previewScale = previewDOM.clientWidth / LensSize; for(let imgWrapDOM of imgWrapDOMs){ let lens = imgWrapDOM.querySelector('.lens'); let zoomlmgDOM = imgWrapDOM.querySelector('.zoom_img'); /** * ホバーした時の処理 */ imgWrapDOM.addEventListener('mouseenter', () => { let img = imgWrapDOM.querySelector('.zoom_img'); previewDOM.classList.add('active'); previewImgDOM.setAttribute('src', img.src); previewImgDOM.style.width = (img.offsetWidth * previewScale) + 'px'; }); /** * ホバーが外れた時の処理 */ imgWrapDOM.addEventListener('mouseleave', () => { previewDOM.classList.remove('active'); }); let xmax; let ymax; /** * .zoom_imgが読み込まれた時の処理 */ zoomlmgDOM.addEventListener('load', () => { xmax = zoomlmgDOM.offsetWidth - LensSize; ymax = zoomlmgDOM.offsetHeight - LensSize; /** * .img_wrapの範囲でマウスが移動した時の処理 */ imgWrapDOM.addEventListener('mousemove', (e) => { let rect = imgWrapDOM.getBoundingClientRect(); let mouseX = e.pageX; let mouseY = e.pageY; let positionX = rect.left + window.pageXOffset; let positionY = rect.top + window.pageYOffset; let offsetX = mouseX - positionX; let offsetY = mouseY - positionY; let left = offsetX - (LensSize / 2); let top = offsetY - (LensSize / 2); lens.style.top = top + 'px'; lens.style.left = left + 'px'; previewImgDOM.style.marginLeft = -(left * previewScale) + 'px'; previewImgDOM.style.marginTop = -(top * previewScale) + 'px'; if(left > xmax){ left = xmax; } if(top > ymax){ top = ymax; } if(left < 0){ left = 0; } if(top < 0){ top = 0; } lens.style.top = top + 'px'; lens.style.left = left + 'px'; previewImgDOM.style.marginLeft = -(left * previewScale) + 'px'; previewImgDOM.style.marginTop = -(top * previewScale) + 'px'; }); }); };
6

完成

これでAmazonのような拡大プレビュー機能の実装は完了です。
最終的なHTML・CSS・JavaScriptはこちらです。

HTML
コピー
<section class="section"> <!-- 拡大プレビューエリアの要素 --> <div class="preview"> <img class="preview_img" src="https://drive.google.com/uc?export=view&id=14YGpCmQOBeQzB3PWoyewmlfiR8E83zki"> </div> <div class="left"> <div class="img_wrap"> <img class="zoom_img" src="https://drive.google.com/uc?export=view&id=14YGpCmQOBeQzB3PWoyewmlfiR8E83zki"> <!-- 拡大レンズの要素 --> <div class="lens"></div> </div> </div> <div class="right"> <p class="text">ダミーテキストダミーテキストダミーテキストダミーテキストダミーテキストダミーテキストダミーテキストダミーテキスト</p> </div> </section>
CSS
コピー
.section{ display: flex; justify-content: space-between; margin-left: auto; margin-right: auto; margin-top: 100px; width: 90%; max-width: 1000px; position: relative; } /* 拡大プレビューエリアのスタイル */ .preview{ border: 1px solid #000; overflow: hidden; position: absolute; top: 0; right: 0; height: 400px; width: 400px; visibility: hidden; } .preview.active{ visibility: visible; } .preview_img{ display: block; width: 1300px; /* あとでJSで値を設定する */ margin-top: -100px; /* あとでJSで値を設定する */ margin-left: -250px; /* あとでJSで値を設定する */ } .left{ width: 50%; } .img_wrap{ position: relative; } .zoom_img{ display: block; width: 100%; } /* 拡大レンズの要素のスタイル */ .lens{ background-color: rgba(0, 0, 0, 0.3); position: absolute; top: 25px; /* あとでJSで値を設定する */ left: 25px; /* あとでJSで値を設定する */ height: 150px; width: 150px; visibility: hidden; } .left:hover .lens{ visibility: visible; } .right{ background-color: #f0f0f0; width: 45%; }
JavaScript
コピー
// 拡大プレビュー let previewDOM = document.querySelector('.preview'); let previewImgDOM = previewDOM.querySelector('.preview_img'); let lensDOM = document.querySelector('.lens'); let imgWrapDOMs = document.querySelectorAll('.img_wrap'); let LensSize = lensDOM.clientWidth; let previewScale = previewDOM.clientWidth / LensSize; for(let imgWrapDOM of imgWrapDOMs){ let lens = imgWrapDOM.querySelector('.lens'); let zoomlmgDOM = imgWrapDOM.querySelector('.zoom_img'); /** * .img_wrapにhoverした時の処理 */ imgWrapDOM.addEventListener('mouseenter', () => { let img = imgWrapDOM.querySelector('.zoom_img'); previewDOM.classList.add('active'); previewImgDOM.setAttribute('src', img.src); previewImgDOM.style.width = (img.offsetWidth * previewScale) + 'px'; }); /** * .img_wrapのhoverが外れた時の処理 */ imgWrapDOM.addEventListener('mouseleave', () => { previewDOM.classList.remove('active'); }); let xmax; let ymax; /** * .zoom_imgが読み込まれた時の処理 */ zoomlmgDOM.addEventListener('load', () => { xmax = zoomlmgDOM.offsetWidth - LensSize; ymax = zoomlmgDOM.offsetHeight - LensSize; /** * .img_wrapの範囲でマウスが移動した時の処理 */ imgWrapDOM.addEventListener('mousemove', (e) => { let rect = imgWrapDOM.getBoundingClientRect(); let mouseX = e.pageX; let mouseY = e.pageY; let positionX = rect.left + window.pageXOffset; let positionY = rect.top + window.pageYOffset; let offsetX = mouseX - positionX; let offsetY = mouseY - positionY; let left = offsetX - (LensSize / 2); let top = offsetY - (LensSize / 2); lens.style.top = top + 'px'; lens.style.left = left + 'px'; previewImgDOM.style.marginLeft = -(left * previewScale) + 'px'; previewImgDOM.style.marginTop = -(top * previewScale) + 'px'; if(left > xmax){ left = xmax; } if(top > ymax){ top = ymax; } if(left < 0){ left = 0; } if(top < 0){ top = 0; } lens.style.top = top + 'px'; lens.style.left = left + 'px'; previewImgDOM.style.marginLeft = -(left * previewScale) + 'px'; previewImgDOM.style.marginTop = -(top * previewScale) + 'px'; }); }); };

おわり

かけちまる
かけちまる
Webエンジニアをしています。
HTML/CSS/JavaScript/jQuery/PHPができます。
WEB制作を中心に日々学びになったこと、興味が沸いたことについて初心者の方でもわかりやすいようにアウトプットしていくブログです。

関連記事

【PHP】json_encode()とjson_decode()でJSONを操る

【PHP】json_encode()とjson_decode()でJSONを操る

Visual Studio Codeの検索と置換

Visual Studio Codeの検索と置換

【WordPress】画像アップ時に自動生成されないようにする方法

【WordPress】画像アップ時に自動生成されないようにする方法

webpack環境で画像追加を監視して自動圧縮&WebP変換する方法

webpack環境で画像追加を監視して自動圧縮&WebP変換する方法

JavaScriptでサイトのスクロールを無効にする方法

JavaScriptでサイトのスクロールを無効にする方法

jQueryでスクロール中か否かを判定する方法

jQueryでスクロール中か否かを判定する方法