수락된 프런트 엔드 과제: CSS 3D Cube

게시 됨: 2022-03-10
빠른 요약 ↬ 도전을 좋아하시나요? 한 번도 해보지 못한 일을 기꺼이 맡아서 기한 내에 하시겠습니까? 작업을 수행하는 동안 해결할 수 없는 것처럼 보이는 문제가 발생하면 어떻게 합니까? 실제 프로젝트에서 처음으로 CSS 3D 효과를 사용한 경험을 공유하고 도전에 대한 영감을 주고 싶습니다. CreativePeople 의 매니저인 Eugene이 내게 편지를 보낸 것은 평범한 날이었다. 그는 나에게 비디오를 보내며 새로운 프로젝트에 대한 개념을 개발 중이라고 설명했고 비디오에 있는 것과 같은 것을 내가 개발할 수 있는지 궁금해했습니다.

당신은 도전을 좋아합니까? 한 번도 해보지 못한 일을 기꺼이 맡아서 기한 내에 하시겠습니까? 작업을 수행하는 동안 해결할 수 없는 것처럼 보이는 문제가 발생하면 어떻게 합니까? 실제 프로젝트에서 처음으로 CSS 3D 효과를 사용한 경험을 공유하고 도전에 대한 영감을 주고 싶습니다.

크리에이티브피플의 매니저인 유진이 저에게 편지를 보낸 평범한 날이었습니다. 그는 나에게 비디오를 보내며 새로운 프로젝트에 대한 개념을 개발 중이라고 설명했고 비디오에 있는 것과 같은 것을 내가 개발할 수 있는지 궁금해했습니다.

SmashingMag에 대한 추가 정보:

  • 비어캠프: CSS 3D 실험
  • 클립 경로를 사용하여 반응형 모양 만들기 및 상자에서 벗어나기
  • 하드웨어 가속 CSS를 사용해 봅시다

축 중 하나를 중심으로 회전하는 3D 개체(정확하게는 직육면체)였습니다. 나는 이미 CSS 3D 작업에 대한 약간의 경험이 있었고 솔루션이 마음속에 형성되기 시작했습니다. 내 아이디어를 확인하기 위해 "CSS 3D 큐브"와 같은 키워드를 구글링했고 유진에게 가능하다고 대답했습니다.

점프 후 더! 아래에서 계속 읽기 ↓

유진의 다음 질문은 내가 그 프로젝트를 맡을 것인가였다. 까다로운 일을 좋아해서 거절을 못했어요. 그 당시에는 내가 무엇을 하고 있는지 몰랐지만, 결정적인 것 이상이었습니다.

도끼를 날카롭게

축에 대해 상기합시다. 전쟁 축이 아니라 숫자 선, 우리가 학교에서 공부한 3차원 데카르트 좌표계에서와 같은 축입니다. Wikipedia는 다음과 같이 알려줍니다.

3차원 공간에 대한 데카르트 좌표계는 쌍으로 수직이고 세 축 모두에 대해 단일 길이 단위를 가지며 각 축에 대한 방향을 갖는 정렬된 선(축)의 삼중항입니다.

아래 그림은 웹 브라우저에서 축의 방향을 보여줍니다.

Z축이 뷰어를 가리키는 오른손잡이 3차원 데카르트 좌표계입니다.
z축이 뷰어를 가리키는 오른손잡이 3차원 데카르트 좌표계입니다. (이미지: Wikimedia Commons) (큰 버전 보기)

x축은 가로, y축은 세로, z축은 화면에서 사용자를 향해 나오는 것처럼 보입니다. z축의 0 값은 화면의 평면입니다. 이것을 기억.

관점 정리

3D 개체를 만들려면 원근감이 있는 요소("장면"이라고 함)가 필요했습니다. 원근은 장면의 깊이이며 포함된 개체의 크기에 따라 다릅니다.

 .scene { perspective: 800px; }

원근이 너무 작으면 물체가 왜곡될 수 있습니다. 너무 크면 3D 효과가 감소합니다.

CodePen에서 Anna Selezniova(@askd)의 Pen jqgMvL을 참조하십시오.

또한 장면의 모든 개체에 대해 하나의 화각만 있습니다. 그리고 3D 효과는 시점의 위치에 따라 다릅니다.

CodePen에서 Anna Selezniova(@askd)의 Pen oxKzKv를 참조하십시오.

그렇다면 관점은 어떻게 계산할까요? 나는 그것이 회전축에 의존한다는 것을 발견했습니다. x축의 경우 높이 값에 4를 곱한 값이 맞습니다. y축의 경우 너비 값에 4를 곱한 값입니다. 다음은 제 마법의 공식입니다.

 const perspective = dimension * 4;

모든면에서 고려

원근감을 결정한 후 3D 개체를 만들기 시작했습니다. 저는 큐브가 직관적이고 예측 가능하기 때문에 큐브를 선택했습니다. 큐브 요소는 너비와 높이가 정의된(예: 200px ) 상대적으로 배치된 일반 div로 생성됩니다. preserve-3d 값을 갖는 transform-style 속성을 통해 3D 객체로 변형합니다. 3D 세계의 규칙에 따라 모든 중첩 요소를 렌더링하도록 브라우저에 지시합니다.

필자의 경우 큐브에는 절대적으로 배치된 6개의 div(또는 "측면")가 있습니다. 클래스 이름은 측면( back , left , right , top , bottom , front )의 초기 위치에 해당합니다. 마크업은 다음과 같습니다.

 <div class="scene"> <div class="cube"> <div class="side back"></div> <div class="side left"></div> <div class="side right"></div> <div class="side top"></div> <div class="side bottom"></div> <div class="side front"></div> </div> </div>

기본적으로 모든 면이 한 평면에 있습니다. 그래서 나는 그것들을 재정렬할 필요가 있었다. 그 모습은 다음과 같습니다.

CodePen에서 Anna Selezniova(@askd)의 Pen mPNwPx를 참조하십시오.

결과 CSS는 다음과 같습니다.

 .cube { position:relative; width: 200px; height: 200px; transform-style: preserve-3d; } .side { position: absolute; width: 200px; height: 200px; } .back { transform: translateZ(-100px); } .left { transform: translateX(-100px) rotateY(90deg); } .right { transform: translateX(100px) rotateY(90deg); } .top { transform: translateY(-100px) rotateX(90deg); } .bottom { transform: translateY(100px) rotateX(90deg); } .front { transform: translateZ(100px); }

큐브를 회전하려면 큐브 요소의 transform 속성을 x축을 따라 임의의 회전 각도로 설정합니다.

 .cube { transform: rotateX(42deg); }

단점 극복

과제에 따르면 큐브를 x축으로만 회전해야 하므로 왼쪽이나 오른쪽이 필요하지 않았습니다. 나머지 면의 초기 위치에 맞게 캡션을 추가했습니다.

큐브를 회전하기 시작했고 아래쪽과 뒷면의 캡션이 거꾸로 표시되는 것을 발견했습니다.

CodePen에서 Anna Selezniova(@askd)의 Pen GZVvMR을 참조하십시오.

이 문제를 해결하기 위해 x축을 따라 이 각 면을 180도 회전했습니다.

 .back { transform: translateZ(-100px) rotateX(180deg); } .bottom { transform: translateY(100px) rotateX(270deg); }

화면 너머로 나아가다

나는 측면을 실제 콘텐츠로 채우기 시작했고 즉시 또 다른 문제에 직면했습니다. 1픽셀의 점선을 표시해야 했지만 흐릿하고 보기 좋지 않았습니다.

CodePen에서 Anna Selezniova(@askd)의 Pen VjeBPg를 참조하십시오.

나는 곧 무엇이 문제인지 깨달았다. 이미지가 화면 너머로 확장되는 3D TV 광고를 기억하십니까? 내 큐브도 마찬가지였습니다.

큐브를 왼쪽이나 오른쪽에서 볼 수 있다면 중심이 화면의 평면(z축에서 0)에 있고 앞면이 화면 너머에 있다는 것을 알 수 있습니다. 따라서 시각적으로 증가하고 흐려졌습니다.

CodePen에서 Anna Selezniova(@askd)의 Pen WwVEMR을 참조하십시오.

이 문제를 해결하기 위해 z축을 따라 큐브를 이동하여 전면을 화면 평면에 정렬했습니다.

 .cube { transform:translateZ(-100px); }

이제 큐브가 거의 준비되었습니다.

CodePen에서 Anna Selezniova(@askd)의 Pen Xdvery를 참조하십시오.

매직 넘버 사용하기

축을 따라 측면을 이동하기 위해 매직 넘버 100 을 사용하고 있다는 것을 눈치채셨을 것입니다. 값 100 은 내 테스트 큐브 높이의 정확히 절반입니다. 키가 왜 반이야? 그것은 정육면체의 한 면에 새겨진 원의 반지름일 것이기 때문입니다(이것은 분명히 정사각형임).

 const offset = dimension / 2;

삼각형 프리즘을 회전해야 하는 경우 원이 삼각형에 내접됩니다. 이 경우 오프셋의 공식은 다음과 같습니다.

 const offset = dimension / (2 * Math.sqrt(3));

큐브 날려버리기

작업이 완료된 것으로 간주하려면 다른 브라우저에서 결과를 테스트해야 했습니다.

Internet Explorer에서 본 그림은 나를 우울증에 빠뜨렸습니다. 무슨 말인지 이해하려면 즐겨 사용하는 브라우저에서 아래 데모를 보십시오. Internet Explorer에서 큐브가 잘못 표시되는 한 속성을 변경했습니다. 그러나 아래 데모 아래의 단락을 읽을 때까지 소스 코드를 엿보지 마십시오.

CodePen에서 Anna Selezniova(@askd)의 Pen XKWMwV를 참조하십시오.

사실 Internet Explorer는 값이 preserve-3dtransform-style 속성을 지원하지 않습니다. 내 신뢰할 수 있는 리소스 Can I Use를 보고 이에 대해 배웠습니다(참고 1 참조). 위의 데모에서 나는 preserve-3dflat 으로 대체했습니다. 이미 알고 계셨나요? 야 내가 엿보지 말라고 했잖아!

속상했지만 포기할 생각은 없었다. 문제는 새로운 것을 배울 수 있는 기회입니다. 게다가 나는 그 도전을 받아들였다.

펄크럼을 찾아서

나는 transform-style: preserve-3d 를 사용하지 않고 3D 객체를 생성하는 방법을 찾고 있었고 결국 유용한 속성인 transform-origin 을 발견했습니다. 요소 변환의 중심점을 결정합니다. 작동 방식을 이해하는 데 도움이 될 대화식 데모를 아래에 만들었습니다.

CodePen에서 Anna Selezniova(@askd)의 Pen rLNmBp를 참조하십시오.

데모에서 요소의 3D 회전은 큐브의 앞면과 매우 유사하지 않습니까? 그게 내가 사용한 것입니다.

(그런데 3D 회전 시 backface-visibility: hidden 체크박스를 선택하려고 했는가? 이 속성은 3D 변환 시 요소의 뒷면을 숨길 때 사용한다.)

다시 시작하기

큐브를 다시 만들기 시작했습니다. 전체 장면과 상호 작용할 필요가 없었기 때문에 scene 요소의 perspective 속성을 제거하고 각 3D 변환에 추가하여 이제 모든 요소가 독립적으로 변환되도록 했습니다. 또한 각 면에 대해 새로운 속성을 설정했습니다. 큐브의 중심 위치와 동일한 값을 가진 transform-originbackface-visibility: hidden . 스타일이 변경된 방법은 다음과 같습니다.

 .scene { } .cube { position: relative; width: 200px; height: 200px; transform: perspective(800px) translateZ(-100px); } .side { position: absolute; transform-origin: 50% 50% -100px; backface-visibility: hidden; }

측면을 올바른 위치에 배치해야 했습니다. transform-origin 속성 때문에 이동할 필요가 없었고 축을 중심으로 회전하기만 했습니다. 그것은 마술과 같습니다! 어떻게 보이는지 봅시다:

CodePen에서 Anna Selezniova(@askd)의 Pen zBYwEm을 참조하십시오.

다음은 측면 배치에 대한 CSS입니다.

 .back { transform: perspective(800px) rotateY(180deg); } .top { transform: perspective(800px) rotateX(90deg); } .bottom { transform: perspective(800px) rotateX(-90deg); } .front { transform: perspective(800px); }

여기에서 새 큐브가 작동하는 것을 볼 수 있습니다.

CodePen에서 Anna Selezniova(@askd)의 Pen wWvdXd를 참조하십시오.

가이사의 것을 가이사에게 돌려주다

두 번째 큐브는 첫 번째 큐브와 모양과 회전이 동일합니다. 그러나 이 경우 각 측면을 개별적으로 변환해야 합니다. 이것은 특히 중간 회전 각도를 제어하려는 경우 쉽지 않을 수 있습니다.

또한 Chrome에서 데모를 열면 회전하는 동안 측면이 깜박이는 것을 볼 수 있습니다. 매우 실망스럽습니다.

결국 나는 transform-style: preserve-3d 의 간단한 테스트를 사용하여 두 접근 방식을 모두 적용했습니다. 첫 번째 큐브가 기본 큐브입니다. 두 번째 큐브는 preserve-3d 를 지원하지 않는 Internet Explorer 및 브라우저용입니다.

수학의 힘 사용하기

마지막으로 시차 효과를 구현해야 했습니다. 일반적으로 이 효과는 마우스 커서의 위치나 스크롤 막대의 위치에 관계없이 사용자의 동작에 응답합니다. 이 경우 효과는 회전 각도에 따라 다릅니다.

CodePen에서 Anna Selezniova(@askd)의 Pen QENyqm을 참조하십시오.

그래서, 나는 어떤 데이터를 가지고 있습니까? 먼저 캡션 위치의 시작점과 끝점, 간단히 말해서 측면 중앙에서 위아래로 offset 이 있습니다. 두 번째로 큐브의 회전 angle 가 있습니다.

공식을 개발하는 데 몇 시간을 보냈습니다. 그러다 문득 떠올랐다. 생각난 내용은 다음과 같습니다.

사인 및 코사인 함수의 그래프
사인 및 코사인 함수의 그래프(이미지: Wikimedia Commons) (큰 버전 보기)

사인과 코사인의 도움으로 각도에 따라 각 캡션의 오프셋을 쉽게 계산했습니다. 제가 생각해낸 공식은 다음과 같습니다.

 const front_offset = offset * sin(angle) * -1; const bottom_offset = offset * cos(angle); const back_offset = offset * sin(angle); const top_offset = offset * cos(angle) * -1;

합산

이제 과제가 완료되었으며 결과를 즐기고 여러분과 공유할 수 있습니다. 어떻게 작동하는지 직접 확인하십시오. 스크롤 또는 화살표 키를 사용하여 프로모션 블록을 회전합니다. 또한 오른쪽의 검은색 삼각형을 위아래로 당겨 수동으로 회전 각도를 제어해 보십시오(안타깝게도 이 기능은 Internet Explorer에서 작동하지 않습니다). 꽤 좋아 보이지 않나요? 그리고 성능은 다소 높습니다(초당 약 60프레임).

이 웹사이트의 개발에 참여하게 되어 매우 기쁩니다. 저는 CSS 3D 작업에서 유용한 경험을 얻었고 많은 흥미로운 속성을 발견했습니다. 더 중요한 것은 사람이 결코 포기해서는 안 된다는 것을 배웠다는 것입니다. 대부분의 경우 작업을 수행하는 방법을 찾을 수 있습니다.

제 이야기를 재미있게 보시고 이제 새로운 도전을 시작할 준비가 되었기를 바랍니다.