1. 소개
이 명세는 [css-transforms-1]을 확장하여 작성자가 요소를 3차원 공간에서 변형할 수 있도록 하는 델타 명세입니다. transform 속성에 대한 새로운 변환 함수들은 3차원 변환을 허용하며, 추가 속성들은 3차원 변환을 더 쉽게 다룰 수 있도록 하고, 작성자가 중첩된 3차원 변환 요소들의 상호작용을 제어할 수 있도록 합니다.
-
perspective 속성은 작성자가 자식 요소에 추가적인 원근 변환을 제공할 수 있게 합니다. perspective-origin 속성은 원근이 적용되는 기준점을 제어하여 "소실점"의 위치를 효과적으로 변경할 수 있게 합니다.
-
transform-style 속성은 3D 변환된 요소와 그 3D 변환된 자손들이 공통된 3차원 공간을 공유할 수 있게 하여, 3차원 객체의 계층 구조를 구성할 수 있도록 합니다.
-
backface-visibility 속성은 요소가 3차원 변환을 통해 뒤집혀서 뒷면이 사용자에게 보일 때 작동합니다. 일부 상황에서는 이 때 요소를 숨기는 것이 바람직할 수 있으며, 이 속성에서 hidden 값을 사용하여 요소를 숨길 수 있습니다.
참고: transform 속성의 일부 값은 요소를 3차원 좌표계에서 변형할 수 있게 하지만, 요소 자체는 3차원 객체가 아닙니다. 대신 요소는 2차원 평면(평평한 표면) 위에 존재하며 깊이가 없습니다.
이 명세는 또한 scale, translate, rotate의 세 가지 편의 속성을 추가하여 간단한 변환을 더 쉽게 기술하고 애니메이션할 수 있습니다.
1.1. 모듈 상호작용
여기 정의된 3D 변환 함수는 transform 속성의 함수 집합을 확장합니다.
perspective, transform-style, backface-visibility의 일부 값은 모든 자손에 대한 포함 블록 및/또는 스태킹 컨텍스트 생성을 야기합니다.
3차원 변환은 요소의 시각적 레이어링에 영향을 주며, Appendix E ([CSS21])에 설명된 앞뒤 페인팅 순서를 덮어씁니다.
1.2. 값 정의
이 명세는 CSS 속성 정의 규약 ([CSS21])을 따르며, 값 정의 문법 ([CSS-VALUES-3])을 사용합니다. 이 명세에서 정의하지 않은 값 타입은 CSS Values & Units [CSS-VALUES-3]에서 정의됩니다. 다른 CSS 모듈과 결합하면 이러한 값 타입의 정의가 확장될 수 있습니다.
각 속성 정의에 명시된 속성별 값 이외에도, 이 명세에서 정의된 모든 속성은 CSS 전역 키워드를 속성 값으로 허용합니다. 가독성을 위해 반복해서 명시하지 않았습니다.
2. 용어
- 3D 변환된 요소
- 3D 행렬
-
4x4 행렬로서 2D 행렬의 요건을 충족하지 않는 행렬입니다.
- 단위 변환 함수
-
CSS Transforms의 단위 변환 함수 외에도, 단위 변환 함수의 예로는 translate3d(0, 0, 0), translateZ(0), scaleZ(1), rotate3d(1, 1, 1, 0), rotateX(0), rotateY(0), rotateZ(0), matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) 등이 있습니다. 원근(perspective)은 특별한 사례로 perspective(none)이 해당됩니다. m34의 값이 극히 작아지고, 변환 함수는 결과적으로 단위 행렬과 같다고 간주됩니다.
- 원근 행렬
-
perspective 및 perspective-origin 속성의 값으로부터 아래에 설명된 방식으로 계산된 행렬입니다.
- 누적된 3D 변환 행렬
-
요소의 3D 렌더링 컨텍스트의 루트에 대해 상대적으로 아래에 설명된 방식으로 계산되는 행렬입니다.
- 3D 렌더링 컨텍스트
-
공통 조상을 가진 요소 집합으로, 아래에 설명된 방식으로 공통 3차원 좌표계를 공유합니다.
2.1. computed value의 <transform-list> 직렬화
<transform-list>의 computed value는 아래 알고리즘에 따라 하나의 <matrix()> 또는 <matrix3d()> 함수로 직렬화됩니다:
-
transform을 단위 행렬로 초기화된 4x4 행렬로 둡니다. transform의 m11, m22, m33, m44 요소는 1로 설정하고, 나머지 transform의 요소들은 모두 0으로 설정합니다.
-
<transform-function>들을 <transform-list>에서 transform에 모두 차례로 우측 곱(post-multiply)합니다.
-
<matrix()> 또는 <matrix3d()> 직렬화 중 선택합니다:
- transform이 2D 행렬이라면
- transform을 <matrix()> 함수로 직렬화합니다.
- 그 외의 경우
- transform을 <matrix3d()> 함수로 직렬화합니다.
이 텍스트를 CSS Transforms 1의 내용에 추가하도록 수정 필요.
3. 2차원 부분집합
UA는 항상 3차원 변환을 렌더링할 수 있는 것은 아니므로, 이 명세의 2차원 부분집합만 지원할 수 있습니다. 이 경우 3차원 변환 및 transform-style, perspective, perspective-origin, backface-visibility 속성은 지원하지 않아야 합니다. 3D 변환 렌더링 섹션은 적용되지 않습니다. 행렬 분해는 "Graphics Gems II, edited by Jim Arvo"의 "unmatrix" 메서드에서 가져온 기법을 2D의 경우로 단순화하여 사용합니다. 변환 함수의 수학적 설명 섹션은 여전히 유효하지만 3x3 변환 행렬을 사용하여 a는 m11, b는 m12, c는 m21, d는 m22, e는 m41, f는 m42로 줄일 수 있습니다(6개 파라미터로 구성된 2D 3x2 행렬 참고).
2차원 변환을 위한 3x3 행렬.
작성자는 UA가 3차원 변환을 지원하지 않을 경우 손쉽게 폴백을 제공할 수 있습니다. 아래 예시에서는 transform 속성에 두 가지 정의가 있습니다. 첫 번째는 두 개의 2차원 변환 함수로 구성되어 있습니다. 두 번째는 2차원 변환 함수와 3차원 변환 함수가 함께 있습니다.
div { transform: scale(2) rotate(45deg); transform: scale(2) rotate3d(0, 0, 1, 45deg); }
3D 지원이 있을 경우, 두 번째 정의가 첫 번째를 덮어씁니다. 3D 지원이 없으면 두 번째 정의는 무효이며 UA는 첫 번째 정의로 폴백합니다.
4. 변환 렌더링 모델
이 명세는 CSS Transforms 1 § 3 변환 렌더링 모델을 확장하여 3차원 변환 함수의 존재, transform-origin의 Z 값, perspective 속성, 그리고 transform-style 속성의 사용 값이 preserve-3d일 때 적용되는 새로운 3D 렌더링 모델을 다룹니다.
3차원 변환 함수는 좌표 공간을 개념적으로 3차원으로 확장하며, 화면 평면에 수직인 Z축을 추가하고, Z축의 값은 사용자 쪽으로 증가합니다.
초기 좌표 공간의 데모.
3D 변환에서는 transform-origin의 Z 성분이 결과에 영향을 주므로, 변환 행렬은 transform 및 transform-origin 속성으로 다음과 같이 계산됩니다:
-
단위 행렬로 시작합니다.
-
transform-origin의 계산된 X, Y, Z 값만큼 이동(translate)합니다.
-
transform 속성에 있는 각 변환 함수를 왼쪽에서 오른쪽 순서로 곱합니다.
-
transform-origin의 계산된 X, Y, Z 값을 부호를 반대로 하여 이동(translate)합니다.
4.1. 3D 변환 렌더링
일반적으로 요소는 평면으로 렌더링되며, 자신의 stacking context와 동일한 평면에 렌더링됩니다. 대부분의 경우 이는 페이지의 나머지 부분과 공유하는 평면입니다. 2차원 변환 함수는 요소의 모양을 변경할 수 있지만, 그 요소는 여전히 stacking context와 같은 평면에 렌더링됩니다.
3차원 변환이 적용된 요소가 3D 렌더링 컨텍스트에 포함되어 있지 않으면, 해당 변환만 적용되어 렌더링되지만 다른 요소와 교차하지는 않습니다. 이때의 3차원 변환은 2차원 변환처럼 단순한 페인팅 효과로 간주할 수 있습니다. 마찬가지로, 변환은 페인팅 순서에 영향을 주지 않습니다. 예를 들어, Z축에 양의 값을 변환하면 요소가 더 커 보일 수 있지만, Z 변환이 없는 요소 앞에 렌더링되지는 않습니다.
중첩된 3D 변환 요소의 렌더링 방식(수식 등 포함) 설명 필요
이 예시는 요소에 3차원 변환이 적용된 효과를 보여줍니다.
<style> div { height: 150px; width: 150px; } .container { border: 1px solid black; } .transformed { transform: rotateY(50deg); } </style> <div class="container"> <div class="transformed"></div> </div>

이 변환은 수직인 Y축을 기준으로 50° 회전입니다. 파란 박스가 더 좁아보이지만, 3차원적으로 보이진 않는 점에 주목하세요.
4.1.1. 원근(perspective)
원근(perspective)은 Z축에서 더 위쪽(사용자에게 더 가까움)에 있는 요소를 더 크게, 더 멀리 있는 요소를 더 작게 보이게 하여 장면에 깊이감을 더할 수 있습니다. 스케일링은 d/(d − Z)에 비례하며, 여기서 d는 perspective 속성의 값으로, 도면 평면에서 사용자 눈의 위치까지의 거리입니다.
원근 효과는 3D 변환된 요소에 두 가지 방법으로 적용할 수 있습니다. 첫째, 요소의 'transform function list'에 perspective() 함수가 포함되어 있으면 해당 함수가 요소의 'current transformation matrix'로 계산됩니다.
둘째, perspective와 perspective-origin 속성을 요소에 적용하여 3D 변환된 자식의 렌더링에 영향을 줄 수 있으며, 이들은 동일한 3차원 장면에 존재하는 것처럼 보이도록 공유된 원근을 제공합니다.

perspective 속성과 Z 위치에 따라 스케일링이 어떻게 달라지는지 보여주는 다이어그램. 위 다이어그램에서는 Z가 d의 절반입니다. 원래 원(실선)이 Z(점선 원)에 나타나도록 하려면 원을 두 배로 확대해야 하며, 그 결과 연한 파란색 원이 됩니다. 아래 다이어그램에서는 원이 1/3만큼 축소되어 원래 위치 뒤에 나타납니다.
일반적으로 사용자의 눈 위치는 그림의 중앙에 있다고 가정합니다. 이 위치는 필요에 따라 조정할 수 있습니다. 예를 들어, 웹 페이지에 여러 그림이 있어 공통 원근을 공유해야 할 경우 perspective-origin을 설정하면 됩니다.

원근 기준점을 위로 이동했을 때의 효과를 보여주는 다이어그램.
원근 행렬은 다음과 같이 계산됩니다:
-
단위 행렬로 시작합니다.
-
perspective-origin의 계산된 X, Y 값만큼 이동(translate)합니다.
-
perspective() 변환 함수에서 얻은 행렬을 곱합니다. 길이는 perspective 속성의 값으로 제공합니다.
-
perspective-origin의 계산된 X, Y 값을 부호를 반대로 하여 이동(translate)합니다.
이 예시는 원근을 사용하여 3차원 변환이 더 현실적으로 보이게 하는 방법을 보여줍니다.
<style> div { height: 150px; width: 150px; } .container { perspective: 500px; border: 1px solid black; } .transformed { transform: rotateY(50deg); } </style> <div class="container"> <div class="transformed"></div> </div>

내부 요소는 이전 예시와 동일한 변환을 사용하지만, 이제 부모 요소의 perspective 속성에 의해 렌더링이 영향을 받습니다. Perspective는 Z 좌표가 양수(사용자에게 가까운)인 꼭짓점은 X, Y 방향으로 확대되고, Z 좌표가 음수(멀리 있는)는 축소되어 깊이감을 줍니다.
4.1.2. 3D 렌더링 컨텍스트
이 섹션은 3D 변환과 transform-style 속성을 사용하는 콘텐츠의 렌더링 모델을 규정합니다. 이 모델을 설명하기 위해 "3D 렌더링 컨텍스트"라는 개념을 도입합니다.
3D 렌더링 컨텍스트란, 3D 변환 렌더링 목적상 공통 조상에 뿌리를 둔 요소 집합으로서, 공통 3차원 좌표계를 공유하는 것으로 간주됩니다. 3D 렌더링 컨텍스트 내 요소들의 앞뒤 렌더링 순서는 해당 3차원 공간에서의 z-위치에 따라 달라지며, 만약 요소의 3D 변환이 교차하게 되면 교차하여 렌더링됩니다.
각 요소의 3차원 공간 내 위치는 해당 요소에서 3D 렌더링 컨텍스트를 설정하는 요소까지 변환 행렬을 누적하여 결정됩니다.
요소는 다음과 같이 3D 렌더링 컨텍스트를 설정하거나 참여합니다:
-
3D 렌더링 컨텍스트는 변환 가능한 요소가 transform-style 속성의 사용 값이 preserve-3d이고, 자신이 이미 3D 렌더링 컨텍스트에 속하지 않은 경우에 설정됩니다. 3D 렌더링 컨텍스트를 설정한 요소도 해당 컨텍스트에 참여합니다.
-
요소가 transform-style의 사용 값이 preserve-3d이고, 이미 3D 렌더링 컨텍스트에 참여하고 있다면, 새로운 컨텍스트를 설정하지 않고 기존 컨텍스트를 확장합니다.
-
요소는 부모가 3D 렌더링 컨텍스트를 설정하거나 확장하는 경우 해당 3D 렌더링 컨텍스트에 참여합니다.
일부 CSS 속성 값은 "그룹화"를 강제하는 것으로 간주됩니다. 이는 해당 요소와 자손이 다른 요소와 합성되기 전에 그룹으로 렌더링되어야 함을 의미합니다. 대표적으로 opacity, filter, 클리핑에 영향을 주는 속성 등이 있으며, 관련 속성 값은 그룹화 속성 값에 나열되어 있습니다. 따라서 transform-style:preserve-3d가 적용된 요소에 이들 속성이 사용되면 사용 값이 flat으로 변경되어 3D 렌더링 컨텍스트를 생성하거나 확장하지 않도록 합니다.
3D 렌더링 컨텍스트에서는 요소의 렌더링 및 정렬이 다음과 같이 이루어집니다:
-
3D 렌더링 컨텍스트를 설정한 요소와, 해당 컨텍스트에 참여하는 다른 3D 변환 요소는 각자의 평면에 렌더링됩니다. 이 평면에는 요소의 배경, 테두리, 기타 박스 장식, 콘텐츠, 자손 요소가 포함되며, 자체 평면을 가진 자손(및 그 자손)은 제외됩니다. 이 렌더링은 CSS 2.1, Appendix E, Section E.2 페인팅 순서에 따라 수행됩니다.
-
이 평면 집합 간의 교차는 Newell 알고리즘에 따라 이루어지며, 각 평면은 누적된 3D 변환 행렬로 변환됩니다. 동일 평면의 3D 변환 요소는 페인팅 순서대로 렌더링됩니다.
2D 변환된 요소를 별도 평면으로 분리하지 않아도 되는지?
참고: 이전 명세에서는 3D 렌더링 컨텍스트를 설정하는 요소의 배경, 테두리, 기타 박스 장식을 전체 3D 장면 뒤에 렌더링한다고 정의했으나, #6238에서 변경되었습니다. 향후 3D 렌더링 컨텍스트 정의가 변경되면 다시 고려될 수 있습니다.
transform에 음수 z-성분이 있는 요소는 설정 요소의 콘텐츠 및 변환되지 않은 자손 뒤에 렌더링되며, 3D 변환 요소는 콘텐츠 및 변환되지 않은 요소와 겹칠 수 있습니다.
참고: 3D 렌더링 컨텍스트 내의 3D 변환 요소들은 모두 깊이 정렬 및 교차가 가능하므로, 실질적으로 서로 형제처럼 렌더링됩니다. transform-style: preserve-3d의 효과는 3D 렌더링 컨텍스트 내 모든 3D 변환 요소를 설정 요소로 끌어올리되, 각 요소는 누적된 3D 변환 행렬에 따라 렌더링하는 것과 같습니다.
<style> div { height: 150px; width: 150px; } .scene { background-color: rgba(0, 0, 0, 0.3); border: 1px solid black; perspective: 500px; } .container { transform-style: preserve-3d; } .container > div { position: absolute; left: 0; } .container > :first-child { transform: rotateY(45deg); background-color: orange; top: 10px; height: 135px; } .container > :last-child { transform: translateZ(40px); background-color: rgba(0, 0, 255, 0.6); top: 50px; height: 100px; } </style> <div class="scene"> <div class="container"> Lorem ipsum dolor sit amet, consectetaur adipisicing elit… <div></div> <div></div> </div> </div>
이 예시는 3D 렌더링 컨텍스트 내에서 요소들이 어떻게 교차할 수 있는지를 보여줍니다. 컨테이너 요소는 자신과 두 자식 요소에 대해 3D 렌더링 컨텍스트를 설정하며, scene 요소는 해당 3D 렌더링 컨텍스트에 원근을 추가합니다. 자식 요소들은 서로 교차하고, 오렌지 색 요소는 컨테이너와도 교차합니다.

<style> div { height: 150px; width: 150px; } .container { perspective: 500px; border: 1px solid black; } .transformed { transform: rotateY(50deg); background-color: blue; } .child { transform-origin: top left; transform: rotateX(40deg); background-color: lime; } </style> <div class="container"> <div class="transformed"> <div class="child"></div> </div> </div>
이 예시는 중첩된 3D 변환이 어떻게 렌더링되는지 보여줍니다. 파란 div는 이전 예시와 같이 변환되며, 부모 요소의 perspective에 의해 렌더링이 영향을 받습니다. 라임(lime) 요소도 X축 기준 3D 변환(상단 기준 transform-origin)되어 있지만, 라임 요소는 부모의 평면에 렌더링되므로 동일한 3D 렌더링 컨텍스트에 속하지 않습니다. 따라서 라임 요소는 단순히 짧아 보일 뿐 파란 요소에서 "튀어나오는" 효과는 없습니다.

4.1.3. 변환 요소 계층 구조
기본적으로 변환 요소는 3D 렌더링 컨텍스트를 생성하지 않으며 내용이 평면화(flattened)되어 렌더링됩니다. 하지만, 공통 3차원 공간을 공유하는 변환 객체 계층을 만드는 것이 유용하므로, transform-style 속성에 preserve-3d 값을 지정하여 이 평면화 동작을 무시할 수 있습니다. 이 설정을 하면 변환 요소의 자손들이 동일한 3D 렌더링 컨텍스트를 공유할 수 있습니다. 그런 요소의 비-3D 변환 자손은 위 C 단계에서 요소의 평면에 렌더링되지만, 동일 3D 렌더링 컨텍스트의 3D 변환 요소는 자신의 평면으로 "튀어나옵니다".
<style> div { height: 150px; width: 150px; } .container { perspective: 500px; border: 1px solid black; } .transformed { transform-style: preserve-3d; transform: rotateY(50deg); background-color: blue; } .child { transform-origin: top left; transform: rotateX(40deg); background-color: lime; } </style>
이 예시는 이전 예시와 동일하지만, 파란 요소에 transform-style: preserve-3d가 추가된 것입니다. 파란 요소는 이제 컨테이너의 3D 렌더링 컨텍스트를 확장합니다. 이제 파란 요소와 라임 요소 모두 공통 3차원 공간을 공유하므로, 라임 요소는 컨테이너의 perspective에 영향을 받아 부모에서 기울어진 채로 "튀어나와" 렌더링됩니다.

4.1.4. 누적 3D 변환 행렬 계산
3D 렌더링 컨텍스트에서 요소를 렌더링하는 데 사용되는 최종 transform 값은 다음과 같이 누적 3D 변환 행렬을 누적하여 계산됩니다:
-
transform을 단위 행렬로 둡니다.
-
current element를 변환된 요소로 둡니다.
-
parent element를 변환된 요소의 부모 요소로 둡니다.
-
current element가 변환된 요소의 3D 렌더링 컨텍스트에 속하는 요소일 때 동안:
-
current element가 transform의 값이 none이 아니면, current element의 변환 행렬을 transform에 전치 곱(pre-multiply)합니다.
-
current element가 parent element로부터의 오프셋(스크롤 오프셋 포함)을 나타내는 이동 행렬을 계산하고, 그 행렬을 transform에 전치 곱합니다.
-
parent element가 perspective의 값이 none이 아니면, parent element의 원근 행렬을 transform에 전치 곱합니다.
-
current element를 parent element로 둡니다.
-
parent element를 current element의 부모로 둡니다.
-
참고: 여기 설명된 대로, 누적 3D 변환 행렬은 변환된 요소 및 해당 조상 체인(3D 렌더링 컨텍스트를 설정하는 요소까지 포함)의 시각적 포맷팅 모델에 의해 생성된 오프셋(스크롤 오프셋 포함)을 고려합니다.
4.1.5. 뒷면(Backface) 표시 여부
3차원 변환을 사용하면 요소가 뒤집혀서 뒷면이 보이도록 변환할 수 있습니다. 3D 변환된 요소는 양쪽에 동일한 콘텐츠를 표시하므로, 뒷면은 앞면의 거울상처럼 보입니다(요소가 유리판에 투영된 것처럼). 일반적으로 뒷면이 사용자 쪽을 향할 때에도 요소는 표시됩니다. 그러나 backface-visibility 속성을 사용하면 요소의 뒷면이 사용자 쪽을 향할 때 요소를 보이지 않게 할 수 있습니다. 이 동작은 "실시간"입니다. 만약 backface-visibility: hidden이 설정된 요소가 애니메이션되어 앞면과 뒷면이 번갈아 보인다면, 앞면이 사용자 쪽을 향할 때만 요소가 보입니다.
요소의 뒷면 표시 여부는 누적 3D 변환 행렬을 기준으로 고려되며, 이는 3D 렌더링 컨텍스트를 설정하는 요소의 부모에 대해 상대적입니다.
참고: 이 속성은 두 요소를 등 뒤로 배치하여 카드처럼 만드는 경우에 유용합니다. 이 속성이 없으면 애니메이션 중에 앞면과 뒷면 요소가 위치를 바꿀 수 있습니다. 또 다른 예로 6개의 요소로 박스를 만들 때, 박스의 내부 면만 보고 싶을 때 사용할 수 있습니다.
이 예시는 클릭 시 뒤집히는 "카드" 요소를 만드는 방법을 보여줍니다. #card의 "transform-style: preserve-3d"는 뒤집힐 때 평면화(flattening)를 방지하기 위해 필요합니다.
<style> .body { perspective: 500px; } #card { position: relative; height: 300px; width: 200px; transition: transform 1s; transform-style: preserve-3d; } #card.flipped { transform: rotateY(180deg); } .face { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: silver; border-radius: 40px; backface-visibility: hidden; } .back { transform: rotateY(180deg); } </style> <div id="card" onclick="this.classList.toggle('flipped')"> <div class="front face">Front</div> <div class="back face">Back</div> </div>
backface-visibility가 변환되지 않거나 2D 변환된 요소에 미치는 영향은? 별도 평면으로 분리되고 교차하게 되는가?
4.2. 원근 변환된 박스 처리
제공된 행렬을 사용하여 요소를 정확히 변환하는 방법을 명확하게 규정하려는 첫 시도입니다. 완벽하지 않을 수 있으며, 구현자 피드백을 권장합니다. #912 참고.
누적 3D 변환 행렬은 perspective 속성뿐 아니라 transform 속성 값에 포함된 perspective() 변환 함수에도 영향을 받습니다.
이 누적 3D 변환 행렬은 4×4 행렬이며, 변환할 객체는 2차원 박스입니다. 박스의 각 꼭짓점(a, b)을 변환하려면 먼저 행렬을 (a, b, 0, 1)에 적용하면, (x, y, z, w)의 4차원 점이 됩니다. 이 점을 아래와 같이 3차원 점(x′, y′, z′)으로 변환합니다:
w > 0인 경우, (x′, y′, z′) = (x/w, y/w, z/w)입니다.
w = 0인 경우, (x′, y′, z′) = (x ⋅ n, y ⋅ n, z ⋅ n)입니다. n은 구현에 따라 선택되는 값으로, 가능하면 x′ 또는 y′가 뷰포트 크기보다 훨씬 크도록 해야 합니다. 예를 들어, (5px, 22px, 0px, 0)은 (5000px, 22000px, 0px)으로 변환될 수 있으며, n = 1000입니다. 하지만 (0.1px, 0.05px, 0px, 0)에는 이 값이 너무 작을 수 있습니다. 이 명세는 n의 값을 정확히 정의하지 않습니다. 개념적으로 (x′, y′, z′)는 (x, y, z) 방향으로 무한히 멀리 있는 점입니다.
변환된 박스의 네 꼭짓점 모두 w < 0이면, 박스는 렌더링되지 않습니다.
변환된 박스의 한 꼭짓점부터 세 꼭짓점이 w < 0이면, 해당 부분을 잘라내어 다각형(폴리곤)으로 대체해야 합니다. 일반적으로 꼭짓점이 3~5개인 폴리곤이 되며, 그 중 정확히 두 꼭짓점은 w = 0이고 나머지는 w > 0입니다. 이 꼭짓점들은 위 규칙을 사용하여 3차원 점으로 변환됩니다. 개념적으로 w < 0인 점은 "사용자 뒤"에 있으므로 보이지 않아야 합니다.
.transformed { height: 100px; width: 100px; background: lime; transform: perspective(50px) translateZ(100px); }
박스의 모든 꼭짓점의 z 좌표가 perspective보다 크므로, 박스는 사용자 뒤에 있고 표시되지 않습니다. 수식적으로, (x, y)는 먼저 (x, y, 0, 1)이 되고, 그 다음 (x, y, 100, 1)로 이동됩니다. perspective 적용 후에는 (x, y, 100, −1)이 되어 w가 음수이므로 표시되지 않습니다. w < 0을 별도로 처리하지 않는 구현은 이 점을 (−x, −y, −100)으로 잘못 표시할 수 있습니다. −1로 나누어 박스를 반전시키는 것입니다.
.transformed { height: 100px; width: 100px; background: radial-gradient(yellow, blue); transform: perspective(50px) translateZ(50px); }
이 경우 박스가 위쪽으로 이동하여 사용자가 바라보는 위치에 놓이게 됩니다. 즉, 박스를 점점 더 가까이 가져와 시야 전체를 채우는 것과 같습니다. 기본 transform-origin이 박스의 중앙(노란색)이므로 화면은 노란색으로 채워집니다.
수식적으로, (x, y)는 먼저 (x, y, 0, 1)이 되고, 그 다음 (x, y, 50, 1)로 이동됩니다. perspective 적용 후에는 (x, y, 50, 0)이 됩니다. 중심 기준으로 왼쪽 위 꼭짓점은 (−50, −50)이므로, (−50, −50, 50, 0)이 됩니다. 이 값은 매우 멀리 왼쪽 위로 변환되며, 예를 들어 (−5000, −5000, 5000) 등이 될 수 있습니다. 다른 꼭짓점도 마찬가지로 멀리 이동합니다. radial-gradient는 박스 전체에 늘어나므로, 스크롤 없이 보이는 부분은 가운데 픽셀의 색상(노란색)이 됩니다. 하지만 박스는 실제로 무한하지 않으므로 사용자가 가장자리(파란색 부분)까지 스크롤할 수 있습니다.
.transformed { height: 50px; width: 50px; background: lime; border: 25px solid blue; transform-origin: left; transform: perspective(50px) rotateY(-45deg); }
박스가 사용자 쪽으로 회전되고, 왼쪽 가장자리는 고정된 상태에서 오른쪽 가장자리가 가까워집니다. 오른쪽 가장자리는 z = 70.7px에 위치하며, 이는 perspective 50px보다 가깝습니다. 따라서 오른쪽 가장자리는 "사용자 뒤"로 사라지고, 보이는 부분은 오른쪽으로 무한히 늘어납니다.
수식적으로, 박스의 오른쪽 위 꼭짓점은 transform-origin 기준으로 원래 (100, −50)입니다. 첫 번째로 (100, −50, 0, 1)로 확장되고, 지정된 변환을 적용하면 대략 (70.71, −50, 70.71, −0.4142)가 됩니다. w = −0.4142 < 0이므로 w < 0인 부분을 잘라내야 합니다. 이 결과로 새로운 오른쪽 위 꼭짓점은 (50, −50, 50, 0)이 되고, 이는 (5000, −5000, 5000)처럼 같은 방향으로 멀리 변환됩니다. 아래 오른쪽 꼭짓점도 마찬가지로 멀리 이동합니다. 결과적으로 박스는 화면 오른쪽을 넘어서까지 늘어납니다.
박스는 여전히 유한하므로 사용자가 전체를 보려면 스크롤할 수 있습니다. 그러나 오른쪽 부분은 잘려서 아무리 스크롤해도 원래 박스의 오른쪽 30px 정도는 보이지 않습니다. 파란색 테두리는 25px로, 왼쪽, 위, 아래에는 보이지만 오른쪽에는 보이지 않습니다.
한 꼭짓점 또는 세 꼭짓점이 w < 0인 경우에도 기본 처리 방법은 동일합니다. 이 경우 w < 0 부분을 잘라내면 사각형 대신 삼각형이나 오각형이 됩니다.
5. 개별 변환 속성: translate, scale, rotate 속성
translate, rotate, scale 속성을 사용하면 작성자는 간단한 변환을 각각 독립적으로 지정할 수 있습니다. 이는 일반적인 사용자 인터페이스 사용 방식에 맞게 동작하며, transform에서 translate(), rotate(), scale()의 순서를 기억하지 않고도 각각 독립적으로 화면 좌표계에서 동작하도록 합니다.
이름: | translate |
---|---|
값: | none | <length-percentage> [ <length-percentage> <length>? ]? |
초기값: | none |
적용 대상: | 변환 가능한 요소 |
상속: | no |
백분율: | 참조 박스의 너비(첫 번째 값) 또는 높이(두 번째 값)를 기준으로 함 |
계산된 값: | 키워드 none 또는 계산된 <length-percentage> 값 쌍과 절대 길이 |
정식 순서: | 문법에 따름 |
애니메이션 타입: | 계산된 값 기준, 단 none은 아래 참고 |
translate 속성은 1~3개의 값을 받으며, 각각 한 축에 대한 이동을 지정합니다(X, Y, Z 순서). 두 번째 또는 세 번째 값이 없으면 0px으로 기본 처리됩니다.
세 번째 값이 생략되거나 0이면 2D 이동을 지정하는 것이며, translate() 함수와 동일합니다. 그렇지 않으면 3D 이동을 지정하며, translate3d() 함수와 동일합니다.
참고: resolved value로서의 translate 속성 값은 계산된 값이며,
getComputedStyle()
결과에 백분율 값이 포함됩니다.
이름: | rotate |
---|---|
값: | none | <angle> | [ x | y | z | <number>{3} ] && <angle> |
초기값: | none |
적용 대상: | 변환 가능한 요소 |
상속: | no |
백분율: | 해당 없음 |
계산된 값: | 키워드 none 또는 <angle> + 축(3개의 <number> 리스트) |
정식 순서: | 문법에 따름 |
애니메이션 타입: | SLERP 방식, 단 none은 아래 참고 |
rotate 속성은 요소를 회전시키는 각도를 받고, 필요에 따라 회전 축도 지정할 수 있습니다.
회전 축은 x, y, 또는 z 키워드로 지정할 수 있습니다. 이는 해당 축을 중심으로 회전하며, rotateX(), rotateY(), rotateZ() 변환 함수와 동일합니다. 또는, x, y, z 성분을 가진 원점 중심 벡터(세 개의 숫자)를 명시적으로 지정하여 rotate3d() 함수와 동일하게 할 수 있습니다.
행동상의 차이는 없습니다. 단순한 <angle>만 지정한 회전과 z축을 중심으로 지정한 회전(키워드 z 사용, 또는 벡터의 앞 두 성분이 0이고 세 번째 성분이 양수인 경우) 모두 rotate() 함수와 동일한 2D 회전입니다. 예를 들어, rotate: 30deg, rotate: z 30deg, rotate: 0 0 1 30deg는 모두 동일합니다.
이름: | scale |
---|---|
값: | none | [ <number> | <percentage> ]{1,3} |
초기값: | none |
적용 대상: | 변환 가능한 요소 |
상속: | no |
백분율: | 해당 없음 |
계산된 값: | 키워드 none 또는 3개의 <number> 리스트 |
정식 순서: | 문법에 따름 |
애니메이션 타입: | 계산된 값 기준, 단 none은 아래 참고 |
scale 속성은 1~3개의 값을 받고, 각각 한 축의 스케일을 지정합니다(X, Y, Z 순서).
Y 값이 없으면, X 값과 동일하게 기본 처리됩니다.
Z 값이 없으면, 1로 기본 처리됩니다.
세 번째 값이 생략, 1 또는 100%이면, 2D 스케일이며, scale() 함수와 동일합니다. 그렇지 않으면, 3D 스케일이며, scale3d() 함수와 동일합니다.
세 번째 값이 생략된 것과 1 또는 100%인 것은 동작상 차이가 없습니다.
<percentage>는 <number>와 동일하며, 예를 들어 scale: 100%는 scale: 1과 같습니다. 지정값 및 계산값 직렬화 시 숫자가 사용됩니다.
세 속성 모두 (기본값도) none 값을 사용할 수 있으며, 이는 변환을 전혀 적용하지 않습니다. 특히, 이 값은 스태킹 컨텍스트나 모든 자손의 포함 블록 생성을 유발하지 않습니다. 그 외 모든 값 (예: translate: 0px처럼 “단위” 변환 포함) 은 변환의 일반 규칙에 따라 스태킹 컨텍스트와 모든 자손의 포함 블록을 생성합니다.
translate, rotate, scale이 애니메이션 또는 트랜지션 중일 때, from 값 또는 to 값(둘 중 하나만)이 none이면, none 값은 해당 단위(identity) 값(translate: 0px, rotate: 0deg, scale: 1)으로 대체됩니다.
5.1. 직렬화
이 속성들은 두 가지 뚜렷한 동작 모드(변환 없음 vs 변환 있음)를 가지므로, 직렬화 시 이를 고려해야 합니다:
- translate에 대해
-
변환 값이 지정된 경우, 속성은 1~3개의 값으로 직렬화되어야 합니다. (관례적으로 두 번째와 세 번째 값이 0px일 때(기본값), 또는 세 번째 값만 0px일 때, 직렬화 시 해당 0px 값은 생략해야 합니다).
원래 none이 지정되었을 때에만 none 키워드로 직렬화해야 합니다. (단위 변환은 포함되지 않으므로, 0px으로 직렬화해야 합니다.)
- rotate에 대해
-
z축(즉, 2D) 회전이 지정된 경우, 속성은 <angle>만으로 직렬화되어야 합니다.
기타 회전이 지정된 경우, 축이 지정된 상태로 직렬화되어야 하며, 축이 x나 y축에 평행할 경우 적절한 키워드로 직렬화되어야 합니다.
원래 none이 지정되었을 때에만 none 키워드로 직렬화해야 합니다. (단위 변환은 포함되지 않으므로, 0deg로 직렬화해야 합니다.)
- scale에 대해
-
스케일이 지정된 경우, 속성은 1~3개의 값으로만 직렬화되어야 합니다. 관례적으로 세 번째 값이 1(기본값)일 때, 직렬화 시 생략해야 합니다. 세 번째 값이 생략되고 두 번째 값이 첫 번째 값과 같을 때(기본값), 직렬화 시 두 번째 값도 생략합니다.
원래 none이 지정되었을 때에만 none 키워드로 직렬화해야 합니다. (단위 변환은 포함되지 않으므로, 1로 직렬화해야 합니다.)
6. 현재 변환 행렬
변환 행렬 계산은 다음과 같이 수정됩니다:
변환 행렬은 transform, transform-origin, translate, rotate, scale, offset 속성으로부터 다음과 같이 계산됩니다:
-
단위 행렬로 시작합니다.
-
transform-origin의 계산된 X, Y, Z 값만큼 이동(translate)합니다.
-
translate의 계산된 X, Y, Z 값만큼 이동합니다.
-
scale의 계산된 X, Y, Z 값만큼 스케일합니다.
-
offset에 지정된 변환만큼 이동 및 회전합니다.
-
transform에 있는 각 변환 함수를 왼쪽에서 오른쪽 순서로 곱합니다.
-
transform-origin의 계산된 X, Y, Z 값의 음수만큼 이동합니다.
7. transform-style 속성
이름: | transform-style |
---|---|
값: | flat | preserve-3d |
초기값: | flat |
적용 대상: | 변환 가능한 요소 |
상속: | no |
백분율: | N/A |
계산된 값: | 지정된 키워드 |
정식 순서: | 문법에 따름 |
애니메이션 타입: | 불연속(discrete) |
사용 값: | 그룹화 속성이 있으면 flat, 없으면 지정된 키워드 |
transform-style의 계산된 값이 preserve-3d인 변환 가능한 요소는 스태킹 컨텍스트와 모든 자손의 포함 블록을 설정합니다. 사용 값이 preserve-3d이면 3D 렌더링 컨텍스트도 설정 또는 확장합니다.
7.1. 그룹화 속성 값
아래 CSS 속성 값들은 UA가 자손 요소들을 먼저 평면화하여 렌더링해야 하므로, 해당 요소의 preserve-3d의 사용 값을 flat으로 강제합니다.
-
opacity: 1보다 작은 값
-
filter: none 이외의 값
-
clip-path: none 이외의 값
-
isolation: 사용 값이 isolate
-
mask-image: none 이외의 값
-
mask-border-source: none 이외의 값
-
mix-blend-mode: normal 이외의 값
-
contain: paint 및 페인트 포함을 유발하는 모든 속성/값 조합 참고: used value에 영향을 주는 모든 속성 포함, 예: content-visibility: hidden.
8. perspective 속성
이름: | perspective |
---|---|
값: | none | <length [0,∞]> |
초기값: | none |
적용 대상: | 변환 가능한 요소 |
상속: | no |
백분율: | N/A |
계산된 값: | 키워드 none 또는 절대 길이 |
정식 순서: | 문법에 따름 |
애니메이션 타입: | 계산된 값 기준 |
- <length [0,∞]>
-
투영의 중심까지의 거리입니다.
<length> 값이 너무 작으면 렌더링 결과가 비정상적이거나 변환 계산의 수치 정확도가 떨어질 수 있으므로, 1px 미만 값은 렌더링 목적상 1px로 처리해야 합니다. (이 클램핑은 내부 값에는 영향을 주지 않으므로, 스타일시트에 perspective: 0;이 있으면 직렬화 결과는 0으로 남습니다.)
- none
-
원근 변환이 적용되지 않습니다. 수학적으로는 <length> 값이 무한대인 것과 유사합니다. 모든 객체가 평면에 평평하게 보입니다.
이 속성에 none 이외의 값이 사용되면 스태킹 컨텍스트가 생성됩니다. 또한 모든 자손의 포함 블록도 생성됩니다. 이는 transform 속성과 동일합니다.
perspective에서 스태킹 컨텍스트나 포함 블록을 반드시 생성할 필요는 없지만, 웹 호환성 때문에 변경할 수 없을 수도 있습니다.
perspective 및 perspective-origin 속성 값은 위에서 설명한 대로 원근 행렬 계산에 사용됩니다.
9. perspective-origin 속성
perspective-origin 속성은 perspective 속성의 기준점을 설정합니다. 이는 해당 요소의 자식들을 볼 때, 시청자가 바라보는 X, Y 위치를 효과적으로 지정합니다.
이름: | perspective-origin |
---|---|
값: | <position> |
초기값: | 50% 50% |
적용 대상: | 변환 가능한 요소 |
상속: | no |
백분율: | reference box의 크기를 기준으로 함 |
계산된 값: | background-position 참고 |
정식 순서: | 문법에 따름 |
애니메이션 타입: | 계산된 값 기준 |
perspective 및 perspective-origin 속성 값은 위에서 설명한 대로 perspective matrix 계산에 사용됩니다.
perspective-origin의 값은 reference box의 왼쪽 위 모서리로부터의 오프셋을 나타냅니다.
- <percentage>
-
수평 방향의 백분율은 reference box의 너비를 기준으로 합니다. 수직 방향의 백분율은 reference box의 높이를 기준으로 합니다. 수평 및 수직 오프셋 값은 reference box의 왼쪽 위 모서리로부터의 오프셋입니다.
- <length>
-
길이 값은 고정된 길이 오프셋을 제공합니다. 수평 및 수직 오프셋은 reference box의 왼쪽 위 모서리로부터의 오프셋입니다.
- top
-
한 개 또는 두 개의 값이 주어졌을 때 수직 위치는 0%로 계산되며, 그렇지 않으면 다음 오프셋의 기준점을 상단으로 지정합니다.
- right
-
한 개 또는 두 개의 값이 주어졌을 때 수평 위치는 100%로 계산되며, 그렇지 않으면 다음 오프셋의 기준점을 오른쪽으로 지정합니다.
- bottom
-
한 개 또는 두 개의 값이 주어졌을 때 수직 위치는 100%로 계산되며, 그렇지 않으면 다음 오프셋의 기준점을 하단으로 지정합니다.
- left
-
한 개 또는 두 개의 값이 주어졌을 때 수평 위치는 0%로 계산되며, 그렇지 않으면 다음 오프셋의 기준점을 왼쪽으로 지정합니다.
- center
-
수평 위치가 별도로 지정되지 않은 경우 50%(left 50%)로 계산되며, 수직 위치가 지정된 경우 50%(top 50%)로 계산됩니다.
perspective-origin 속성은 height와 같은 resolved value 특례 속성입니다. [CSSOM]
10. backface-visibility 속성
이름: | backface-visibility |
---|---|
값: | visible | hidden |
초기값: | visible |
적용 대상: | 변환 가능한 요소 |
상속: | no |
백분율: | N/A |
계산된 값: | 지정된 키워드 |
정식 순서: | 문법에 따름 |
애니메이션 타입: | 불연속(discrete) |
backface-visibility: hidden이 적용된 요소의 표시 여부는 다음과 같이 결정됩니다:
-
요소의 누적 3D 변환 행렬을 계산합니다.
-
행렬의 3행 3열 성분이 음수이면, 요소를 숨겨야 하며 그렇지 않으면 표시됩니다.
Backface-visibility는 m33만으로 판단할 수 없음. #917 참고.
참고: 이 정의의 논리는 다음과 같습니다. 요소를 두께가 극히 얇은 x–y 평면의 직사각형으로 가정합니다. 변환되지 않은 요소의 앞면 좌표는 (x, y, ε), 뒷면은 (x, y, −ε)입니다(ε는 매우 작은 값). 변환 후 앞면이 뒷면보다 사용자에게 더 가까운지(더 높은 z값) 판단하고자 합니다. 앞면의 z 좌표는 m13x + m23y + m33ε + m43이고, 뒷면은 m13x + m23y − m33ε + m43입니다. 첫 번째 값이 두 번째 값보다 크려면 m33 > 0 이어야 합니다.(0이면 앞면과 뒷면이 동일 거리, 대략 90도 회전 등으로 보이지 않음. 실제로 사라지는 것이므로 신경 쓸 필요 없음)
11. SVG 및 3D 변환 함수
이 명세는 3차원 변환 함수가 다음 컨테이너 요소에 적용되는 것을 명시적으로 요구합니다:
a
,
g
,
svg
,
모든 그래픽 요소, 모든 그래픽 참조 요소 및 SVG
foreignObject
요소.
3차원 변환 함수 및 perspective, perspective-origin, transform-style, backface-visibility 속성은 다음
요소에는 사용할 수 없습니다:
clipPath
,
linearGradient
,
radialGradient
및
pattern
.
변환 리스트에 3차원 변환 함수가 포함되어 있으면, 전체 변환 리스트는 무시되어야 합니다. 앞서 명시된 모든 속성 값도 무시되어야 합니다. 이러한 요소에 포함된 변환 가능한 요소는 3차원 변환 함수를 가질
수 있습니다.
clipPath
,
mask
,
pattern
요소는 UA가 자손 요소를 먼저 평면화하여 렌더링해야 하므로, transform-style:
preserve-3d의 동작을 덮어씁니다.
vector-effect 속성이 non-scaling-stroke로 설정되어 있고 객체가 3D 렌더링 컨텍스트 내에 있으면 해당 속성은 스트로크에 아무런 영향을 주지 않습니다.
SVG 내 3D 변환 함수의 문법을 2D 함수처럼 공식적으로 기술 필요.
12. 변환 함수
transform 속성의 값은 <transform-function> 리스트입니다. 허용되는 변환 함수 집합은 아래와 같습니다. 이 명세에서 <angle>이 사용되는 모든 곳에서, 0과 같은 <number>도 허용되며 이는 0도의 각도와 동일하게 처리됩니다. 수평 이동에서의 백분율은 reference box의 너비를 기준으로 하며, 수직 이동에서는 reference box의 높이를 기준으로 합니다. scale 함수의 백분율은 숫자와 동일하며, 지정값 직렬화 시 숫자로 직렬화됩니다. 예를 들어, scale3d(50%, 100%, 150%)는 scale3d(0.5, 1, 1.5)로 직렬화됩니다.
12.1. 2D 변환 함수
[css-transforms-1]에서 정의된 scale 함수는 이제 백분율을 지원합니다.
- scale() = scale( [ <number> |
<percentage> ]#{1,2} )
- scaleX() = scaleX( [ <number> | <percentage> ] )
- scaleY() = scaleY( [ <number> | <percentage> ] )
- scaleX() = scaleX( [ <number> | <percentage> ] )
-
css-transforms-1에서 정의된 것과 같지만, 위에서 설명한 것처럼 백분율도 허용합니다.
12.2. 3D 변환 함수
아래 3d 변환 함수에서 <zero>는 0deg와 동일하게 동작합니다. ("단위 없는 0" 각도는 레거시 호환성 때문에 유지됨.)
- matrix3d() = matrix3d( <number>#{16} )
-
16개의 값으로 구성된 4x4 동차 행렬(컬럼 우선 순서)로 3D 변환을 지정합니다.
- translate3d() = translate3d( <length-percentage> , <length-percentage> , <length> )
-
첫 번째, 두 번째, 세 번째 매개변수 tx, ty, tz로 이루어진 벡터 [tx,ty,tz] 만큼 3D 이동을 지정합니다.
- translateZ() = translateZ( <length> )
-
[0,0,tz] 벡터로 지정된 Z 방향으로 3D 이동을 지정합니다.
- scale3d() = scale3d( [ <number> | <percentage> ]#{3} )
-
세 매개변수로 지정된 [sx,sy,sz] 스케일 벡터만큼 3D 스케일 동작을 지정합니다.
- scaleZ() = scaleZ( [ <number> | <percentage> ] )
-
[1,1,sz] 스케일 벡터로 지정된 3D 스케일 동작을 지정합니다. sz는 해당 매개변수 값입니다.
- rotate3d() = rotate3d( <number> , <number> , <number> , [ <angle> | <zero> ] )
-
첫 세 매개변수로 지정된 [x,y,z] 방향 벡터를 기준으로 마지막 매개변수의 각도만큼 3D 회전을 지정합니다. [0,0,0]처럼 정규화할 수 없는 방향 벡터는 회전이 적용되지 않습니다.
참고: 벡터 끝에서 원점을 바라보면 시계 방향 회전입니다.
- rotateX() = rotateX( [ <angle> | <zero> ] )
-
rotate3d(1, 0, 0, <angle>)과 동일합니다.
- rotateY() = rotateY( [ <angle> | <zero> ] )
-
rotate3d(0, 1, 0, <angle>)과 동일합니다.
- rotateZ() = rotateZ( [ <angle> | <zero> ] )
-
rotate3d(0, 0, 1, <angle>)과 동일하며, 이는 2D 변환 rotate(<angle>)과 동일한 3D 변환입니다.
- perspective() = perspective( <length [0,∞]> | none )
-
원근 투영 행렬을 지정합니다. 이 행렬은 Z 값에 따라 X, Y 방향으로 점을 스케일링하며, Z가 양수이면 원점에서 멀어지고, 음수이면 원점 쪽으로 가까워집니다. z=0 평면의 점은 변화가 없습니다. 매개변수는 z=0 평면에서 시청자까지의 거리를 나타냅니다. 값이 작을수록 원근 효과가 더 강해집니다. 예를 들어, 1000px은 약간의 축소 효과, 200px은 극단적 원근 효과를 줍니다.
깊이 값이 1px 미만이면, 렌더링, resolved value 계산, transform의 결과, 보간 결과 모두에서 1px로 처리해야 합니다.
참고: 위 규칙은 perspective() 함수를 행렬로 변환해야 할 때의 경우를 포함하려고 만든 것입니다.
12.3. 변환 함수 프리미티브와 파생 함수
일부 변환 함수는 더 일반적인 변환 함수로 표현될 수 있습니다. 이러한 변환 함수는 파생 변환 함수(derived transform functions)라 하며, 일반적인 변환 함수는 프리미티브 변환 함수(primitive transform functions)라 합니다. 3차원 프리미티브와 그 파생 변환 함수는 다음과 같습니다:
- translate3d()
- <translateX()>, <translateY()>, translateZ(), <translate()>에 해당합니다.
- scale3d()
- <scaleX()>, <scaleY()>, scaleZ(), <scale()>에 해당합니다.
- rotate3d()
- <rotate()>, rotateX(), rotateY(), rotateZ()에 해당합니다.
2차원 프리미티브와 3차원 프리미티브를 모두 가지는 파생 변환 함수의 경우, 사용되는 프리미티브는 문맥(context)에 따라 결정됩니다. 자세한 내용은 프리미티브 및 파생 변환 함수의 보간을 참조하세요.
13. 행렬 보간
두 행렬 간의 보간 시, 각 행렬은 대응하는 이동(translation), 회전(rotation), 스케일(scale), 왜곡(skew) 및 (3D 행렬의 경우) 원근(perspective) 값으로 분해됩니다. 분해된 행렬의 각 대응 컴포넌트들은 수치적으로 보간되고, 마지막 단계에서 다시 행렬로 합성(recompose)됩니다.
13.1. 3D 행렬의 보간
13.1.1. 3D 행렬 분해
아래 의사코드는 "Graphics Gems II, edited by Jim Arvo"의 "unmatrix" 방법을 기반으로 하지만, Gimbal Lock 문제를 피하기 위해 오일러 각 대신 쿼터니언(Quaternion)을 사용하도록 수정되었습니다.
다음의 의사코드는 4x4 동차 행렬(homogeneous matrix)에 적용됩니다:
Input: matrix ; a 4x4 matrix Output: translation ; a 3 component vector scale ; a 3 component vector skew ; skew factors XY,XZ,YZ represented as a 3 component vector perspective ; a 4 component vector quaternion ; a 4 component vector Returns false if the matrix cannot be decomposed, true if it can // Normalize the matrix. if (matrix[3][3] == 0) return false for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) matrix[i][j] /= matrix[3][3] // perspectiveMatrix is used to solve for perspective, but it also provides // an easy way to test for singularity of the upper 3x3 component. perspectiveMatrix = matrix for (i = 0; i < 3; i++) perspectiveMatrix[i][3] = 0 perspectiveMatrix[3][3] = 1 if (determinant(perspectiveMatrix) == 0) return false // First, isolate perspective. if (matrix[0][3] != 0 || matrix[1][3] != 0 || matrix[2][3] != 0) // rightHandSide is the right hand side of the equation. rightHandSide[0] = matrix[0][3] rightHandSide[1] = matrix[1][3] rightHandSide[2] = matrix[2][3] rightHandSide[3] = matrix[3][3] // Solve the equation by inverting perspectiveMatrix and multiplying // rightHandSide by the inverse. inversePerspectiveMatrix = inverse(perspectiveMatrix) transposedInversePerspectiveMatrix = transposeMatrix4(inversePerspectiveMatrix) perspective = multVecMatrix(rightHandSide, transposedInversePerspectiveMatrix) else // No perspective. perspective[0] = perspective[1] = perspective[2] = 0 perspective[3] = 1 // Next take care of translation for (i = 0; i < 3; i++) translate[i] = matrix[3][i] // Now get scale and shear. 'row' is a 3 element array of 3 component vectors for (i = 0; i < 3; i++) row[i][0] = matrix[i][0] row[i][1] = matrix[i][1] row[i][2] = matrix[i][2] // Compute X scale factor and normalize first row. scale[0] = length(row[0]) row[0] = normalize(row[0]) // Compute XY shear factor and make 2nd row orthogonal to 1st. skew[0] = dot(row[0], row[1]) row[1] = combine(row[1], row[0], 1.0, -skew[0]) // Now, compute Y scale and normalize 2nd row. scale[1] = length(row[1]) row[1] = normalize(row[1]) skew[0] /= scale[1]; // Compute XZ and YZ shears, orthogonalize 3rd row skew[1] = dot(row[0], row[2]) row[2] = combine(row[2], row[0], 1.0, -skew[1]) skew[2] = dot(row[1], row[2]) row[2] = combine(row[2], row[1], 1.0, -skew[2]) // Next, get Z scale and normalize 3rd row. scale[2] = length(row[2]) row[2] = normalize(row[2]) skew[1] /= scale[2] skew[2] /= scale[2] // At this point, the matrix (in rows) is orthonormal. // Check for a coordinate system flip. If the determinant // is -1, then negate the matrix and the scaling factors. pdum3 = cross(row[1], row[2]) if (dot(row[0], pdum3) < 0) for (i = 0; i < 3; i++) scale[i] *= -1; row[i][0] *= -1 row[i][1] *= -1 row[i][2] *= -1 // Now, get the rotations out quaternion[0] = 0.5 * sqrt(max(1 + row[0][0] - row[1][1] - row[2][2], 0)) quaternion[1] = 0.5 * sqrt(max(1 - row[0][0] + row[1][1] - row[2][2], 0)) quaternion[2] = 0.5 * sqrt(max(1 - row[0][0] - row[1][1] + row[2][2], 0)) quaternion[3] = 0.5 * sqrt(max(1 + row[0][0] + row[1][1] + row[2][2], 0)) if (row[2][1] > row[1][2]) quaternion[0] = -quaternion[0] if (row[0][2] > row[2][0]) quaternion[1] = -quaternion[1] if (row[1][0] > row[0][1]) quaternion[2] = -quaternion[2] return true
13.1.2. 분해된 3D 행렬 값의 보간
분해된 translation, scale, skew, perspective 값의 각 컴포넌트는 출발 행렬과 도착 행렬에서 각각 선형적으로 보간(linear interpolation)됩니다.
참고: 예를 들어, 출발 행렬의 translate[0]
와 도착 행렬의
translate[0]
는 수치적으로 보간되고, 그 결과는 애니메이션 요소의 이동 값으로 사용됩니다.
분해된 출발 행렬의 쿼터니언(quaternion)은 분해된 도착 행렬의 쿼터니언과 구면 선형 보간법(Slerp)으로 보간되며, 아래의 의사코드처럼 처리합니다:
Input: quaternionA ; a 4 component vector quaternionB ; a 4 component vector t ; interpolation parameter with 0 <= t <= 1 Output: quaternionDst ; a 4 component vector product = dot(quaternionA, quaternionB) // Clamp product to -1.0 <= product <= 1.0 product = min(product, 1.0) product = max(product, -1.0) if (abs(product) == 1.0) quaternionDst = quaternionA return theta = acos(product) w = sin(t * theta) / sqrt(1 - product * product) for (i = 0; i < 4; i++) quaternionA[i] *= cos(t * theta) - product * w quaternionB[i] *= w quaternionDst[i] = quaternionA[i] + quaternionB[i] return
13.1.3. 3D 행렬로 재조합
보간 후 결과 값들은 요소의 사용자 공간(user space)을 변환하는 데 사용됩니다. 한 가지 방법은 이 값들을 4x4 행렬로 다시 합성(recompose)하는 것으로, 아래 의사코드와 같이 처리할 수 있습니다:
Input: translation ; a 3 component vector scale ; a 3 component vector skew ; skew factors XY,XZ,YZ represented as a 3 component vector perspective ; a 4 component vector quaternion ; a 4 component vector Output: matrix ; a 4x4 matrix Supporting functions (matrix is a 4x4 matrix): matrix multiply(matrix a, matrix b) returns the 4x4 matrix product of a * b // apply perspective for (i = 0; i < 4; i++) matrix[i][3] = perspective[i] // apply translation for (i = 0; i < 4; i++) for (j = 0; j < 3; j++) matrix[3][i] += translation[j] * matrix[j][i] // apply rotation x = quaternion[0] y = quaternion[1] z = quaternion[2] w = quaternion[3] // Construct a composite rotation matrix from the quaternion values // rotationMatrix is a identity 4x4 matrix initially rotationMatrix[0][0] = 1 - 2 * (y * y + z * z) rotationMatrix[0][1] = 2 * (x * y - z * w) rotationMatrix[0][2] = 2 * (x * z + y * w) rotationMatrix[1][0] = 2 * (x * y + z * w) rotationMatrix[1][1] = 1 - 2 * (x * x + z * z) rotationMatrix[1][2] = 2 * (y * z - x * w) rotationMatrix[2][0] = 2 * (x * z - y * w) rotationMatrix[2][1] = 2 * (y * z + x * w) rotationMatrix[2][2] = 1 - 2 * (x * x + y * y) matrix = multiply(matrix, rotationMatrix) // apply skew // temp is a identity 4x4 matrix initially if (skew[2]) temp[2][1] = skew[2] matrix = multiply(matrix, temp) if (skew[1]) temp[2][1] = 0 temp[2][0] = skew[1] matrix = multiply(matrix, temp) if (skew[0]) temp[2][0] = 0 temp[1][0] = skew[0] matrix = multiply(matrix, temp) // apply scale for (i = 0; i < 3; i++) for (j = 0; j < 4; j++) matrix[i][j] *= scale[i] return
14. 프리미티브 및 파생 변환 함수의 보간
이름과 인자 개수가 동일한 두 변환 함수는 변환 없이 수치적으로 보간됩니다. 계산된 값은 동일한 변환 함수 타입과 동일한 인자 개수를 가집니다. <matrix()>, <matrix3d()>, <perspective()>에는 특별 규칙이 적용됩니다.
<matrix()>, matrix3d(), perspective() 변환 함수는 먼저 4x4 행렬로 변환되고, 이후 행렬 보간 섹션에 따라 보간됩니다.
프리미티브 rotate3d()의 보간에서는, 변환 함수의 방향 벡터가 먼저 정규화됩니다. 정규화된 벡터가 같지 않고 두 회전 각도가 모두 0이 아니면, 변환 함수는 먼저 4x4 행렬로 변환된 후 행렬 보간 섹션에 따라 보간됩니다. 그렇지 않으면 회전 각도만 수치적으로 보간되고, 0이 아닌 각도의 회전 벡터가 사용되거나(둘 다 0이면 (0, 0, 1) 사용) 사용됩니다.
translate(0)과 translate(100px) 두 변환 함수는 동일한 타입, 동일한 인자 개수를 가지므로 수치적으로 보간 가능합니다. translateX(100px)은 타입이 다르고 translate(100px, 0)은 인자 개수가 다르므로, 이 변환 함수들은 변환 없이 바로 보간될 수 없습니다.
프리미티브가 동일한 서로 다른 타입의 변환 함수, 또는 동일 타입이지만 인자 개수가 다른 변환 함수도 보간이 가능합니다. 두 변환 함수 모두 먼저 공통 프리미티브로 변환된 후 수치적으로 보간됩니다. 계산된 값은 보간된 인자를 가진 프리미티브가 됩니다.
다음 예시는 div 박스에 마우스를 올릴 때 translateX(100px)에서 translateY(100px)로 3초간 전환되는 트랜지션을 설명합니다. 두 변환 함수 모두 동일한 프리미티브 translate()에서 파생되므로 보간이 가능합니다.
div {
transform : translateX ( 100 px );
}
div:hover {
transform : translateY ( 100 px );
transition : transform 3 s ;
}
트랜지션 동안 두 변환 함수 모두 공통 프리미티브로 변환됩니다. translateX(100px)은 translate(100px, 0)으로, translateY(100px)은 translate(0, 100px)으로 변환됩니다. 이후 두 변환 함수는 수치적으로 보간될 수 있습니다.
두 변환 함수가 2차원 공간의 프리미티브를 공유한다면, 두 변환 함수 모두 2차원 프리미티브로 변환됩니다. 하나 또는 두 변환 함수가 3차원 변환 함수라면, 공통 3차원 프리미티브가 사용됩니다.
이 예시에서는 2차원 변환 함수가 3차원 변환 함수로 애니메이션됩니다. 공통 프리미티브는 translate3d()입니다.
div {
transform : translateX ( 100 px );
}
div:hover {
transform : translateZ ( 100 px );
transition : transform 3 s ;
}
먼저 translateX(100px)은 translate3d(100px, 0, 0)으로, translateZ(100px)은 translate3d(0, 0, 100px)으로 변환됩니다. 그런 다음 변환된 두 함수는 수치적으로 보간됩니다.
15. 변환 리스트의 덧셈 및 누적
Vresult = Va + Vb - 1
div. animate( { transform: [ 'scale(1)' , 'scale(2)' ] }, { duration: 1000 , easing: 'ease' , } );
이를 아래처럼 확장하면 기대한 대로 동작합니다:
div. animate( { transform: [ 'scale(1)' , 'scale(2)' ] }, { duration: 1000 , easing: 'ease' , iterations: 5 , iterationComposite: 'accumulate' , } );
15.1. 덧셈의 중립 원소
일부 애니메이션에서는 덧셈의 중립 원소가 필요합니다. 변환 함수에서 중립 원소란 0 또는 0 리스트입니다. 예시로 translate(0), translate3d(0, 0, 0), translateX(0), translateY(0), translateZ(0), scale(0), scaleX(0), scaleY(0), scaleZ(0), rotate(0), rotate3d(vx, vy, vz, 0) (여기서 v는 문맥 의존 벡터), rotateX(0), rotateY(0), rotateZ(0), skew(0, 0), skewX(0), skewY(0), matrix(0, 0, 0, 0, 0, 0), matrix3d(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), perspective(none) 등이 있습니다.
참고: <matrix()>, matrix3d(), perspective()의 중립 원소로의 애니메이션은 불연속 애니메이션으로 처리됩니다(§ 13 행렬 보간 참고).
16. 변환 함수의 수학적 설명
수학적으로 모든 변환 함수는 다음 형식의 4x4 변환 행렬로 표현할 수 있습니다:
$$\begin{bmatrix} m11 & m21 & m31 & m41 \\ m12 & m22 & m32 & m42 \\ m13 & m23 & m33 & m43 \\ m14 & m24 & m34 & m44 \end{bmatrix}$$
행렬에서 1 이동 단위는 요소의 로컬 좌표계에서 1픽셀에 해당합니다.
-
tx, ty, tz 파라미터로 지정된 3D 이동은 다음
행렬과 동일합니다:
$$\begin{bmatrix} 1 & 0 & 0 & tx \\ 0 & 1 & 0 & ty \\ 0 & 0 & 1 & tz \\ 0 & 0 & 0 & 1 \end{bmatrix}$$
-
sx, sy, sz 파라미터로 지정된 3D 스케일은 다음 행렬과
동일합니다:
$$\begin{bmatrix} sx & 0 & 0 & 0 \\ 0 & sy & 0 & 0 \\ 0 & 0 & sz & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}$$
-
벡터 [x,y,z]와 alpha 파라미터로 지정된 3D 회전은 다음 행렬과 동일합니다:
$$\begin{bmatrix} 1 - 2 \cdot (y^2 + z^2) \cdot sq & 2 \cdot (x \cdot y \cdot sq - z \cdot sc) & 2 \cdot (x \cdot z \cdot sq + y \cdot sc) & 0 \\ 2 \cdot (x \cdot y \cdot sq + z \cdot sc) & 1 - 2 \cdot (x^2 + z^2) \cdot sq & 2 \cdot (y \cdot z \cdot sq - x \cdot sc) & 0 \\ 2 \cdot (x \cdot z \cdot sq - y \cdot sc) & 2 \cdot (y \cdot z \cdot sq + x \cdot sc) & 1 - 2 \cdot (x^2 + y^2) \cdot sq & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}$$
여기서:
$$sc = \sin (\alpha/2) \cdot \cos (\alpha/2)$$ $$sq = \sin^2 (\alpha/2)$$
x, y, z는 정규화되어야 함 (즉, 주어진 x, y, z 값을 각각 제곱의 합의 제곱근으로 나눔).
이는 X축 회전이 다음과 같이 단순화됨을 의미합니다:$$\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 - 2 \cdot sq & -2 \cdot sc & 0 \\ 0 & 2 \cdot sc & 1 - 2 \cdot sq & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}$$
Y축 회전은 다음과 같이 단순화됩니다:
$$\begin{bmatrix} 1 - 2 \cdot sq & 0 & 2 \cdot sc & 0 \\ 0 & 1 & 0 & 0 \\ -2 \cdot sc & 0 & 1 - 2 \cdot sq & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}$$
Z축 회전은 다음과 같이 단순화됩니다:
$$\begin{bmatrix} 1 - 2 \cdot sq & -2 \cdot sc & 0 & 0 \\ 2 \cdot sc & 1 - 2 \cdot sq & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}$$
-
파라미터 d로 지정된 원근 투영 행렬은 다음과 동일합니다:
$$\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & -1/d & 1 \end{bmatrix}$$
파라미터 d가 none이면, 무한대로 간주되며 (결과 행렬은 단위 행렬이 됨).
17. SVG transform 속성
이 명세는 새로운 표현 속성 transform-origin, perspective, perspective-origin, transform-style, backface-visibility도 도입합니다.
새로 도입된 표현 속성의 값은 SVG 데이터 타입 [SVG11]의 구문 규칙을 따라 파싱됩니다.
18. SVG 애니메이션
18.1.
animate
및
set
요소
도입된 표현 속성 perspective, perspective-origin, transform-style, backface-visibility는 애니메이션이 가능합니다. transform-style과 backface-visibility는 비가산(non-additive) 속성입니다.
19. 추가 이슈
https://lists.w3.org/Archives/Public/www-style/2015Mar/0371.html에 따라, WG는 변환을 통합 "scale"로 분해하는 공식을 추가하기로 결정했습니다 (명세에서는 이미 scaleX/Y/Z로 분해하는 방법을 정의함). 이는 SVG의 non-scaling stroke 명세 등에서 사용할 수 있습니다. 공식은 여기 정의되어 있습니다.
20. 보안 및 개인정보 보호 고려 사항
이 명세는 새로운 보안 또는 개인정보 보호 고려 사항을 도입하지 않습니다.
변경사항
최근 변경사항
2020년 3월 3일 WD 이후 주요 변경사항:
-
명세는 더 이상 개별 변환 속성이 2D 또는 3D 값을 가지는지 상태를 유지할 필요가 없으며, 2D로 표현 가능한 값은 2D로 처리해야 함(자세한 내용은 #3305 참고).
참고: transform 함수에도 유사한 변경이 적용될 예정이나 아직 반영되지 않음.
- scale 속성 및 scale(), scaleX(), scaleY() 함수가 이제 백분율을 지원함(#3399 참고).
-
3D 렌더링 컨텍스트 정의와 일치하도록 여러 정의 수정:
- 3D 렌더링 컨텍스트를 설정하는 요소의 테두리, 배경 및 박스 장식은 3D 장면 뒤가 아니라 z=0에서 렌더링됨(#6238 참고).
- 누적 3D 변환 행렬에 컨텍스트 설정 요소의 transform과 부모의 perspective를 포함하도록 정의(#6191 참고).
- 페인트 포함(paint containment)을 그룹화 속성으로 정의(#6202 참고).
- none 인자를 perspective()에 지원 추가(#6488 참고).
- perspective() 값의 클램핑이 resolved value와 보간에도 적용됨을 명확히 함(#6320, #6346 참고).
- preserve-3d의 효과는 변환 가능한 요소에만 적용됨을 명확히 함(#6430 참고).
- 덧셈의 중립 원소에서 perspective()의 중립 원소를 perspective(none)으로 고정
- translate의 resolved value에 백분율이 포함된다는 점을 주석 추가(#2124 참고).
- 3D 정렬(3D sorting)을 더 정확히 설명하며, 어떤 자손이 포함되는지 명확히 하였고 Appendix E 참조를 1–7단계로 제한하지 않음(#926 참고).