【初心者向け】JavaScript で虫眼鏡を実装する方法

# これを作るよ！

![イメージGIF.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187949/7a2c26b1-bfd1-9fb9-f6c6-f9cc8b371edd.gif)


[画像にマウスをホバーすると拡大するjavascript](https://jsfiddle.net/23to33to43/bg9ae1uf/2/)


虫眼鏡、拡大鏡のような、画像の上にホバーすると画像を拡大させる方法は
~~jQuery等のライブラリはあるけど、素のHTML CSS Javascriptで実装しているのは~~

~~検索しても見つからなかったので、今回作ってみました~~
「虫眼鏡 css javascript」 ... では見つからなかったのですが
「magnifying glass css javascript」で検索したら普通に見つかりました..... 
[How TO - Image Magnifier Glass](https://www.w3schools.com/howto/howto_js_image_magnifier_glass.asp)


## 対象
+ 虫眼鏡、拡大鏡を素のJavascriptで実装したい人
+ domを取得して、要素を参照、変更、したことがある人
+ タグにonclick=""　を書いたり　addEventListener でイベントを書いたことがある人

## 構成
この上なくシンプルな構成です

```ディレクトリ図.
 虫眼鏡
    ├── css
    │   └── style.css     
    ├── script
    │   └── sample.js  
    ├─── img
    │   ├── SamplePic.jpg
    │   └── LargeSamplePic.jpg
    └── index.html      
```

## ファイル一覧

```html:index.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="css/style.css">
    <title>Document</title>
</head>

<body>
    <div class="content">
        <div class="base_img_box">
            <img class="base_img" src='img/SamplePic.jpg' />
            <!-- :: htmlタグに直接関数を書く場合は↑上記一行のタグ内容を ↓下記一行に代える-->
            <!-- img class="base_img" src='img/SamplePic.jpg' onmousemove="zoomIn(event)" onmouseout="zoomOut(event)" /-->
        </div>
        <div class="lens_img_box">
            <img class="lens_img" src='img/LargeSamplePic.jpg' />
        </div>
    </div>
    <script type="text/javascript" src="script/sample.js"></script>
</body>

</html>
```

```css:style.css
.content {
    cursor: pointer;
    position: relative;
    margin: 30px;
}
.base_img_box {
    z-index: 0;
}

.lens_img_box {
    position: absolute;
    opacity:0;
    width: 200px;
    height: 200px;
    overflow: hidden;
    pointer-events: none;
    border-radius: 100%;
    border: solid 5px #ffffff;
    z-index: 1;
}

.lens_img{
    position: absolute;
    transform-origin: top left;
}
```



```javascript:sample.js

        // Dom  元画像
        let baseImg = document.querySelector(".base_img");
        
        // Dom  拡大画像Box (虫眼鏡)
        let lensImgBox = document.querySelector(".lens_img_box");
        
        // Dom  拡大画像
        let lensImg = document.querySelector(".lens_img");

        // 拡大比率　今回は2倍
        let ratio = 2;

        // 元画像の上をマウスポインタが通った場合、虫眼鏡を表示
        baseImg.addEventListener("mousemove", function (event) {
            // :: htmlタグに直接関数を書く場合は↑上記一行を ↓下記一行に代える
            // function zoomIn(event) {

            // 拡大画像Box(虫眼鏡...元画像の座標から100引いた値を設定)
            lensImgBox.style.opacity = 1;
            lensImgBox.style.top = (event.offsetY - 100) + "px";
            lensImgBox.style.left = (event.offsetX - 100) + "px";

            //　拡大画像 (元画像の座標から反転して100足した値を設定)
            let newLensOffsetY = event.offsetY * ratio * -1 + 100;
            let newLensOffsetX = event.offsetX * ratio * -1 + 100;

            lensImg.style.top = (newLensOffsetY) + "px";
            lensImg.style.left = (newLensOffsetX) + "px";
            // :: ↑上記二行は ↓下記一行でも実装可能
            // lensImg.style.transform = " translate(" + newLensOffsetX + "px, " + newLensOffsetY + "px)";
        
        }, false);
        // :: htmlタグに直接関数を書く場合は↑上記一行を ↓下記一行に代える
        // }
        
        // 元画像の上にマウスポインタがない場合、虫眼鏡を非表示
        baseImg.addEventListener("mouseout", function () {
            // :: htmlタグに直接関数を書く場合は↑上記一行を ↓下記一行に代える
            // function zoomOut(event) {
            lensImgBox.style.opacity = 0;

        }, false);
        // :: htmlタグに直接関数を書く場合は↑上記一行を ↓下記一行に代える
        // }
```
### SamplePic.jpg (元画像)
![SamplePic.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187949/f7599c98-13a2-d72b-9264-72a0c6f1bb39.jpeg)

### LargeSamplePic.jpg (拡大画像)
![LargeSamplePic.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187949/aaa7a87b-ff38-5c86-0cb5-ae33a1a5b570.jpeg)

## HTML 各パーツの構成
div ブロック content があり、中に 
	base_img_box > base_img
	lens_img_box > lens_img 
が入れ子になっています

※base_img_boxはdiv として切り出す必要はないが、便宜上、設置

## パーツのイメージ

lens_img_boxの位置は　position:absolute;を設定
親要素、content の位置を基準となるように設定しています

※詳しくは、ここらへんを：[CSSのposition: absoluteとrelativeとは](https://uxmilk.jp/63409)

ついでに、初期値は　opacity: 0;で透明にします。
pointer-events none なので、ユーザーは、
このlens_img_boxをクリックしたり選択したりすることはできません

※　z-indexは 無くても大丈夫だがこちらも便宜上設置、

## dom　取得

```javascript:sample.js
        // Dom  元画像
        let baseImg = document.querySelector(".base_img");
        
        // Dom  拡大画像Box (虫眼鏡)
        let lensImgBox = document.querySelector(".lens_img_box");
        
        // Dom  拡大画像
        let lensImg = document.querySelector(".lens_img");

        // 拡大比率　今回は2倍
        let ratio = 2;
```

document.querySelector を使ってdomを取得しています
今回は拡大率:ratio を 2倍に設定しています。


## 実現したいこと

![イメージ図.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187949/f7c99547-51c5-8ca1-f7e1-c52d484a3ffa.jpeg)

上記図は、Javascriptで虫眼鏡を実装している瞬間のイメージ図です。
マウスを元画像:SamplePic.jpgにホバーさせると
元画像:SamplePic.jpg に 拡大画像:LargeSamplePic.jpg が重なり
虫眼鏡:lens_img_box を被せ、なおかつ要素からはみ出した画像部分をhidden で見えなくすると、まるで画像が拡大されたように見えます

## Javascript offsetY offsetX 
画像要素のマウス座標を取得する場合
「offsetY」 「offsetX」を使います。
sample.jsを以下に書き換えると、単純に座標だけを出力するデモが作れます

```javascript:sample.js
// Dom  元画像
let baseImg = document.querySelector(".base_img");

// 元画像の上をマウスポインタが通った場合、座標を表示
baseImg.addEventListener("mousemove", function (event) {
    baseImg.innerText = ":X座標" + event.offsetX + ":Y座標" + event.offsetY;
}, false);
```

#### 実装画面
![offsetX_offsetY出力.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187949/fa81cdec-deae-e032-f67f-3d29ce019389.gif)

## 「-100」 ?

ホバーしたときに、拡大画像（虫眼鏡）の透明度を0から1に、
そして元画像の座標を、拡大画像(虫眼鏡)の座標にも反映させます、
event.offsetY offsetX をそれぞれ　-100してます

```javascript:sample.js
            // 拡大画像Box(虫眼鏡...元画像の座標から100引いた値を設定)
            lensImgBox.style.opacity = 1;
            lensImgBox.style.top = (event.offsetY - 100) + "px";
            lensImgBox.style.left = (event.offsetX - 100) + "px";
```

「event.offsetY - 100」
「event.offsetX - 100」
としている理由は以下。

+ lensImgBox.style.top = event.offsetY + "px";lensImgBox.style.left = event.offsetX + "px"; とした場合
![top.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187949/88fd299e-6b5c-7f56-620e-19c7852f2e59.png)

 + lensImgBox.style.top = (event.offsetY - 100) + "px";
            lensImgBox.style.left = (event.offsetX - 100) + "px";
とした場合

![center.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187949/db408cd9-a1eb-1e1d-e839-9ba0d6fa45ee.png)

ポインターの位置を中央にするために値を合わせています

##  「* -1」 ?

```javascript:sample.js
            //　拡大画像 (元画像の座標から反転して100足した値を設定)
            let newLensOffsetY = event.offsetY * ratio * -1 + 100;
            let newLensOffsetX = event.offsetX * ratio * -1 + 100;
```

元画像の座標を、拡大画像の座標にも反映させます、
event.offsetY offsetX にそれぞれ　マイナスを掛けています。

理由は以下

### 拡大画像、移動イメージ
![拡大画像、移動.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187949/1462d102-6e9d-a081-f04e-66c58ca96dcf.gif)

マウスを移動させると、元画像と拡大画像の移動方向は対になっていることがわかると思います。
なので　マイナス 1

## マウスアウトすると透明化を外す

```javascript:sample.js
        // 元画像の上にマウスポインタがない場合、虫眼鏡を非表示
        baseImg.addEventListener("mouseout", function () {
            // :: htmlタグに直接関数を書く場合は↑上記一行を ↓下記一行に代える
            // function zoomOut(event) {
            lensImgBox.style.opacity = 0;

        }, false);
        // :: htmlタグに直接関数を書く場合は↑上記一行を ↓下記一行に代える
        // }
```

マウスポイントを外すと拡大画像はまた元の透明度に戻ります

## 終了

今回は、style.top, style.lett, offsetY, offsetX, のプロパティを使って動的部分を作成しましたが

様々な条件で適したプロパティが変わってきます。
拡大比率、他にもいくつか条件が加わる場合は　 
lensImg.style.transform = " translate(" + newLensOffsetX + "px, " + newLensOffsetY + "px)";
等で実装したほうがいい場合もありますし

重なる順番、を変える場合は　css　に定義した
z-index で調整したほうがいい場合もあります。
[MDN:z-index](https://developer.mozilla.org/ja/docs/Web/CSS/z-index)
[MDN:transform](https://developer.mozilla.org/ja/docs/Web/CSS/transform)

最近は 「React」 「Vue.js」 など様々なフロントエンド系のライブラリ、フレームワークが増えてきていますが
素のHTML CSS が書けることが大前提ですし
こういった初歩的な機能の実装は
CSSハック的なコードに慣れていないと意外と苦戦すると思います。

JQueryがあまり使われなくなったり、ECMAScriptの規格が新しくなったり
ブラウザエンジンがモダンになったり、様々なJsライブラリが生まれては消えて行ったりと
玉石混交なフロントエンド界隈ですが、まずは素のコードが書けるようにしておいて損はないと思います。 

### おまけ
例では、あらかじめ、「元画像」と「拡大画像」を用意した状態で実装していますが、「拡大画像」を用意しない（元画像を流用する）形でも実装可能です。

「index.html」から

``` html:index.html
    <div class="lens_img_box">
        <img class="lens_img" src='img/LargeSamplePic.jpg' />
    </div>
```
の行を除去

「sample.js」を以下

```javascript:sample.js

// Dom  元画像Box
let baseImgBox = document.querySelector(".base_img_box");

baseImgBox.onload = setImgClone();

function setImgClone() {
    // Dom コンテント
    let content = document.querySelector(".content");

    // Dom 拡大画像Box(元画像Box のクローン)
    let lensImgBox = baseImgBox.cloneNode(true);
    // クラス名「lens_img_box」に変更
    lensImgBox.className = "lens_img_box";

    // Dom 拡大画像(lensImgBoxの子要素)
    let lensImg = lensImgBox.firstElementChild;
    // クラス名「lens_img」に変更
    lensImg.className = "lens_img";
    // 二倍に拡大
    lensImg.style.transform = "scale(2)";

    // 元画像のクローンを配置
    content.appendChild(lensImgBox);

    // Dom  元画像
    let baseImg = document.querySelector(".base_img");

    // 拡大比率　今回は2倍
    let ratio = 2;

    // 元画像の上をマウスポインタが通った場合、虫眼鏡を表示
    baseImg.addEventListener("mousemove", function (event) {

        // 拡大画像Box(虫眼鏡...元画像の座標から100引いた値を設定)
        lensImgBox.style.opacity = 1;
        lensImgBox.style.top = (event.offsetY - 100) + "px";
        lensImgBox.style.left = (event.offsetX - 100) + "px";

        //　拡大画像 (元画像の座標から反転して100足した値を設定)
        let newLensOffsetY = event.offsetY * ratio * -1 + 100;
        let newLensOffsetX = event.offsetX * ratio * -1 + 100;

        lensImg.style.top = (newLensOffsetY) + "px";
        lensImg.style.left = (newLensOffsetX) + "px";

    }, false);

    // 元画像の上にマウスポインタがない場合、虫眼鏡を非表示
    baseImg.addEventListener("mouseout", function () {

        lensImgBox.style.opacity = 0;

    }, false);
}


```

に書き換えると実装可能です
流れとしては


+「元画像Box」のクローンを作成、
+ class名を　「iens_img_box」 「lens_img」に設定
+ 「lens_img」のサイズを2倍に設定
+ 「mousemove」「mouseout」に「拡大」「拡大中止」のイベントを付与
上記4処理を　元画像Boxの読み込み時「baseImgBox.onload」の中に内包することで実装しています。