[TypeScript] 변환 원점 찾기

Table of contents

잠깐! transform에 대해 아직 잘 모른다면 [CSS] 변환 행렬을 먼저 읽어보세요!

상황에 따라 동적으로 뷰포트에 대한 transform-origin의 좌표를 구해야 할 수 있습니다. 대상이 변환이 수행되지 않은 경우라면 getBoundingClientRect를 사용해 쉽게 구할 수 있습니다. 하지만 변환이 수행된 경우는 생각보다 까다롭습니다.

차근차근 살펴보도록 하겠습니다. 우선 변환이 고려되지 않은 상황, 그중에서도 대상 영역에 대한 transform-origin의 상대 좌표를 가져오는 방법은 아래와 같습니다.

const [
    transformOriginX,
    transformOriginY
] = getComputedStyle(target)
    .transformOrigin
    .split(" ")
    .map(p => parseFloat(p));

style 속성은 지정된 경우에만 가져올 수 있을뿐더러 단위에 대한 일관성도 없기 때문에 실제 계산된 결과를 가져와야 합니다. 이는 getComputedStyle을 통해 비교적 간단히 수행할 수 있습니다.

이제 transform-origin대한 각 꼭짓점의 상대 좌표를 얻을 차례입니다. 영역에 대한 transform-origin의 상대 좌표를 \((O_x, O_y)\)라 하고 영역의 너비를 \(w\), 높이를 \(h\)라 할 때 각 꼭짓점의 좌표는 아래와 같습니다.

$$\begin{aligned} & A(-O_x, -O_y) \\ & B(w - O_x, -O_y) \\ & C(w - O_x, h - O_y) \\ & D(-O_x, h - O_y) \end{aligned}$$

그리고 변환 행렬은 함수로 나타낼 때 \(m: (x, y) \mapsto (ax + cy + e, bx + dy + f)\) 이므로 좌표는 각각 아래와 같이 변환됩니다.

$$\begin{aligned} x & = -O_x \\ x' & = ax + cy + e \\ \\ y & = -O_y \\ y' & = bx + dy + f \end{aligned}$$

라 하면

$$\begin{aligned} A(x, y) & \xrightarrow{m} A'(x', y') \\ B(x + w, y) & \xrightarrow{m} B'(x' + aw, y' + bw) \\ C(x + w, y + h) & \xrightarrow{m} C'(x' + aw + ch, y' + bw + dh) \\ D(x, y + h) & \xrightarrow{m} D'(x' + ch, y' + dh) \end{aligned}$$

이제 다음은 무엇을 해야 할까요? 새로운 영역의 한 꼭짓점 좌표를 구해야 합니다. 이 좌표와 getBoundingClientRect를 통해 얻을 수 있는 실제 좌표의 차를 구하면 초기 변환 원점 즉, 변환 이전 transform-origin의 뷰포트에 대한 상대 좌표를 구할 수 있습니다.

바로 가봅시다. 어느 꼭짓점이라도 상관없지만 가장 조건이 단순한 점은 좌측 상단일 것입니다. 모든 축에 대해 가장 작은 경우이니 말이지요.

$$\begin{aligned} x_{min} & = x' + min(min(aw, ch), min(0, aw + ch)) \\ & = ax + cy + e + min(min(aw, ch), min(0, aw + ch))\\ \\ y_{min} & = y' + min(min(bw, dh), min(0, bw + dh)) \\ & = bx + dy + f + min(min(bw, dh), min(0, bw + dh)) \end{aligned}$$

따라서 getBoundingClientRect로 얻은 좌측 상단 좌표를 \((l, t)\)라 할 때,

$$\begin{aligned} x_{transformOrigin} & = l - x_{min} \\ & = l - (ax + cy + e + min(min(aw, ch), min(0, aw + ch)))\\ \\ y_{transformOrigin} & = t - y_{min} \\ & = t - (bx + dy + f + min(min(bw, dh), min(0, bw + dh))) \end{aligned}$$

결국 변환 후 transform-origin은 아래와 같습니다.

$$\begin{aligned} x'_{transformOrigin} & = x_{transformOrigin} + e \\ & = l - (ax + cy + e + min(min(aw, ch), min(0, aw + ch))) + e \\ & = l - (ax + cy + min(min(aw, ch), min(0, aw + ch))) \\ \\ y'_{transformOrigin} & = y_{transformOrigin} + f \\ & = t - (bx + dy + f + min(min(bw, dh), min(0, bw + dh))) + f \\ & = t - (bx + dy + min(min(bw, dh), min(0, bw + dh))) \end{aligned}$$

이를 코드로 나타내면 아래와 같습니다.

interface Point {
    x: number;
    y: number;
}

function findTransformOrigin(target: HTMLElement): Point {
    const [ x, y ] = getComputedStyle(target)
        .transformOrigin
        .split(" ")
        .map(p => -parseFloat(p));
    const { a, b, c, d } = new DOMMatrix(getComputedStyle(target).transform);
    const { offsetWidth: w, offsetHeight: h } = box;

    const { left: l, top: t } = box.getBoundingClientRect();

    return {
        x: l - (
            a * x + c * y + Math.min(
                Math.min(a * w, c * h),
                Math.min(0, a * w + c * h)
            )
        ),
        y: t - (
            b * x + d * y + Math.min(
                Math.min(b * w, d * h),
                Math.min(0, b * w + d * h)
            )
        )
    };
}

읽어주셔서 감사합니다!

묻고 답하기

개인적인 판단에 의해 적절하다고 여겨지는 경우, 모두가 볼 수 있도록 이곳에 문답이 추가됩니다. 그렇지 않더라도 질문에 대한 답변은 별도로 이루어집니다.