読み込み中...

JavaScriptでピンチアウトを実装する8つの方法

JavaScriptでピンチアウトを実装する8つの方法 JS
この記事は約46分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

●ピンチアウト(ピンチイン)とは?

ピンチアウトとは、スマートフォンやタブレットの画面上で2本の指を広げる動作のことを指します。

この動作によって、画面の拡大や縮小を行うことができます。

逆に、2本の指を狭める動作をピンチインと呼びます。

○ピンチアウトの使い方

ピンチアウトは、Webページや画像、地図アプリなどを拡大して詳細を見たいときに便利な機能です。

たとえば、小さな文字が読みにくい場合や、画像の細部を確認したい場合などに活用できます。

具体的な使い方としては、まず拡大したい部分に親指と人差し指を置きます。

そして、2本の指を広げるようにスライドさせると、画面が拡大されていきます。

拡大率は指の間隔に応じて変化するので、好みの大きさになるまで調整できます。

○スマホ・タブレットでのピンチアウト

ほとんどのスマートフォンやタブレットでは、標準でピンチアウトによる拡大縮小機能が搭載されています。

iPhoneやiPadのSafari、AndroidのChromeブラウザなどで、特別な設定をしなくてもピンチアウトが可能です。

ただ、Webサイトによってはピンチアウトを無効化している場合もあります。

その場合は、後述する方法でピンチアウトを有効にする必要があります。

●JavaScriptでピンチアウトを実装する方法

JavaScriptを使えば、Webアプリやスマホアプリでピンチアウトによる拡大縮小機能を実装できます。

ただ、どのように実装すればよいのか、初めは戸惑ってしまうかもしれません。

そこで、ここからはJavaScriptでピンチアウトを実現するための具体的な方法を、サンプルコードを交えながら詳しく解説していきます。

touchイベントやjQuery、Hammer.jsなど、さまざまなアプローチを紹介するので、自分に合った方法を見つけてみてください。

それでは、まずはtouchイベントを使ったサンプルコードから見ていきましょう。

touchイベントとは、スマートフォンやタブレットでタッチ操作を検出するためのイベントです。

これを利用することで、ピンチアウトのジェスチャーを判定できます。

○サンプルコード1:touchイベントを使う

// 要素の取得
const element = document.getElementById('target');

// ピンチアウトのジェスチャーを検出
let startDistance = 0;
let currentDistance = 0;
let initialScale = 1;
let currentScale = 1;

element.addEventListener('touchstart', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    startDistance = getDistance(event.touches[0], event.touches[1]);
    initialScale = currentScale;
  }
}, false);

element.addEventListener('touchmove', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    currentDistance = getDistance(event.touches[0], event.touches[1]);
    currentScale = initialScale * (currentDistance / startDistance);
    setScale(currentScale);
  }
}, false);

// 2点間の距離を計算する関数
function getDistance(touch1, touch2) {
  const dx = touch1.clientX - touch2.clientX;
  const dy = touch1.clientY - touch2.clientY;
  return Math.sqrt(dx * dx + dy * dy);
}

// 要素の拡大縮小を設定する関数
function setScale(scale) {
  element.style.transform = `scale(${scale})`;
}

このサンプルコードでは、touchstartイベントで2本指のタッチを検出し、指の間の距離を計算します。そして、touchmoveイベントで指の動きを追跡し、初期の距離からの変化量に応じて要素の拡大率を更新しています。

実行結果は以下のようになります。

<div id="target">
  <img src="sample.jpg" alt="サンプル画像">
</div>
#target {
  width: 100%;
  height: auto;
  transform-origin: center center;
}

上記のHTMLとCSSで要素を用意し、先ほどのJavaScriptコードを適用すると、画像をピンチアウトで拡大縮小できるようになります。

指を広げるほど画像が大きくなり、狭めるほど小さくなる挙動を実現できました。

touchイベントを使ったサンプルの説明は以上です。

touchイベントはスマートフォンやタブレット向けのアプリでよく使われる手法ですが、ピンチアウトに限らずさまざまなジェスチャーの検出に応用できます。

○サンプルコード2:jQueryを使う

つぎに、jQueryを使ってピンチアウトを実装する方法を見ていきます。

jQueryは、JavaScriptのライブラリのひとつで、よく使われる機能を簡単に呼び出せるようにしてくれます。

jQueryでピンチアウトを実現するには、pinchというイベントを使います。

次のサンプルコードを見てみましょう。

// jQueryを読み込む
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

<script>
$(function() {
  // 要素の取得
  const $element = $('#target');

  // ピンチアウトのジェスチャーを検出
  $element.on('pinch', function(event) {
    const scale = event.originalEvent.scale;
    setScale(scale);
  });

  // 要素の拡大縮小を設定する関数
  function setScale(scale) {
    $element.css('transform', `scale(${scale})`);
  }
});
</script>

jQueryを使うと、onメソッドでイベントリスナーを簡単に登録できます。

ここでは、pinchイベントをリッスンし、イベントオブジェクトから拡大率を取得しています。

あとは、cssメソッドで要素のスタイルを変更するだけです。

実行結果は次のようになります。

<div id="target">
  <img src="sample.jpg" alt="サンプル画像">
</div>
#target {
  width: 100%;
  height: auto;
  transform-origin: center center;
}

HTMLとCSSは先ほどと同じように用意します。jQueryのコードを適用することで、画像をピンチアウトで拡大縮小できます。

jQueryを使えば、わずか数行のコードでピンチアウトを実装できました。

jQueryは他にもさまざまなイベントやメソッドを提供しているので、覚えておくと便利です。

○サンプルコード3:Hammer.jsを使う

最後に、Hammer.jsというライブラリを使ってピンチアウトを実装する方法を紹介します。

Hammer.jsは、タッチジェスチャーを簡単に扱えるようにしてくれるライブラリです。

次のサンプルコードを見てみましょう。

// Hammer.jsを読み込む
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js"></script>

<script>
  // 要素の取得
  const element = document.getElementById('target');

  // Hammerインスタンスを作成
  const hammer = new Hammer(element);

  // ピンチアウトのジェスチャーを検出
  hammer.get('pinch').set({ enable: true });

  hammer.on('pinch', function(event) {
    const scale = event.scale;
    setScale(scale);
  });

  // 要素の拡大縮小を設定する関数
  function setScale(scale) {
    element.style.transform = `scale(${scale})`;
  }
</script>

Hammer.jsを使うには、まずHammerインスタンスを作成します。

引数には、ジェスチャーを検出したい要素を指定します。

そして、getメソッドでpinchジェスチャーを有効化し、onメソッドでイベントリスナーを登録します。

あとは、イベントオブジェクトから拡大率を取得し、要素のスタイルを変更するだけです。

実行結果は次のようになります。

<div id="target">
  <img src="sample.jpg" alt="サンプル画像">
</div>
#target {
  width: 100%;
  height: auto;
  transform-origin: center center;
}

HTMLとCSSは同じように用意します。

Hammer.jsのコードを適用することで、画像をピンチアウトで拡大縮小できます。

Hammer.jsを使えば、他にもさまざまなジェスチャーを簡単に扱えます。

ピンチアウトだけでなく、スワイプやタップなども検出できるので、ジェスチャーを活用したアプリ開発に役立つでしょう。

○サンプルコード5:zoomプロパティを使う

JavaScriptでピンチアウトを実装する別の方法として、CSSのzoomプロパティを使う方法があります。

zoomプロパティは、要素の拡大率を設定するためのものです。

JavaScriptからzoomプロパティを操作することで、ピンチアウトによる拡大縮小を実現できます。

それでは、サンプルコードを見ていきましょう。

// 要素の取得
const element = document.getElementById('target');

// ピンチアウトのジェスチャーを検出
let startDistance = 0;
let currentDistance = 0;
let initialZoom = 1;
let currentZoom = 1;

element.addEventListener('touchstart', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    startDistance = getDistance(event.touches[0], event.touches[1]);
    initialZoom = currentZoom;
  }
}, false);

element.addEventListener('touchmove', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    currentDistance = getDistance(event.touches[0], event.touches[1]);
    currentZoom = initialZoom * (currentDistance / startDistance);
    setZoom(currentZoom);
  }
}, false);

// 2点間の距離を計算する関数
function getDistance(touch1, touch2) {
  const dx = touch1.clientX - touch2.clientX;
  const dy = touch1.clientY - touch2.clientY;
  return Math.sqrt(dx * dx + dy * dy);
}

// 要素の拡大率を設定する関数
function setZoom(zoom) {
  element.style.zoom = zoom;
}

このサンプルコードは、先ほどのtouchイベントを使ったコードと似ています。

ただし、拡大率の設定にtransformプロパティではなくzoomプロパティを使っています。

touchstartイベントで初期の指の距離を計算し、touchmoveイベントで現在の指の距離から拡大率を算出します。

そして、setZoom関数で要素のzoomプロパティを更新することで、拡大縮小を実現しているのです。

実行結果は次のようになります。

<div id="target">
  <img src="sample.jpg" alt="サンプル画像">
</div>
#target {
  width: 100%;
  height: auto;
}

HTMLとCSSは前述の例と同じです。JavaScriptのコードを適用することで、画像をピンチアウトで拡大縮小できるようになります。

zoomプロパティを使う利点は、transformプロパティと比べてシンプルな記述で拡大縮小を実現できる点です。

ただし、zoomプロパティはIE8以前のバージョンでは使えないので、注意が必要です。

○サンプルコード6:CSS transformを使う

CSSのtransformプロパティを使って、ピンチアウトによる拡大縮小を実装することもできます。

transformプロパティは、要素に回転や拡大縮小、移動などの変形を加えるためのものです。

次のサンプルコードを見てみましょう。

// 要素の取得
const element = document.getElementById('target');

// ピンチアウトのジェスチャーを検出
let startDistance = 0;
let currentDistance = 0;
let initialScale = 1;
let currentScale = 1;

element.addEventListener('touchstart', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    startDistance = getDistance(event.touches[0], event.touches[1]);
    initialScale = currentScale;
  }
}, false);

element.addEventListener('touchmove', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    currentDistance = getDistance(event.touches[0], event.touches[1]);
    currentScale = initialScale * (currentDistance / startDistance);
    setTransform(currentScale);
  }
}, false);

// 2点間の距離を計算する関数
function getDistance(touch1, touch2) {
  const dx = touch1.clientX - touch2.clientX;
  const dy = touch1.clientY - touch2.clientY;
  return Math.sqrt(dx * dx + dy * dy);
}

// 要素の変形を設定する関数
function setTransform(scale) {
  element.style.transform = `scale(${scale})`;
}

このサンプルコードも、touchイベントを使ったコードと似ています。

ただし、拡大率の設定にzoomプロパティではなくtransformプロパティを使っています。

touchstartイベントで初期の指の距離を計算し、touchmoveイベントで現在の指の距離から拡大率を算出します。

そして、setTransform関数で要素のtransformプロパティを更新することで、拡大縮小を実現しているのです。

実行結果は次のようになります。

<div id="target">
  <img src="sample.jpg" alt="サンプル画像">
</div>
#target {
  width: 100%;
  height: auto;
  transform-origin: center center;
}

HTMLは前述の例と同じですが、CSSでtransform-originプロパティを設定しています。

これは、変形の基点を要素の中心にするためです。

JavaScriptのコードを適用することで、画像をピンチアウトで拡大縮小できるようになります。

transformプロパティを使えば、他にも回転や移動など、さまざまな変形効果を加えられます。

ただし、transformプロパティはIE9以前のバージョンでは使えないので、注意が必要です。

また、GPUアクセラレーションが効くブラウザでは、transformプロパティの方がzoomプロパティよりも高速に動作します。

○サンプルコード7:iframeを使う

iframe要素を使って、ピンチアウトによる拡大縮小を実装する方法もあります。

iframe要素は、別のHTMLページを埋め込むためのものです。

iframe要素のwidth属性とheight属性を操作することで、埋め込んだページの拡大縮小を実現できます。

次のサンプルコードを見てみましょう。

<iframe id="target" src="sample.html" width="100%" height="400px"></iframe>
// 要素の取得
const element = document.getElementById('target');

// ピンチアウトのジェスチャーを検出
let startDistance = 0;
let currentDistance = 0;
let initialWidth = element.offsetWidth;
let initialHeight = element.offsetHeight;
let currentWidth = initialWidth;
let currentHeight = initialHeight;

element.addEventListener('touchstart', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    startDistance = getDistance(event.touches[0], event.touches[1]);
  }
}, false);

element.addEventListener('touchmove', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    currentDistance = getDistance(event.touches[0], event.touches[1]);
    const scale = currentDistance / startDistance;
    currentWidth = initialWidth * scale;
    currentHeight = initialHeight * scale;
    setSize(currentWidth, currentHeight);
  }
}, false);

// 2点間の距離を計算する関数
function getDistance(touch1, touch2) {
  const dx = touch1.clientX - touch2.clientX;
  const dy = touch1.clientY - touch2.clientY;
  return Math.sqrt(dx * dx + dy * dy);
}

// 要素のサイズを設定する関数
function setSize(width, height) {
  element.style.width = `${width}px`;
  element.style.height = `${height}px`;
}

このサンプルコードでは、iframe要素にtouchstartイベントとtouchmoveイベントを登録しています。

touchstartイベントで初期の指の距離を計算し、iframe要素の初期サイズを取得します。

touchmoveイベントで現在の指の距離から拡大率を算出し、setSize関数でiframe要素のwidthheightを更新することで、拡大縮小を実現しているのです。

実行結果は次のようになります。

<iframe id="target" src="sample.html" width="100%" height="400px"></iframe>

iframe要素にsample.htmlを埋め込み、初期サイズを設定しています。

JavaScriptのコードを適用することで、iframe要素をピンチアウトで拡大縮小できるようになります。

iframe要素を使う利点は、別のページを丸ごと拡大縮小できる点です。

ただし、セキュリティ上の理由から、同一オリジン以外のページは埋め込めないので注意が必要です。

●ピンチアウトを無効にする方法

JavaScriptを使ってピンチアウトを実装する方法を見てきましたが、時にはピンチアウトを無効にしたい場合もあるでしょう。

たとえば、レイアウトが崩れてしまうようなコンテンツでは、ユーザーに意図しない拡大縮小をさせないほうがよいかもしれません。

そこで、ここではピンチアウトを無効にする方法を3つ紹介します。

HTMLやCSSを使う方法から、JavaScriptを使う方法まで、それぞれの特徴を踏まえながら解説していきますので、ぜひ参考にしてみてください。

○HTMLのmetaタグを使う

ピンチアウトを無効にする最も簡単な方法は、HTMLのmetaタグを使うことです。

次のように、viewportuser-scalable属性をnoに設定するだけです。

<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">

このように設定すると、ユーザーがピンチアウトで拡大縮小しようとしても、何も反応しなくなります。

ただし、この方法ではピンチアウトだけでなく、他の方法での拡大縮小も一切できなくなるので注意が必要です。

実行結果は次のようになります。

<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
</head>
<body>
  <div>
    <img src="sample.jpg" alt="サンプル画像">
  </div>
</body>
</html>

上記のHTMLを表示すると、画像をピンチアウトで拡大縮小しようとしても、何も起こりません。

シンプルに拡大縮小を禁止したい場合は、この方法が手っ取り早いでしょう。

○CSSのtouch-actionプロパティを使う

次に、CSSのtouch-actionプロパティを使う方法を見ていきます。

touch-actionプロパティは、タッチ操作をどのように処理するかを指定するためのものです。

次のように、touch-actionプロパティにpinch-zoomを指定することで、ピンチアウトによる拡大縮小を無効にできます。

body {
  touch-action: pinch-zoom;
}

この方法なら、ピンチアウト以外の操作は通常通り行えます。たとえば、ダブルタップで拡大することもできます。

実行結果は次のようになります。

<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      touch-action: pinch-zoom;
    }
  </style>
</head>
<body>
  <div>
    <img src="sample.jpg" alt="サンプル画像">
  </div>
</body>
</html>

上記のHTMLとCSSを適用すると、画像をピンチアウトで拡大縮小できなくなります。

ただし、ダブルタップでの拡大は可能です。

touch-actionプロパティを使えば、ピンチアウトだけを無効にしつつ、他の操作は許可するといった細かい制御ができるので便利です。

ただし、このプロパティはIE11以前のバージョンでは使えないので注意が必要です。

○JavaScriptでタッチイベントを無効にする

最後に、JavaScriptを使ってタッチイベントを無効にする方法を紹介します。

具体的には、touchstartイベントとtouchmoveイベントのデフォルトの動作をキャンセルすることで、ピンチアウトを無効化します。

次のサンプルコードを見てみましょう。

// タッチイベントを無効にする関数
function disableTouchEvent(event) {
  if (event.touches.length > 1) {
    event.preventDefault();
  }
}

// タッチイベントのリスナーを登録
document.addEventListener('touchstart', disableTouchEvent, { passive: false });
document.addEventListener('touchmove', disableTouchEvent, { passive: false });

このサンプルコードでは、disableTouchEvent関数を定義し、touchstartイベントとtouchmoveイベントのリスナーとして登録しています。

disableTouchEvent関数内では、event.touches.lengthでタッチ点の数を判定し、2点以上の場合にevent.preventDefault()を呼び出してイベントのデフォルト動作をキャンセルしています。

これにより、ピンチアウトが無効化されるのです。

実行結果は次のようになります。

<!DOCTYPE html>
<html>
<head>
  <script>
    // タッチイベントを無効にする関数
    function disableTouchEvent(event) {
      if (event.touches.length > 1) {
        event.preventDefault();
      }
    }

    // タッチイベントのリスナーを登録
    document.addEventListener('touchstart', disableTouchEvent, { passive: false });
    document.addEventListener('touchmove', disableTouchEvent, { passive: false });
  </script>
</head>
<body>
  <div>
    <img src="sample.jpg" alt="サンプル画像">
  </div>
</body>
</html>

上記のHTMLとJavaScriptを適用すると、画像をピンチアウトで拡大縮小できなくなります。

ただし、ダブルタップでの拡大は可能です。

JavaScriptを使う利点は、柔軟にイベントをコントロールできる点です。

たとえば、特定の条件下でだけピンチアウトを無効にするといったことも可能です。

ただし、addEventListenerの第3引数で{ passive: false }を指定している点に注意が必要です。

これは、preventDefault()を呼び出すために必要な設定ですが、パフォーマンスに影響を与える可能性があります。

●よくあるエラーと対処法

JavaScriptでピンチアウトを実装する際、思わぬエラーに遭遇することがあります。

初心者の方は特に、どこに問題があるのかわからず、頭を抱えてしまうかもしれません。

ここでは、ピンチアウトに関するよくあるエラーとその対処法を3つ紹介します。

これを押さえておけば、つまずきを減らし、スムーズに実装できるでしょう。

それでは、具体的なエラーとその解決方法を見ていきましょう。

きっと、みなさんが抱えている悩みにも答えがあるはずです。

一緒に、JavaScriptのスキルアップを目指していきましょう!

○ピンチアウトができない場合

ピンチアウトを実装したつもりでも、動作しないことがあります。

その原因のひとつが、タッチイベントの設定ミスです。

たとえば、次のようなコードを書いたとします。

// 要素の取得
const element = document.getElementById('target');

// ピンチアウトのジェスチャーを検出
element.addEventListener('touchstart', function(event) {
  // 処理を記述
}, false);

element.addEventListener('touchmove', function(event) {
  // 処理を記述
}, false);

このコードでは、touchstartイベントとtouchmoveイベントのリスナーを登録していますが、肝心の処理が記述されていません。

これでは、ピンチアウトが動作しないのも当然です。

正しくは、次のように処理を記述する必要があります。

// 要素の取得
const element = document.getElementById('target');

// ピンチアウトのジェスチャーを検出
let startDistance = 0;
let currentDistance = 0;
let initialScale = 1;
let currentScale = 1;

element.addEventListener('touchstart', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    startDistance = getDistance(event.touches[0], event.touches[1]);
    initialScale = currentScale;
  }
}, false);

element.addEventListener('touchmove', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    currentDistance = getDistance(event.touches[0], event.touches[1]);
    currentScale = initialScale * (currentDistance / startDistance);
    setScale(currentScale);
  }
}, false);

// 2点間の距離を計算する関数
function getDistance(touch1, touch2) {
  const dx = touch1.clientX - touch2.clientX;
  const dy = touch1.clientY - touch2.clientY;
  return Math.sqrt(dx * dx + dy * dy);
}

// 要素の拡大縮小を設定する関数
function setScale(scale) {
  element.style.transform = `scale(${scale})`;
}

このように、touchstartイベントで初期の指の距離を計算し、touchmoveイベントで現在の指の距離から拡大率を算出します。

そして、setScale関数で要素の拡大縮小を設定しています。

処理を正しく記述することで、ピンチアウトが動作するようになります。

コードを書く際は、イベントリスナーの登録だけでなく、中身の処理もきちんと記述しましょう。

○ズームが固定されてしまう場合

ピンチアウトを実装したら、次は拡大率の制限を設けたくなるかもしれません。

でも、うっかりコードを書き間違えると、ズームが固定されて動かなくなってしまうことがあります。

たとえば、次のようなコードを書いたとします。

// 要素の取得
const element = document.getElementById('target');

// ピンチアウトのジェスチャーを検出
let startDistance = 0;
let currentDistance = 0;
let initialScale = 1;
let currentScale = 1;

element.addEventListener('touchstart', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    startDistance = getDistance(event.touches[0], event.touches[1]);
    initialScale = currentScale;
  }
}, false);

element.addEventListener('touchmove', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    currentDistance = getDistance(event.touches[0], event.touches[1]);
    currentScale = initialScale * (currentDistance / startDistance);

    // 拡大率を1倍に固定
    currentScale = 1;

    setScale(currentScale);
  }
}, false);

// 2点間の距離を計算する関数
function getDistance(touch1, touch2) {
  const dx = touch1.clientX - touch2.clientX;
  const dy = touch1.clientY - touch2.clientY;
  return Math.sqrt(dx * dx + dy * dy);
}

// 要素の拡大縮小を設定する関数
function setScale(scale) {
  element.style.transform = `scale(${scale})`;
}

このコードでは、touchmoveイベントの処理の中で、currentScaleを常に1に設定しています。

その結果、拡大率が1倍に固定され、ピンチアウトが機能しなくなってしまいます。

正しくは、次のように拡大率の範囲を制限する必要があります。

element.addEventListener('touchmove', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    currentDistance = getDistance(event.touches[0], event.touches[1]);
    currentScale = initialScale * (currentDistance / startDistance);

    // 拡大率の範囲を制限
    currentScale = Math.min(Math.max(currentScale, 0.5), 2);

    setScale(currentScale);
  }
}, false);

このように、Math.minMath.maxを使って拡大率の最小値と最大値を設定することで、拡大率を制限しつつピンチアウトを機能させることができます。

拡大率を制限する際は、固定値を設定するのではなく、適切な範囲を指定するようにしましょう。

また、アプリの要件に合わせて、最小値と最大値を調整するのも大切です。

○意図しない拡大縮小が発生する場合

ピンチアウトを実装すると、意図しない拡大縮小が発生することがあります。

たとえば、要素内のテキストを選択しようとしたときに、うっかりピンチアウトが動作してしまうようなケースです。

これを防ぐには、ピンチアウトのジェスチャーを適切に判定する必要があります。

次のように、touchstartイベントとtouchmoveイベントの両方で、タッチ点の数をチェックするのがポイントです。

// 要素の取得
const element = document.getElementById('target');

// ピンチアウトのジェスチャーを検出
let startDistance = 0;
let currentDistance = 0;
let initialScale = 1;
let currentScale = 1;

element.addEventListener('touchstart', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    startDistance = getDistance(event.touches[0], event.touches[1]);
    initialScale = currentScale;
  }
}, false);

element.addEventListener('touchmove', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    currentDistance = getDistance(event.touches[0], event.touches[1]);
    currentScale = initialScale * (currentDistance / startDistance);
    setScale(currentScale);
  }
}, false);

// 2点間の距離を計算する関数
function getDistance(touch1, touch2) {
  const dx = touch1.clientX - touch2.clientX;
  const dy = touch1.clientY - touch2.clientY;
  return Math.sqrt(dx * dx + dy * dy);
}

// 要素の拡大縮小を設定する関数
function setScale(scale) {
  element.style.transform = `scale(${scale})`;
}

このように、touchstartイベントとtouchmoveイベントの両方でevent.touches.lengthをチェックし、2本指でタッチされている場合のみ処理を実行するようにします。

こうすることで、1本指での操作時はピンチアウトが動作しなくなるので、意図しない拡大縮小を防ぐことができます。

ユーザビリティの観点からも、適切なジェスチャー判定は欠かせません。

また、状況によってはtouchendイベントやtouchcancelイベントでも処理を記述し、ジェスチャーの終了時に拡大率をリセットするなどの工夫も必要でしょう。

●ピンチアウトの応用例

ここまで、JavaScriptでピンチアウトを実装する方法やエラー対処法について詳しく見てきました。

でも、実際のアプリ開発では、ピンチアウトをどのように活用すればよいのでしょうか?

そこで、ここからはピンチアウトの応用例を3つ紹介します。

画像ビューワーや地図アプリ、プレゼンテーションアプリなど、実践的なサンプルコードを交えながら解説していきますので、ぜひ参考にしてみてください。

これまで学んだ知識を活かして、より実用的なアプリ開発に挑戦してみましょう。

きっと、JavaScriptのスキルアップにつながるはずです。それでは、早速見ていきましょう!

○サンプルコード9:画像ビューワーを作る

まずは、ピンチアウトを使った画像ビューワーを作ってみましょう。

ユーザーが画像をピンチアウトで拡大縮小できるようにすることで、より直感的な操作性を実現できます。

次のサンプルコードを見てみましょう。

<div id="viewer">
  <img src="sample.jpg" alt="サンプル画像">
</div>
#viewer {
  width: 100%;
  height: 400px;
  overflow: hidden;
}

#viewer img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  transform-origin: center center;
}
// 要素の取得
const viewer = document.getElementById('viewer');
const image = viewer.querySelector('img');

// ピンチアウトのジェスチャーを検出
let startDistance = 0;
let currentDistance = 0;
let initialScale = 1;
let currentScale = 1;

viewer.addEventListener('touchstart', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    startDistance = getDistance(event.touches[0], event.touches[1]);
    initialScale = currentScale;
  }
}, false);

viewer.addEventListener('touchmove', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    currentDistance = getDistance(event.touches[0], event.touches[1]);
    currentScale = initialScale * (currentDistance / startDistance);
    setScale(currentScale);
  }
}, false);

// 2点間の距離を計算する関数
function getDistance(touch1, touch2) {
  const dx = touch1.clientX - touch2.clientX;
  const dy = touch1.clientY - touch2.clientY;
  return Math.sqrt(dx * dx + dy * dy);
}

// 画像の拡大縮小を設定する関数
function setScale(scale) {
  image.style.transform = `scale(${scale})`;
}

このサンプルコードでは、#viewer要素内にimg要素を配置し、画像を表示しています。

そして、#viewer要素にtouchstartイベントとtouchmoveイベントを登録し、ピンチアウトのジェスチャーを検出しています。

touchstartイベントで初期の指の距離を計算し、touchmoveイベントで現在の指の距離から拡大率を算出します。

算出した拡大率は、setScale関数でimg要素のtransformプロパティに設定されます。

実行結果

<div id="viewer">
  <img src="sample.jpg" alt="サンプル画像">
</div>
#viewer {
  width: 100%;
  height: 400px;
  overflow: hidden;
}

#viewer img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  transform-origin: center center;
}

上記のHTMLとCSSを適用し、JavaScriptのコードを動作させると、画像をピンチアウトで拡大縮小できるようになります。

overflow: hiddenを設定することで、拡大時に画像がはみ出ないようにしています。

このように、ピンチアウトを使えば、シンプルな画像ビューワーを実装できます。

画像のサイズや配置を調整することで、より見やすいビューワーに仕上げていきましょう。

○サンプルコード10:地図アプリを作る

つぎに、ピンチアウトを使った地図アプリを作ってみましょう。

GoogleマップなどのWeb地図サービスでは、ピンチアウトによる地図の拡大縮小が一般的です。

次のサンプルコードを見てみましょう。

<div id="map"></div>
#map {
  width: 100%;
  height: 400px;
}
// 地図の初期化
function initMap() {
  const map = new google.maps.Map(document.getElementById('map'), {
    center: { lat: 35.6809591, lng: 139.7673068 },
    zoom: 12
  });

  // ピンチアウトのジェスチャーを検出
  let startDistance = 0;
  let currentDistance = 0;
  let initialZoom = map.getZoom();
  let currentZoom = initialZoom;

  map.addListener('touchstart', function(event) {
    // 2本指でタッチされている場合のみ処理
    if (event.touches.length === 2) {
      startDistance = getDistance(event.touches[0], event.touches[1]);
      initialZoom = currentZoom;
    }
  });

  map.addListener('touchmove', function(event) {
    // 2本指でタッチされている場合のみ処理
    if (event.touches.length === 2) {
      currentDistance = getDistance(event.touches[0], event.touches[1]);
      currentZoom = initialZoom + Math.log2(currentDistance / startDistance);
      map.setZoom(currentZoom);
    }
  });

  // 2点間の距離を計算する関数
  function getDistance(touch1, touch2) {
    const dx = touch1.clientX - touch2.clientX;
    const dy = touch1.clientY - touch2.clientY;
    return Math.sqrt(dx * dx + dy * dy);
  }
}

このサンプルコードでは、Google Maps APIを使って地図を表示しています。

initMap関数内で、google.maps.Mapコンストラクタを使って地図を初期化し、東京の座標を中心に表示しています。

そして、地図にtouchstartイベントとtouchmoveイベントを登録し、ピンチアウトのジェスチャーを検出しています。

touchstartイベントで初期の指の距離とズームレベルを取得し、touchmoveイベントで現在の指の距離から新しいズームレベルを算出します。

算出したズームレベルは、map.setZoomメソッドで地図に設定されます。

ズームレベルの計算には、Math.log2を使って対数を求めています。

これにより、指の距離の変化に応じて滑らかにズームできます。

実行結果は次のようになります。

<div id="map"></div>
#map {
  width: 100%;
  height: 400px;
}

上記のHTMLとCSSを適用し、JavaScriptのコードを動作させると、地図をピンチアウトで拡大縮小できるようになります。

ズームレベルに応じて、地図の詳細度が変化するのが確認できるでしょう。

このように、ピンチアウトを使えば、直感的な操作性を備えた地図アプリを実装できます。

実際のアプリ開発では、マーカーの表示や経路検索など、さまざまな機能を組み合わせていくことになります。

今回のサンプルコードを参考に、オリジナルの地図アプリ開発に挑戦してみてはいかがでしょうか。

Google Maps APIの公式ドキュメントも参照しながら、理解を深めていきましょう。

○サンプルコード11:プレゼンテーションアプリを作る

最後に、ピンチアウトを使ったプレゼンテーションアプリを作ってみましょう。

スライドをピンチアウトで拡大縮小できるようにすることで、プレゼンテーションの自由度が高まります。

次のサンプルコードを見てみましょう。

<div id="presentation">
  <div class="slide">
    <h2>スライド1</h2>
    <p>ここにスライドの内容を記述します。</p>
  </div>
  <div class="slide">
    <h2>スライド2</h2>
    <p>ここにスライドの内容を記述します。</p>
  </div>
  <div class="slide">
    <h2>スライド3</h2>
    <p>ここにスライドの内容を記述します。</p>
  </div>
</div>
#presentation {
  width: 100%;
  height: 400px;
  overflow: hidden;
  position: relative;
}

.slide {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  opacity: 0;
  transition: opacity 0.5s;
  transform-origin: center center;
}

.slide.active {
  opacity: 1;
}
// 要素の取得
const presentation = document.getElementById('presentation');
const slides = presentation.querySelectorAll('.slide');

// 初期スライドを表示
let currentIndex = 0;
slides[currentIndex].classList.add('active');

// ピンチアウトのジェスチャーを検出
let startDistance = 0;
let currentDistance = 0;
let initialScale = 1;
let currentScale = 1;

presentation.addEventListener('touchstart', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    startDistance = getDistance(event.touches[0], event.touches[1]);
    initialScale = currentScale;
  }
}, false);

presentation.addEventListener('touchmove', function(event) {
  // 2本指でタッチされている場合のみ処理
  if (event.touches.length === 2) {
    currentDistance = getDistance(event.touches[0], event.touches[1]);
    currentScale = initialScale * (currentDistance / startDistance);
    setScale(currentScale);
  }
}, false);

// 2点間の距離を計算する関数
function getDistance(touch1, touch2) {
  const dx = touch1.clientX - touch2.clientX;
  const dy = touch1.clientY - touch2.clientY;
  return Math.sqrt(dx * dx + dy * dy);
}

// スライドの拡大縮小を設定する関数
function setScale(scale) {
  slides[currentIndex].style.transform = `scale(${scale})`;
}

// スライドを切り替える関数
function changeSlide(index) {
  slides[currentIndex].classList.remove('active');
  currentIndex = index;
  slides[currentIndex].classList.add('active');
  currentScale = 1;
  setScale(currentScale);
}

// スライドの切り替えイベントを設定
presentation.addEventListener('click', function() {
  const nextIndex = (currentIndex + 1) % slides.length;
  changeSlide(nextIndex);
}, false);

このサンプルコードでは、#presentation要素内に複数の.slide要素を配置し、スライドを表現しています。

初期状態では、最初のスライドにactiveクラスを付与し、表示しています。

そして、#presentation要素にtouchstartイベントとtouchmoveイベントを登録し、ピンチアウトのジェスチャーを検出しています。

touchstartイベントで初期の指の距離を計算し、touchmoveイベントで現在の指の距離から拡大率を算出します。

算出した拡大率は、setScale関数で現在のスライドのtransformプロパティに設定されます。

また、clickイベントを登録し、クリックされたらスライドを切り替えるようにしています。

実行結果

<div id="presentation">
  <div class="slide">
    <h2>スライド1</h2>
    <p>ここにスライドの内容を記述します。</p>
  </div>
  <div class="slide">
    <h2>スライド2</h2>
    <p>ここにスライドの内容を記述します。</p>
  </div>
  <div class="slide">
    <h2>スライド3</h2>
    <p>ここにスライドの内容を記述します。</p>
  </div>
</div>
#presentation {
  width: 100%;
  height: 400px;
  overflow: hidden;
  position: relative;
}

.slide {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  opacity: 0;
  transition: opacity 0.5s;
  transform-origin: center center;
}

.slide.active {
  opacity: 1;
}

上記のHTMLとCSSを適用し、JavaScriptのコードを動作させると、スライドをピンチアウトで拡大縮小できるようになります。

クリックするとスライドが切り替わり、それぞれのスライドで拡大縮小が可能です。

まとめ

JavaScriptを使ったピンチアウトの実装方法について、詳しく見てきましたが、いかがでしたか?

最初は戸惑うこともあったかもしれませんが、サンプルコードを参考に、少しずつ理解が深まったのではないでしょうか。

この記事が、みなさんのJavaScriptの学習に少しでも役立てば幸いです。

ピンチアウトを始めとする、さまざまなUI操作の実装に挑戦し、ユーザーにとって使いやすく価値のあるアプリを開発していきましょう。