오늘의 문제
display:none; display:block; 에 transition 애니메이션이 적용되지 않는다!
See the Pen display none animation1 by ylem76 (@ylem76) on CodePen.
메뉴를 마우스 오버했을 때 하위 메뉴가 보이고, 마우스 아웃하면 사라지는 흔한 메뉴.
간단할 것 같아서 css transition으로 구현하려고 하는데, 애니메이션이 뚝뚝 끊기고 안 예쁘다..!!!
어째서 이런 현상이 발생하는 건지 원인을 알아보고 해결 방법을 고민해보았다.
문제 분석
display none에서만 생기는 트랜지션 오류
화면에 보이지 않게 만드는 방법은 여러 가지가 있다.
display:none;
opacity:0;
visibility:hidden;
transform:scale(0);
z-index:-1;
기타 등등등... 그중에서 display none 처리했을 때에만 transition 애니메이션이 제대로 동작하지 않는다.
다른 것과 어떤 차이가 있길래?!
원인 분석
2. transition을 이용한 애니메이션의 특징
먼저 transition 애니메이션에 대해 살펴보자.
transition
은 이전 프로퍼티의 상태와 이후 프로퍼티의 상태를 비교해서 그 차이를 부드럽게 이어준다. 일반적으로 숫자 연산 가능한 속성에 사용한다.
그러나 display
와 visibility
는 숫자로 연산이 안된다. 따라서 이 둘은 transition 효과도 불가능.
아니 근데 내가 바보도 아니고 display:none
과 opacity
를 같이 설정했다. 그러니 display
에 설정한 transition
효과는 안 먹더라도 opacity
는 제대로 동작해야 하는 거 아닌가?
이걸 이해하려면 브라우저 렌더링을 이해해야 했다... 고난의 길 스타뜨...
3. 주요 렌더링 경로 Critical Rendering Path
인터넷 브라우저가 화면 상에 코드를 그릴 때 한 번에 짠! 하는 것처럼 보이지만 내부적으로는 여러 단계를 거친다. 이 일련의 단계들을 중요 렌더링 경로(Critical Rendering Path)라고 부른다.
관련 내용 중심으로 간략하게 아래의 4단계로 나누어 볼 수 있다.
- DOM, CSSOM 트리 생성 : 파싱 한 HTML과 CSS를 이용해 document object model과 css object model을 각각 생성한다.
- 렌더링 트리 생성 : 앞 단계에서 생성한 트리 정보를 바탕으로, '화면에 표시'할 요소들의 정보를 정리해 렌더링 트리를 만든다
- 레이아웃(혹은 리플로우) : 요소가 실제 화면(뷰포트) 상에 너비, 높이, 위치가 어떻게 될 것인지 계산(만)한다. (아직 화면에는 보이지 않음)
- 페인트 : 실제로 화면에 픽셀을 그림 쨔잔
display:none
요소는 화면 상에 아예 보이지 않기 때문에 렌더링 트리에서 누락되게 된다. 따라서 렌더링 트리 X, 레이아웃 계산 X, 페인트 X
display block
이 되었을 때가 되어야 비로소 렌더링 트리에 들어가게 되고 그제야 레이아웃과 페인트가 일어난다. transition 애니메이션을 이용하려면 none 상태의 opacity와 block 상태의 opacity값의 차이를 비교할 수 있어야 하는데, none상태의 opacity는 애초에 존재한 적이 없으니(렌더링 트리 X), 상태를 비교할 값이 없어 transition 애니메이션을 사용할 수 없는 것.
해결을 위한 방법들(순수 CSS)
1. transition이 아니라, keyframe animation 활용
transition이 비교할 초기값이 없어 애니메이션이 불가능 한 반면에 키프레임 애니메이션은 초기값을 유저가 직접 할당하고 실행시키기 때문에 동작이 가능하다.
그러나 이는 완벽하지는 않다.
display:none → block
으로 가는 애니메이션은 초기값 설정으로 애니메이션 구현이 가능하지만
display:block → none
으로 가는 애니메이션은 불가능하다.
none이 되었을 때 렌더 트리에서 바로 삭제해버리니까 애니메이션이 재생할 틈이 없는 것.
사라질 때 애니메이션이 필요 없다면 추천~!
2. display none을 안 쓰고 구현하자
none 속성이 아니라 다른 방법으로 화면 상에서 보이지 않게 처리한다면 transition을 활용한 애니메이션도 활용 가능하다. 다양한 여러 방법들이 가능하겠지만, 두루두루 적용할 수 있는 방법 두 가지.
2_1. display 대신에 visibility 이용
display:none
과 달리 visibility:hidden
은 화면에 보이지 않아도 공간을 차지하고 있다고 한다. 위의 렌더링 과정을 이용해 다시 말하자면 visibility는 3단계 레이아웃 계산을 끝마쳤다는 것과 같은 의미이다. 따라서 visibility:hidden
상태와 visibility:visible
상태를 서로 비교가 가능하고, transition 애니메이션을 사용할 수 있다!
- visibility:hidden 처리된 요소는 display:none처럼 접근성 트리에도 삭제된다.
(애니메이션을 이용할 생각이라면 어쩌면 display:none보다 더 나을 수도 있겠다.)- transition-property를 all로 해야만 적용된다. 주의!
2_2. pointer-event 활용
근데 애초에 display none
을 왜 굳이 썼을까? 돔 트리 렌더 트리 어려운 얘기는 차치하고 실제로 구현하고자 했던 것은 이거 아니었을까?
화면 상에 안 보이고, 높이값도 안 차지하고 그 자리에 클릭 이벤트도 안 걸렸으면 좋겠다!!
화려한 애니메이션이 들어갈 것도 아니고 화면 상에서 보이지 않게만 처리할 거라면 그냥 opacity:0 지정하고
마우스 클릭 이벤트를 없애버려도 된다.
이때 사용할 수 있는 CSS 속성이 있는데, 바로 pointer-events
이다.
해당 요소에 pointer-events
속성을 none
으로 지정하면, 화면 상에서 클릭했을 때 해당 영역은 잡히지 않고 크롬 개발자 도구에서도 클릭으로 잡을 수 없다.
다시 화면상에 나타날 때에 pointer-events:auto;
로 지정하면 된다.
위의 방법들을 실제 코드로 확인해보세요!
See the Pen display none animation by ylem76 (@ylem76) on CodePen.
렌더 과정..? 이런 거 굳이.. 알아야 할까..?라고 생각했지만
결국 알아야 하는 순간이 오는 것 같네요.
원리를 숙지해야 문제의 원인이 정확히 파악 가능하네요.
같은 문제로 헤매셨던 분이 있다면 도움이 되었으면 좋겠습니다!!