どうも!かけちまるです!
商品詳細ページなどに、次のサンプルのような拡大プレビュー機能の実装方法を解説します。
この記事では、
がわかります。
上のCode Penは実践的な見た目にするためにslick.jsを使っており、余計な記述が多いのでここでは拡大プレビュー機能にだけ焦点を当てて解説します。
まず、実装する事を手順も含めまとめました。
商品詳細っぽい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%; }
拡大レンズを作る
.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%; }
拡大プレビューエリアを作る
画像エリアの右横に拡大プレビューエリアを作ります。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%; }
レンズとプレビューの表示非表示
レンズ(.lens
)とプレビュー(.preview
)は基本、非表示にしておきたいのでvisibility: hidden;
にしておきます。
レンズ(.lens
)は.img_wrap
にhover
したら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_wrap
はquerySelectorAll
で取得しfor
文でまわす。mouseenter
イベントで.img_wrap
にhover
したときに.active
をつけて.preview
を表示mouseleave
イベントで.img_wrap
のhover
が外れたときに.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'); }); };
カーソルに合わせてレンズとプレビューを移動させる
コードが長いですが、機能によって切り分けて考えるとわかりやすいです。
処理としては、
.img_wrap
にhover
した時の処理.img_wrap
のhover
が外れた時の処理.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'; }); }); };
完成
これで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'; }); }); };
おわり
フィードバックを送信
記事についてのフィードバックはTwitterかお問い合わせフォームから受け付けております。