통합검색

Javascript

[Javascript] 이미지맵 반응형에서 적용하기 (rect, circle, poly 모두 지원)

  • 2025.08.14 13:46:58


pc기준으로 이미지맵을 적용한 경우
반응형에서 줄어든 이미지의 비율에 따라 자동으로 이미지맵 좌표를 수정해준다.
(rect, circle, poly 모두 지원)

단, 이미지는 max-width: 100%; height: aut; 처리 되어 있어야 한다.


[!]javascript[/!]
아래와 같이 구현한다.
 
<style>
/* 반응형 기본 */
img[usemap] { max-width: 100%; height: auto; display:block; }
</style>

<script>
// 반응형 이미지맵 스케일러
(() => {
  const imgs = Array.from(document.querySelectorAll('img[usemap]'));

  const parseNums = s => s.split(',').map(n => parseFloat(n.trim()));

  const storeOriginalCoords = map => {
    map.querySelectorAll('area').forEach(a => {
      if (!a.dataset.origCoords) a.dataset.origCoords = a.coords;
    });
  };

  const scaleArea = (area, rw, rh) => {
    const shape = (area.shape || 'rect').toLowerCase();
    const orig = parseNums(area.dataset.origCoords);

    if (shape === 'rect') {
      // x1,y1,x2,y2
      const [x1,y1,x2,y2] = orig;
      area.coords = [
        Math.round(x1 * rw), Math.round(y1 * rh),
        Math.round(x2 * rw), Math.round(y2 * rh)
      ].join(',');
    } else if (shape === 'circle') {
      // x,y,r
      const [x,y,r] = orig;
      const rScaled = Math.round(r * (rw + rh) / 2);
      area.coords = [
        Math.round(x * rw), Math.round(y * rh), rScaled
      ].join(',');
    } else if (shape === 'poly') {
      // x1,y1,x2,y2, ...
      const scaled = orig.map((v, i) => Math.round(v * (i % 2 ? rh : rw)));
      area.coords = scaled.join(',');
    }
  };

  const resizeMapForImage = img => {
    const usemap = img.getAttribute('usemap');
    if (!usemap) return;

    // 연결된 <map> 찾기
    const name = usemap.replace('#','');
    const map = document.querySelector(`map[name="${name}"], map#${name}`);
    if (!map) return;

    // 원본 좌표 저장
    storeOriginalCoords(map);

    // 이미지 실제 크기/표시 크기
    const naturalW = img.naturalWidth || img.width;
    const naturalH = img.naturalHeight || img.height;
    const displayW = img.clientWidth;
    const displayH = img.clientHeight || Math.round(displayW * (naturalH / naturalW));

    if (!naturalW || !naturalH || !displayW || !displayH) return;

    const rw = displayW / naturalW;
    const rh = displayH / naturalH;

    map.querySelectorAll('area').forEach(a => scaleArea(a, rw, rh));
  };

  // 디바운스 리사이즈
  let rid;
  const onResize = () => {
    cancelAnimationFrame(rid);
    rid = requestAnimationFrame(() => imgs.forEach(resizeMapForImage));
  };

  // 초기 바인딩
  imgs.forEach(img => {
    if (img.complete && img.naturalWidth) {
      resizeMapForImage(img);
    } else {
      img.addEventListener('load', () => resizeMapForImage(img), { once:true });
    }
  });

  window.addEventListener('resize', onResize);
  window.addEventListener('orientationchange', onResize);
})();
</script>