본문 바로가기
frontend

[TIL] html에서 돋보기 뷰 구현하기

by marble25 2023. 4. 3.

svg를 이용한 작업을 진행 중에, 특정 부분을 확대한 뷰를 구현해야 하는 태스크가 있었다.

돋보기 뷰를 구현한 과정을 정리해 보았다.

Use 태그

기존 svg 태그를 동일하게 복사해서 scale과 translate하는 작업이 필요하다.

https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use

use 태그는 svg 도큐멘트 내부에서 node를 가져와서 복사한다.

<svg viewBox="0 0 30 10" xmlns="http://www.w3.org/2000/svg">
  <circle id="myCircle" cx="5" cy="5" r="4" stroke="blue" />
  <use href="#myCircle" x="10" fill="blue" />
  <use href="#myCircle" x="20" fill="white" stroke="red" />
</svg>

use 태그는 id를 이용해서 svg 요소를 복사해 온다.

예제

<div style="display: flex;">
  <svg style="border: 1px solid black; margin-right: 100px;" width="500" height="500" id="board">
    <rect x="30" y="30" width="40" height="20" />
    <rect x="100" y="100" width="80" height="30" style="fill: red;" />
    <rect x="180" y="180" width="20" height="50" style="fill: green;" />
    <rect x="200" y="200" width="100" height="60" style="fill: blue;" />
  </svg>
</div>

돋보기 뷰

돋보기 뷰의 요구사항은 다음과 같다.

  • 상하좌우로 최소 마진을 설정한다.
  • svg 내의 특정 엘리먼트를 클로즈업해서 보여준다.
  • 원본 엘리먼트와 동일한 비율로 확대/축소한다.

 

  • html
<div style="display: flex;">
  <svg style="border: 1px solid black; margin-right: 100px;" width="400" height="400" id="board">
    <rect x="30" y="30" width="40" height="20" />
    <rect x="100" y="100" width="80" height="30" style="fill: red;" />
    <rect x="180" y="180" width="20" height="50" style="fill: green;" />
    <rect x="200" y="200" width="100" height="60" style="fill: blue;" id="target" />
  </svg>
  <svg style="border: 1px solid black;" width="400" height="400" id="magnifier">
    <use href="#board" id="image"/>
  </svg>
</div>
  • js
window.onload = () => {
  const target = document.getElementById("target");
  
  // 최소로 설정할 마진
  const MARGIN_SIZE = 20;
  
  const magnifier = document.getElementById("magnifier");
  
  const w = magnifier.clientWidth;
  const h = magnifier.clientHeight;
  
  // 스케일할 비율을 구한다.
  // target * scale => magnifier
  // magnifier / scale => target
  const bbox = target.getBBox();
  const sx = (w - MARGIN_SIZE * 2) / bbox.width; // target의 width -> 왼쪽 오른쪽 마진을 뺀 w 값에 대응
  const sy = (h - MARGIN_SIZE * 2) / bbox.height; // target의 height -> 위 아래 마진을 뺀 h 값에 대응
  const scale = Math.min(sx, sy); // 더 작은 값으로 스케일링
  
  // 새 x, y 좌표를 구한다.
  // 새 마진으로 가능한 값은 (기존 width - 새로운 box의 width) / 2이다.
  // 계산한 새 마진은 돋보기 좌표계이므로 target 좌표계로 가려면 scale로 나눠줘야 한다.
  const x = bbox.x - Math.max(MARGIN_SIZE, (w - bbox.width * scale) / 2) / scale;
  const y = bbox.y - Math.max(MARGIN_SIZE, (h - bbox.height * scale) / 2) / scale;
  
  const image = document.getElementById("image");
  image.setAttribute("transform", `scale(${scale})`);
  image.setAttribute("x", (-x).toString());
  image.setAttribute("y", (-y).toString());
  
}
  • 결과

하지만 위와 같은 방법에는 단점이 있는데, 기존에 있는 모든 svg element를 복사한다는 점이다. 따라서 기존 svg 엘리먼트의 변화는 돋보기 뷰에 동일하게 적용되게 된다. 결과적으로 연산을 *2 만큼 하게 된다.