Skip to content

React + Tailwind - transform과 overflow-hidden의 충돌 문제 해결기

Published: at 오후 03:40Suggest Changes

Table of contents

Open Table of contents

React + Tailwind: transform과 overflow-hidden의 충돌 문제 해결기

🎯 문제 발견

웹 애플리케이션에서 다운로드 버튼을 구현하던 중 이상한 현상을 발견했습니다.

처음 페이지를 로드했을 때는 버튼이 완벽하게 작동했습니다. 둥근 모서리(rounded-full)가 예쁘게 렌더링되고, 호버 시 그라데이션 효과도 버튼의 경계를 벗어나지 않았죠.

하지만 다운로드 버튼을 클릭한 후, 다시 호버를 하면 왼쪽에 희미한 하얀색 네모가 둥근 모서리를 뚫고 나오는 기괴한 현상이 발생했습니다.

// 문제가 있는 코드
const getButtonStyles = (state: DownloadState) => {
const baseStyles = `
relative overflow-hidden
h-16 px-12 text-lg font-semibold
border-0 rounded-full
transform transition-all duration-500 ease-in-out // 🚨 문제의 시작
hover:scale-105 hover:shadow-2xl hover:shadow-purple-500/25
disabled:opacity-70 disabled:cursor-not-allowed disabled:transform-none
`;
return baseStyles;
};
// 버튼 내부의 호버 효과
<div className="absolute inset-0 rounded-full bg-gradient-to-r from-white/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>

🔍 디버깅 과정

1단계: 첫 번째 시도 - “rounded-full을 빼먹었나?”

가장 먼저 의심한 것은 단순한 실수였습니다. 혹시 호버 효과 div에 rounded-full을 빼먹은 건 아닐까?

// 시도 1: rounded-full 추가
<div className="absolute inset-0 rounded-full bg-gradient-to-r ..."></div>

결과: 초기 로드 시에는 문제 해결!
하지만: 다운로드 클릭 후 여전히 같은 증상 발생

2단계: “overflow-hidden이 제대로 작동하나?”

다음으로 의심한 것은 overflow-hidden이 제대로 적용되지 않는다는 것이었습니다.

// 시도 2: overflow-hidden 명시적으로 추가
className={`${getButtonStyles(state)} overflow-hidden group`}

결과: 여전히 초기에는 정상
하지만: 상태 변경 후 동일한 문제

3단계: “왜 다운로드 후에만 발생하지?”

여기서 중요한 힌트를 얻었습니다. 왜 초기에는 괜찮은데, 상태가 변경된 후에만 문제가 발생할까?

React DevTools로 렌더링을 추적해보니, 상태가 변경될 때마다 버튼이 재렌더링되고 있었습니다. 그런데 뭔가 CSS 적용 순서나 방식이 달라지는 것 같았습니다.

💡 문제의 근본 원인

여러 실험과 MDN 문서를 뒤진 끝에, 문제의 원인을 찾았습니다.

원인 1: CSS Transform과 Stacking Context

Tailwind의 transform 유틸리티 클래스는 CSS transform: translateX(0) translateY(0) rotate(0) skewX(0) skewY(0) scaleX(1) scaleY(1)를 적용합니다.

핵심은 이것입니다: CSS transform 속성은 새로운 stacking context를 생성합니다.

🔹 Stacking Context란?
요소들의 z축 순서를 결정하는 독립적인 레이어입니다.
새로운 stacking context가 생성되면, 그 안의 자식 요소들은
외부의 overflow-hidden 규칙과 다르게 작동할 수 있습니다.

원인 2: 재렌더링 시 Transform 재적용

const stateStyles = {
idle: "bg-purple-600 hover:bg-purple-700",
downloading: "bg-purple-600",
completed: "bg-green-600",
error: "bg-red-600",
};

상태가 변경되면:

  1. React가 버튼을 재렌더링
  2. transform 클래스가 제거되고 다시 적용됨
  3. Stacking context가 재생성
  4. 이 과정에서 overflow-hidden과의 관계가 깨짐
  5. 내부 absolute 요소들이 경계를 무시하기 시작

원인 3: Scale과 Overflow의 미묘한 관계

hover:scale-105 // 버튼을 1.05배 확대

scale(1.05)는 요소를 중심으로 확대하는데, 이때:

✅ 해결 방법

해결책: transform 클래스 제거

놀랍게도 해결책은 간단했습니다. 불필요한 transform 클래스를 제거하는 것이었습니다.

// 수정 전
const baseStyles = `
relative overflow-hidden
h-16 pl-8 text-lg font-semibold
border-0 rounded-full
transform transition-all duration-500 ease-in-out // ❌ 제거!
hover:scale-105 hover:shadow-2xl hover:shadow-purple-500/25
disabled:opacity-70 disabled:cursor-not-allowed disabled:transform-none
`;
// 수정 후
const baseStyles = `
relative overflow-hidden
h-16 pl-8 text-lg font-semibold
border-0 rounded-full
transition-all duration-500 ease-in-out // ✅ transform 제거
hover:scale-105 hover:shadow-2xl hover:shadow-purple-500/25
disabled:opacity-70 disabled:cursor-not-allowed disabled:hover:scale-100
`;

왜 이렇게 하면 해결될까?

  1. transform 클래스 불필요: hover:scale-105는 이미 자체적으로 transform을 적용합니다. 기본 transform 클래스는 중복이었습니다.

  2. Stacking Context 단순화: transform 클래스를 제거하면 초기 상태에서 불필요한 stacking context가 생성되지 않습니다.

  3. disabled 상태 개선: disabled:transform-none 대신 disabled:hover:scale-100을 사용하여 disabled 상태에서도 transform이 일관되게 작동합니다.

📊 Before & After

Before (문제 있는 코드)

// ❌ 초기: 정상 → 상태 변경 후: 깨짐
transform transition-all
hover:scale-105
disabled:transform-none

렌더링 사이클:

idle → transform 적용 → stacking context 생성 → overflow 정상
↓ (다운로드 클릭)
downloading → transform 재적용 → stacking context 재생성 → overflow 깨짐!
↓ (완료)
completed → transform 재적용 → overflow 여전히 깨짐

After (수정된 코드)

// ✅ 항상 정상 작동
transition-all
hover:scale-105
disabled:hover:scale-100

렌더링 사이클:

idle → stacking context 없음 → overflow 정상
↓ (다운로드 클릭)
downloading → stacking context 없음 → overflow 정상
↓ (완료)
completed → stacking context 없음 → overflow 정상

🎓 배운 점

1. Tailwind의 transform 클래스는 신중하게 사용하자

transform 유틸리티는 편리하지만, 필요하지 않은 곳에 사용하면 예상치 못한 부작용이 있습니다.

// ❌ 나쁜 예
className="transform hover:scale-105" // transform 중복!
// ✅ 좋은 예
className="hover:scale-105" // scale이 이미 transform을 포함

2. CSS Stacking Context를 이해하자

다음 속성들은 새로운 stacking context를 생성합니다:

3. 상태 변경 시의 렌더링을 주의깊게 관찰하자

초기 로드 시에는 괜찮지만 상태 변경 후 이상해진다면:

4. overflow-hidden과 transform은 상극일 수 있다

overflow-hiddentransform을 함께 사용할 때는:

🔧 추가 팁

디버깅 도구

/* CSS로 stacking context 확인하기 */
.debug-stacking {
/* 새로운 stacking context를 생성하는 속성들 */
outline: 2px solid red;
}
.debug-stacking[style*="transform"] {
outline-color: blue;
}

Tailwind 설정

tailwind.config.js
module.exports = {
// transform 대신 개별 속성 사용
theme: {
extend: {
// 필요한 transform만 명시적으로 정의
}
}
}

📝 결론

이 문제를 해결하면서 다음을 깨달았습니다:

  1. 단순함이 최고: 불필요한 CSS 클래스는 독이 될 수 있다
  2. 문서를 읽자: Tailwind와 CSS 스펙 문서는 보물창고
  3. 재현성이 중요: “왜 특정 상황에서만?”이라는 질문이 해답의 열쇠
  4. 근본 원인 파악: 증상이 아닌 원인을 찾아야 진짜 해결

이제 다운로드 버튼은 어떤 상태에서도 완벽하게 작동합니다! 🎉


🔗 참고 자료



Previous Post
Expressive Code를 기존 theme 시스템과 연동하기