🗾

Reactを用いたResponsiveなImage map

2024/08/25に公開

TL;DR

現在個人でも業務でもImage mapを使うことはまずないんですが備忘録として。

<map>タグは形を決めて内包する<area>タグの指定する座標にリンクを追加することができます。
<map>タグはname属性を<img>タグのuseMap属性で対応することで利用できるようになります。

たとえば地図などにリンクを追加したい際などがよく利用されるシナリオでしょうか。
ただ、利用する際には画像幅を固定して座標を変更する必要がないようにするか、画面幅の変更に合わせて座標を更新する必要があるでしょう。
また、画像にリンクを追加するという仕様上、<area>タグには確実にaltなどの補助的な情報を付与するなどアクセシビリティへの考慮を忘れないようにするべきでしょう。

今回はReactを用いてWindowの画面サイズ変更によって<area>タグの座標更新を発火する方法になります。

import { useCallback, useState, useEffect, useRef } from "react";
import textImage from "../../assets/googleYahoo.png";

function ImageMap() {
  // 元の画像でのリンクを付与したい座標
  const initialCoords = {
    area1: [23, 32, 237, 98],
    area2: [259, 32, 448, 83],
  };
  // 初期座標を元にステートの初期化
  const [coords, setCoords] = useState({
    area1: initialCoords.area1,
    area2: initialCoords.area2,
  });
  // 画像のRefを取得
  const imgRef = useRef(null);
  // 座標計算。元の画像幅と現在の画像幅から割合を出して更新する
  const updateCoordinates = useCallback(() => {
    if (!imgRef.current) return;
    const img = imgRef.current;
    const widthRatio = img.clientWidth / img.naturalWidth;
    const heightRatio = img.clientHeight / img.naturalHeight;
    const calculateCoords = (coords) => {
      return coords.map((coord, index) =>
        index % 2 === 0 ? coord * widthRatio : coord * heightRatio
      );
    };
    setCoords({
      area1: calculateCoords(initialCoords.area1),
      area2: calculateCoords(initialCoords.area2),
    });
  }, [initialCoords.area1, initialCoords.area2]);
  // マウント時にWindowのresizeイベントに座標計算をあてる。
  useEffect(() => {
    window.addEventListener("resize", updateCoordinates);
    const handleLoad = () => {
      updateCoordinates();
    };
    // 画像の読み込み時に再計算する
    const img = imgRef.current;
    if (img) {
      img.addEventListener("load", handleLoad);
    }
    // Initial calculation on component mount
    updateCoordinates();
    // アンマウント時にイベントを除く
    return () => {
      window.removeEventListener("resize", updateCoordinates);
      if (img) {
        img.removeEventListener("load", handleLoad);
      }
    };
  }, [updateCoordinates]);
  return (
    <div>
        <h2>Responsive Image map area</h2>
        <p>
          The coordinates updates on window resize to keep the areas in the
          correct position.
        </p>
        <map name="some-any">
          <area
            shape="rect"
            coords={coords.google.join(",")}
            href="https://www.somewhere.com"
            target="_blank"
            alt="Somewhere"
          ></area>
          <area
            shape="rect"
            coords={coords.yahoo.join(",")}
            href="https://www.anywhere.com"
            target="_blank"
            alt="Anywhere"
          ></area>
        </map>
        <img
          src={textImage}
          ref={imgRef}
          alt="Somewhere Anywhere"
          useMap="#some-any"
          style={{ width: "100%", height: "auto" }}
        />
    </div>
  );
}

export default ImageMap;

実例

https://playcode.io/1982165

Discussion