今回の記事では、画像にhoverした際に、拡大プレビューを表示する機能を実装していきます。
ECサイトの商品ページなどで、よく見られる機能です。

※紹介する実装方法は、素のJavaScriptで実装しており、ライブラリは使用していません。
あくまで、機能の内部的な仕組みを学びたい方向けの記事になっています。

■ 関連記事 : JavaScriptの基礎習得に役立つ技術書【おすすめ3選】

まずは完成イメージを確認ください。
黄色枠内が商品画像にhoverした際に拡大対象になる領域で、赤枠内がプレビューの表示領域になります。

動作

実装

実際の動作を確認いただいたところで、実装方法を解説していきます。

ソースコードの全体像

こちらはいつも通り、結論から知りたい方のためにコードから公開します。
コピペで動作しますので、ご自身の環境でお試しください。

HTML

<div class="container">
    <div class="product_image">
        <img src="https://cdn.pixabay.com/photo/2016/06/25/12/50/handbag-1478814_960_720.jpg" alt="商品画像">
        <div class="zoom_area"></div>
    </div>
    <div class="product_info">
        <div class="previewer"><img src="https://cdn.pixabay.com/photo/2016/06/25/12/50/handbag-1478814_960_720.jpg" alt="商品画像"></div>
        <div class="product_info_inner"></div>
    </div>
</div>

CSS

img {
    width: 100%;
    vertical-align: bottom;
}
.container {
    max-width: 1200px;
    display: flex;
    margin: 0 auto;
}
.container > div {
    position: relative;
    padding: 0 10px;
    box-sizing: border-box;
}
.product_image {
    width: 60%;
    overflow: hidden;
}
.product_info {
    width: 40%;
}
.product_info_inner {
    background: #eee;
    height: 100%;
}
.previewer {
    display: none;
    position: absolute;
    right: 0;
    top: 0;
    left: 0;
    bottom: 0;
    overflow: hidden;
}
.previewer.active {
    display: block;
}

/* zoom_area */
.zoom_area {
    width: 200px;
    height: 200px;
    background: rgba(238, 238, 238, 0.5);
    position: absolute;
    display: none;
}
.zoom_area.active {
    display: block;
}

JavaScript

const product_image_wrap = document.querySelector('.product_image');
const product_image_element = document.querySelector('.product_image img');
const previewer = document.querySelector('.previewer');
const previewer_img = document.querySelector('.previewer img');
const zoom_area = document.querySelector('.zoom_area');

let size = 200;
let scale = 2.5;
let limit_x, limit_y;

/* プレビュー画像をset */
product_image_element.addEventListener('load', function() {
    previewer_img.setAttribute('src', product_image_element.src);
    previewer_img.style.width = (product_image_element.offsetWidth * scale) + 'px';
    limit_x = product_image_element.offsetWidth - size;
    limit_y = product_image_element.offsetHeight - size;
});

// 商品画像へのhover
product_image_wrap.addEventListener('mouseenter', function(e) {
    previewer.classList.add('active');
    zoom_area.classList.add('active');
});

// mousemoveイベント
product_image_wrap.addEventListener('mousemove', function(e) {

    const rect_obj = product_image_wrap.getBoundingClientRect();

    const offset_x = e.pageX - rect_obj.left;
    const offset_y = e.pageY - rect_obj.top;

    let left_position = offset_x - (size / 2);
    let top_position = offset_y - (size / 2);

    left_position > limit_x ? left_position = limit_x + 10 : left_position < 0 ? left_position = 0 + 10 : false;
    top_position > limit_y ? top_position = limit_y : top_position < 0 ? top_position = 0 : false;


    zoom_area.style.top = top_position + 'px';
    zoom_area.style.left = left_position + 'px';

    previewer_img.style.marginLeft = -(left_position * scale) + 'px';
    previewer_img.style.marginTop = -(top_position * scale) + 'px';

});

// mouseleave
product_image_wrap.addEventListener('mouseleave', function() {
    previewer.classList.remove('active');
    zoom_area.classList.remove('active');
})

■ 関連記事 : JavaScriptの基礎習得に役立つ技術書【おすすめ3選】

解説 : JavaScript

JSにフォーカスして解説を行います

処理順

  1. 処理に使う共通の要素や数値を、定数 or 変数で定義する
  2. 商品画像のsrcを、プレビュー用画像のsrcにsetする
  3. 商品画像にhoverした時に、hover領域とpreview領域を表示させる
  4. 商品画像上でmousemoveした際に、マウス座標を取得し要素のposition値に割り当てる
  5. 商品画像からhoverが外れた時に、hover領域とpreview領域を非表示にする

1. 処理に使う共通の要素や数値を、定数 or 変数で定義する

const product_image_wrap = document.querySelector('.product_image');
const product_image_element = document.querySelector('.product_image img');
const previewer = document.querySelector('.previewer');
const previewer_img = document.querySelector('.previewer img');
const zoom_area = document.querySelector('.zoom_area');

let size = 200;
let scale = 2.5;
let limit_x, limit_y;

DOM操作に必要な要素を定数化します。
また、今回は拡大機能を実装するので、拡大用の比率(scale)も変数定義しておきます。
sizeは、商品画像にhoverした際に表示されるhover領域のサイズです。

2. 商品画像のsrcを、プレビュー用画像のsrcにsetする

product_image_element.addEventListener('load', function() {
    previewer_img.setAttribute('src', product_image_element.src);
    previewer_img.style.width = (product_image_element.offsetWidth * scale) + 'px';
    limit_x = product_image_element.offsetWidth - size;
    limit_y = product_image_element.offsetHeight - size;
});

商品画像の読み込みが完了したタイミングで、preview要素のimg属性にsrcをsetします。
同時にpreview用の画像のサイズも指定します。
1.の変数定義で、scale(比率)を2.5倍に設定しているので、preview用の画像のサイズは、2.5倍になります。

3. 商品画像のhover時に、hover領域とpreview領域を表示させる

product_image_wrap.addEventListener('mouseenter', function(e) {
    previewer.classList.add('active');
    zoom_area.classList.add('active');
});

このフローは基本的なDOM操作です。
商品画像領域にマウスがhoverした際に、プレビュー要素とhover領域を表示させます。

イベントについては、mouseovermouseenterの2つの似たイベントがありますが、
mouseoverは子要素にイベントが伝搬する特性があるので、今回はmouseenterを採用しました。

4. 商品画像上でmousemoveした際に、マウス座標を要素のposition値に割り当てる

product_image_wrap.addEventListener('mousemove', function(e) {

    const rect_obj = product_image_wrap.getBoundingClientRect();

    const offset_x = e.pageX - rect_obj.left;
    const offset_y = e.pageY - rect_obj.top;

    let left_position = offset_x - (size / 2);
    let top_position = offset_y - (size / 2);

    left_position > limit_x ? left_position = limit_x + 10 : left_position < 0 ? left_position = 0 + 10 : false;
    top_position > limit_y ? top_position = limit_y : top_position < 0 ? top_position = 0 : false;


    zoom_area.style.top = top_position + 'px';
    zoom_area.style.left = left_position + 'px';

    previewer_img.style.marginLeft = -(left_position * scale) + 'px';
    previewer_img.style.marginTop = -(top_position * scale) + 'px';

});

ここのフローが今回の機能のコアになります。
まずイベントは、mousemoveイベントを使用し、商品画像上でマウスが動くたびにマウス座標を取得します。

const rect_obj = product_image_wrap.getBoundingClientRect();
const offset_x = e.pageX - rect_obj.left;
const offset_y = e.pageY - rect_obj.top;

上記の記述では、
・rect_obj変数に、ブラウザ全体を基準にした商品画像要素の座標位置を格納します。
・e.pageX、e.pageYのドキュメント全体を基準にしたイベントの座標から、rect_objのtop, leftの値を引くことで、
正確なマウス座標の位置を取得します。

hover領域の位置を制御
let left_position = offset_x - (size / 2);
let top_position = offset_y - (size / 2);

上記の記述では、hover領域の要素の位置を指定しています。
offset_x – (size / 2) offset_y – (size / 2)とすることで、
マウスがhover領域の中心に配置されるようにしています。

hover領域の動作範囲を制御
left_position > limit_x ? left_position = limit_x + 10 : left_position < 0 ? left_position = 0 + 10 : false;
top_position > limit_y ? top_position = limit_y : top_position < 0 ? top_position = 0 : false;

上記の記述では、hover領域が画像枠内からはみ出ないように、
X,Y座標が0以下になった場合は、positionの絶対値に0を指定するようにしています。

preview領域の画像の位置を制御
previewer_img.style.marginLeft = -(left_position * scale) + 'px';
previewer_img.style.marginTop = -(top_position * scale) + 'px';

上記の記述では、preview領域の画像の位置を制御しています。
preview領域の画像サイズは2.5倍に設定されているので、marginLeftとmarginTopの値も2.5倍にしています。

5. 商品画像からhoverが外れた時に、拡大対象のhover要素と拡大preview領域を非表示にする

product_image_wrap.addEventListener('mouseleave', function() {
    previewer.classList.remove('active');
    zoom_area.classList.remove('active');
})

3.の処理の対照のDOM操作で、商品画像領域からhoverが外れたらプレビュー機能をOFFにします。

処理は以上になります。

カテゴリー: JavaScript