マウス座標の中心で
画像を拡大縮小
させるJavascript
ポインタやマウス座標の中心で
魔法のように自在に
画像を拡大縮小させるJavascript
ドラッグもできるよ

image: マウス座標の中心で画像を拡大縮小させるJavascript
Toplabo
zoom in/out an image around the mouse coordinates

マウス座標を中心に拡大縮小するとはどういうことか

 | 2024/02/19

経緯

苦節5年の懸案事項
  • やっと構想を実現できるスキルが身についたようだ。

    実はこのマウス座標を中心に拡大縮小、思い立ったのは5年ほど前になります。画像のビュアーをjqueryを利用して制作した時です。
    当時はスキルが追い付かず、というかマウス座標を中心に拡大縮小するロジックが解析できず、実際に着手したのは去年の12月頃です。
    着想からだと実に5年以上になります。感慨深いですね。
    その間、google検索や、ChatGPTなどで「マウス座標を中心に拡大縮小」を調べましたが、ろくな検索結果がありませんでした。サンプルあっても、デモページが無かったり、動かないデモだったり、最初の拡大だけのサンプルだったり、GIFアニメだったり・・・全部ブロックしました。
    一番参考になったのはphotoshopです。photoshopをじっくり眺め解析しました。
    大事なのはロジック。photoshopを眺めていて気が付いたのがドラッグと拡大縮小が分離すればいいじゃんってこと。そしてその順番。これが一番のヒントがなりました。実際にphotoshopそうなってるかは知りませんが・・・。

    どんなものかのデモ

    ホイールを使って画像をマウス座標を中心に拡大縮小し、ドラッグ移動ができるサンプルです。
    ES6以上で制作してあります。jQueryなどは無しです。※一番下にもリンクを置いてます。
    当然ですが、スマホには、すでに同様機能があるので対応していません。
    マウス座標を中心に拡大縮小のデモ。ドラッグにも対応:https://labo.studio-happyvalley.com/interactjs/center/

基本

transform-origin: を考える
  • WEBでは拡大縮小はtransformを使うのが一般的。

    WEBでは拡大縮小はtransformを使うのが一般的で効率も良いです。
    そしてその変更の基準がtransform-originです。
    trasform: top left;で左上を基準に拡大縮小や回転、移動ができます。trasform: center;でその基準が要素の中心になります。
    この場合、基準は変形する要素であり、ブラウザではありません。
    そこで最初に考えるのはマウスの座標をtransform-originにすればよいという方法です。
    ところがこの方法はうまくいきません。その理由を次で説明します。

    https://studio-happyvalley.com/wp/wp-content/uploads/fig1-17.png任意のマウス座標を中心に2倍に拡大した仮想図。 transform-originをマウス座標と仮定。https://studio-happyvalley.com/wp/wp-content/uploads/fig2-13.png次にマウスを元画像の左上に移動し、transform-originを変更します。するとその瞬間…https://studio-happyvalley.com/wp/wp-content/uploads/fig3-10.png次にマウスを元画像の左上に移動し、transform-originを変更します。するとその瞬間…

    transform-originを変更した瞬間、移動した座標をtransform-originにした変形に変更されます。
    これはransformの変形が終了しても変形前のパラメータが随時保持されているためです。
    なんとなく変形ごとに元の要素のデータも変更されるのかと思ってしまいますが、常にもともとの要素のwidthとheightを基準に変更されます。

探求

transform-origin: を使わない方法を考える
  • 他の方法を探求

    transform-originが使えないのであればtranslateを使う他、方法はありません。
    つまり座標が変動するたびにその差分をtranslateしてやるという考えです。
    また、transform-originは左上に固定します。
    WEBの座標は左上が基準なのでこれが一番効率が良いです。

    さて、下図をご覧あれ。図Aが元画像。画像内の右下の十字点が仮のマウス座標です。
    BはAを左上を基点に2倍に拡大した画像。
    そして、Cはマウス座標を基準した場合の2倍の拡大画像。
    つまりBがCになればよいわけです。そのためにはD分のX座標、Y座標を移動すればよいことになります。
    ということで使用するメソッドは、transform-ogrigin: left top固定で、transform: scale(), transform: translate()の二つということになります。

    https://studio-happyvalley.com/wp/wp-content/uploads/fig4-6.png

実証

実際に理論通りか構築し実験してみる
  • さて、理論は構築できたので、実際にその通りになるか試してみます。

    準備したのは基画像とそれを2倍にし、上図のD分だけ移動したサンプルです。これのそれぞれの座標がぴったり重なれば理論は正しかったことになります。
    まず画像の準備。1200ⅹ800の画像を準備、それぞれの同じ箇所に仮想のクリック座標を記入。次に最初の倍率をscale:1で考え何倍にするか?
    複数パターンの材料が欲しかったので4~5回変倍することに。
    ということで次のような式を構築。
    複数の画像にそれぞれ倍率を設定し、それらを配列へ格納、forEachで個別に構築した式を割り当てます。
    これですべての画像の仮想クリック座標同じ座標になれば構築した式が正しいことになります。

    const p={
    	_orgX: 1,
    	get orgX(){return this._orgX},
    	set orgX(v){this._orgX = v},
    	_orgY: 1,
    	get orgY(){return this._orgY},
    	set orgY(v){this._orgY = v},
    	_scale: 1,
    	get scale(){return this._scale},
    	set scale(v){this._scale = v},
    	_mousePoints: {x: 999, y: 598},
    	get mousePoints(){
    		return this._mousePoints;
    	},
    	set mousePoints(v){
    		if(v){
    			this._mousePoints= v;
    		}else{
    			this._mousePoints= {x: this._wW / 2, y: this._wH / 2};
    		}
    	},
    };
    
    const zoomImgs= document.querySelectorAll('#prvBlc img');
    zoomImgs.forEach( (item, ) => {
    	const scale = Math.round( item.dataset.scale *100) / 100;
    	p.scale= scale;
    	p.orgX= p.mousePoints.x * (scale - 1) * -1;
    	p.orgY= p.mousePoints.y * (scale - 1) * -1;
    	item.style.transform= 'translate( '+p.orgX+'px, '+p.orgY+'px) scale('+scale+')';
    });
    どうやら成功しました。

    ブラウザで表示してみてください。
    きちんと座標が重なっているでしょう。式は正しかったことになります。
    次は実際にwheelイベントで拡大機能と一緒にしてみます。

    下層マウス座標を中心とした拡大縮小テスト:https://labo.studio-happyvalley.com/interactjs/simple/

注意点

Transformの注意すべき点。(重要)
  • 実装の前に基本を検証

    transformを使うとき非常に重要なことがあります。それは順番。変形方法にはtranslate, scale,rotateの三つありますがこの順番次第で変形結果の形状はまったく違ってきます。たとえば今回使用予定のtranslate, scale,。これらも順番を正しくしないと期待通りの結果は得られません。
    次にその検証です。先にscaleを先するサンプルと後にscaleするサンプルを組み合わせたコードです。

    /*Aが元画像、*/
    #prvBlc img#normal{
    	transform: scale(1) translate(0, 0);
    }
    
    /*B左上が先にsacle2それからtranslate*/
    #prvBlc img#scale{
    	transform: scale(2) translate(-300px, -200px);
    }
    
    /*C右下が先にtranslateそれからsacle2*/
    #prvBlc img#translate{
    	transform: translate(-300px, -200px) scale(2);
    }
    https://studio-happyvalley.com/wp/wp-content/uploads/fig5-2.png
    ここまで変わるのわかりますか?

    transformの順番による違い:https://labo.studio-happyvalley.com/interactjs/test/

    もうひとつの注意点、初期画像のサイズ

    もうひとつの注意点として、初期画像のサイズがあります。
    つまり、最初の画像が小さいサイズだと、拡大してもぼそぼその画像になるだけです。ドット絵を拡大するようなものです。
    拡大した後に高解像度画像と差し替える方法もありますが、スマートではないです。
    今回はもともとの高解像度画像を初期表示ではブラウザサイズに縮小して表示する仕様にしています。
    これで拡大してもきれいな画像が表示されます。
    PC専用だし、1点だけなので重くはなりません。
    このプログラムではベストな選択だと思います。

完成

マウス座標を中心に拡大縮小とドラッグにも対応したコード
  • いろいろな研鑽を重ねついに完成しました。

    上記のほかにもドラッグするたびに座標が飛ぶ現象や、拡大率による長さの割合などいくつもの難関がまだまだ立ちふさがりましたがついに完成しました。
    基本構造は親子になっていて、親が拡大縮小専用。子供がドラッグ専用です。分離することで計算を簡単にしました。
    特に拡大縮小の影響が計算に影響しないように親側を拡大縮小専用に設定してあります。
    言うまでもなく、拡大縮小はマウスホィールで行います。
    ちなみにドラッグはinteractjsというモジュールを使っています。
    また、wheelエベントと拡大縮小のサンプルはMDNから参考してます。

    かなり長くなったので、後半はかなり端折りました。機会をみてまた、解説したいと思います。
    ちなみにこのプログラムはPC専用です。
    スマホではピンチで拡大縮小できますから。

    interactjs公式:https://interactjs.io/
    MDN|開発者向けのウェブ技術Element: wheel イベント:https://developer.mozilla.org/ja/docs/Web/API/Element/wheel_event
    マウス座標を中心に拡大縮小のデモ。ドラッグにも対応:https://labo.studio-happyvalley.com/interactjs/center/
    マウス座標中心に拡大縮小を実装した画像ビュアー:Search Gallery:https://labo.studio-happyvalley.com/freeview/?imgDir=org&imgConv=on

『マウス座標の中心で画像を拡大縮小させるJavascript』関連のお薦め

このページで紹介しているプログラムやビジュアルなどご依頼いただければ実装を賜ります。
お問い合わせはこちら