작동 화면에서 div 리사이징 기능은 react-grid-layout 라이브러리를 이용해서 구현한 것.
사내 프로젝트에서 react-grid-layout 을 이용해 대시보드 페이지의 위젯을 만들면서, 컨테이너 크기에 비례하는 크기를 가지는 텍스트 컴포넌트를 만들어야 했다.
처음엔 미디어쿼리를 이용해서 만들려고 했으나, 브레이크 포인트를 지정하기가 애매한 상황이였고,
react-fittext 라는 라이브러리를 사용하려고 했으나, 요구사항을 모두 충족시키기에 부족한 점이 있어서 직접 구현하기로 했다.
react-resize-detector 라이브러리가 필요하다.
react-resize-detector는 window.addEventListener(”resize”, handleResize);
처럼, 지정한 React Element의 사이즈가 변경될 때 마다 어떤 작동을 수행하거나, width, height 값을 구할 수 있도록 도와주는 라이브러리 이다.
텍스트 영역(<text>
)의 bounding box의 정보(width, height)와 컨테이너 영역(<div>
)의 width, height의 비율을 계산해서, 텍스트 영역(<text>
)을 그 비율만큼 scale 시켜주는 것이다.
svg로 구현하기 때문에 scale 되더라도 이미지가 깨지는 일이 없다.
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useResizeDetector } from 'react-resize-detector';
const ResponsiveText = ({ children }) => {
const textRef = useRef(null);
const [ContainerW, setContainerW] = useState(1);
const [ContainerH, setContainerH] = useState(1);
useEffect(() => {
const textElement = textRef.current;
if (textEl) {
// getBBox를 이용해서 <text> 가 가지고있는 bounding box의 정보를 구한다.
// * getBBox는 scale 같은 속성에 영향을 받지 않은 상태의 bounding box를 리턴한다.
const textBox = textElement.getBBox();
const textW = textBox.width;
const textH = textBox.height;
// scale 시킬 비율을 구한다.
const wRatio = ContainerW / textW;
const hRatio = ContainerH / textH;
// 텍스트가 넘치지 않도록 하기 위해
const ratio = wRatio < hRatio ? wRatio : hRatio;
// 좌측 정렬
// const hAlign = 0;
// 가운데 정렬
const hAlign = (ContainerW - textW * ratio) / 2;
// 우측 정렬
// const hAlign = ContainerW - textW * ratio;
// matrix function의 인자 의미는 다음과 같다.
// matrix(scaleX, skewY, skewX, scaleY, translateX, translateY)
textEl.setAttribute(
'transform',
`matrix(${ratio}, 0, 0, ${ratio}, ${hAlign}, 0)`,
);
}
}, [ContainerW, ContainerH]);
// useResizeDetector의 핸들러
// w, h는 ref를 지정한 React Element의 width, height이다.
const onResize = useCallback((w, h) => {
setContainerW(w);
setContainerH(h);
}, []);
// useResizeDetector가 React Element의 사이즈 변경을 감지한다.
const { ref: containerRef } = useResizeDetector({ onResize });
return (
<div ref={containerRef}>
<text x="0" y="0" ref={textRef} fill="#000000">
{children}
</text>
</div>
);
};