🐧

画像を拡大表示するダイアローグの実装方法について(React, Vue対応)

2022/10/05に公開

こんにちは、りょーたです

本記事では、以下のように、画像をクリックしたらダイアローグが開いて拡大表示される機能の実装方法について紹介します。セマンティックなHTMLコードを意識しましたので、ぜひ参考にしていただけたらと思います。

公開してから気づきましたが、Zennでも実装されていたので試してみてください笑

また、私のポートフォリオ内のブログで実際に動作をご確認いただけますので、気になった方は合わせてご覧ください🙇‍♂️

https://www.ryotanny.com/blog

また、Vue.jsとReact.jsによるサンプルも記事の最後の方に載せていますのでご参考ください。(こちらからジャンプできます)
バージョン情報は以下の通りです。

"react": "18.2.0",
"vue": "3.2.26",

以降、実装方法の紹介は、サンプルで作成したReact.jsのコードを元にします。

実装方法

HTML

まず、ベースとなる、画像とダイアローグのHTMLの実装をします。

{/* 常時表示しておく画像 */}
<div role="button" className="image-box" tabIndex={0}>
  <img src={src} alt={alt} width="300" height="200" />
</div>
{/* クリックした際に表示するダイアローグ */}
<dialog className="image-dialog">
  <div className="contents">
    <img src={src} alt={alt} width="300" />
  </div>
</dialog>

JavaScript

ここから、クリックした際にダイアローグを表示するために、イベントハンドラに渡す関数を実装します。(HTMLの部分で変更した行にハイライトを当てています)

export const ImageDialog = ({ src, alt = '' }: Props) => {
  const ref = useRef<HTMLDialogElement | null>(null);
  /**
   * ダイアローグを開く処理
   */
  const handleOpenDialog = useCallback(() => {
    if (ref.current) ref.current.showModal();
  }, [ref]);
  /**
   * ダイアローグを閉じる処理
   */
  const handleCloseDialog = useCallback(() => {
    if (ref.current) ref.current.close();
  }, [ref]);
  /**
   * ダイアローグ内のクリックイベントハンドラーに渡す処理
   */
  const handleClickInDialog = useCallback(
    (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
      e.stopPropagation();
    },
    []
  );
  return (
    <React.Fragment>
      <div
        role="button"
+        onClick={handleOpenDialog}
+        onKeyDown={handleOpenDialog}
        className="image-box"
        tabIndex={0}
      >
        <img src={src} alt={alt} width="300" height="200" />
      </div>
      <dialog
+       ref={ref}
       className="image-dialog"
+       onClick={handleCloseDialog}
      >
        <div
+	 onClick={handleClickInDialog}
	 className="contents"
	>
          <img src={src} alt={alt} width="300" />
        </div>
      </dialog>
    </React.Fragment>
  );
};

イベントハンドラに渡す処理は、ダイアローグを開く関数(handleOpenDialog)とダイアローグを閉じる関数(handleCloseDialog)とダイアローグ内をクリックした際の処理のハンドラー関数(handleClickInDialog)の三つがあります。

そのうち、ダイアローグを開く処理とダイアローグを閉じる処理は、dialog要素が提供しているメソッドを利用しています。

参照:https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement#methods

関数内でDOM要素にアクセスする必要があるため、refを使用しています。

参照:https://ja.reactjs.org/docs/refs-and-the-dom.html

また、今回は、ダイアローグ内をクリックしても閉じないが、ダイアローグの外をクリックしたら閉じるようにしたかったので、ダイアローグの表示領域をラップする要素(<div className="contents">)でhandleClickInDialog関数をイベントハンドラに設定し、イベントの伝播を防ぐようにしています。

参照:https://developer.mozilla.org/ja/docs/Web/API/Event/stopPropagation

必要な機能の実装は以上です。あとはCSSを最低限実装していきます😏

CSS

今回はサンプルなので、最低限必要じゃないかなと思う実装を紹介します。

/* 画像にホバー時、カーソルを変更し、拡大可能であることを明示する */
.image-box {
  cursor: zoom-in;
  display: inline-block;
}

/* ダイアローグ内をクリックしても閉じないようにしたい場合は必要 */
.image-dialog {
  padding: 0;
}

/* ダイアローグの背景部分をデコりたい場合は必要、今回はカーソルを変更し縮小可能であることを明示する */
.image-dialog::backdrop {
  cursor: zoom-out;
}

/* ダイアローグ内の画像の大きさを指定する */
.contents img {
  width: 90vw;
}

以上の実装で、最初にお見せしたような画像を拡大表示すするダイアローグを実装できます。

サンプルの後に、私が作成したポートフォリオの事例をもとに実装したことをTipsとして紹介していますので、よければご覧ください!

サンプル

React.js

Vue.js

TIPS

今回、ポートフォリオ内のブログページにおいてこの機能を実装したのですが、開いたダイアローグに閉じるためのボタンを配置したいと考えました。というのも、ダイアローグの背景の部分は、あくまでも疑似要素であり、フォーカス可能な要素ではないためです。
というわけで、フォーカス可能なボタンを設置するわけですが、ダイアローグの右上に「x」アイコンを設置すると、アイコンが画像の上に被ってしまいます。。。これだと、後ろの画像の色によって、アイコンが見えにくくなってしまい視認性に欠けたUIになってしまいます😱😱😱😱

そんな時は、CSS!!!

mix-blend-mode: difference;

これを指定すれば、全て解決!!簡単!!最高!!

上記のCSSを指定すると、以下のようになります。

こんな感じで、下に重なっている画像の色によって、ボタンの色(つまり、アイコンの色)が変化します。これでも背景の色によっては視認性が低いものはありますが、認識できないとユーザーの操作にクリティカルに影響が出る機能というわけではないので今回はここまでの対応とさせていただきます😏

Discussion