반응형, 적응형, 접근성 있는 3D 게임 메뉴를 빌드하는 방법에 관한 기본 개요입니다.
이 게시물에서는 3D 게임 메뉴 구성요소를 빌드하는 방법에 관한 생각을 공유하고자 합니다. 데모를 사용해 보세요.
동영상을 선호하는 경우 이 게시물의 YouTube 버전을 확인하세요.
개요
동영상 게임은 사용자에게 애니메이션으로 제작되고 3D 공간에 표시되는 창의적이고 특이한 메뉴를 제공하는 경우가 많습니다. 새 AR/VR 게임에서는 메뉴가 공간에 떠 있는 것처럼 보이도록 하는 것이 인기가 있습니다. 오늘은 적응형 색상 구성표와 동작 감소를 선호하는 사용자를 위한 편의 기능을 추가하여 이 효과의 필수 요소를 재현해 보겠습니다.
HTML
게임 메뉴는 버튼 목록입니다. HTML에서 이를 표현하는 가장 좋은 방법은 다음과 같습니다.
<ul class="threeD-button-set">
<li><button>New Game</button></li>
<li><button>Continue</button></li>
<li><button>Online</button></li>
<li><button>Settings</button></li>
<li><button>Quit</button></li>
</ul>
버튼 목록은 스크린 리더 기술에 잘 알려져 있으며 JavaScript나 CSS 없이 작동합니다.

CSS
버튼 목록 스타일 지정은 다음과 같은 대략적인 단계로 나눌 수 있습니다.
- 맞춤 속성 설정
- 플렉스박스 레이아웃입니다.
- 장식 의사 요소가 있는 맞춤 버튼
- 3D 공간에 요소를 배치합니다.
맞춤 속성 개요
맞춤 속성은 의미 있는 이름을 부여하여 값을 명확하게 하고, 반복되는 코드를 피하고, 하위 요소 간에 값을 공유합니다.
다음은 CSS 변수로 저장된 미디어 쿼리입니다. 맞춤 미디어라고도 합니다. 이는 전역이며 코드를 간결하고 읽기 쉽게 유지하기 위해 다양한 선택기에서 사용됩니다. 게임 메뉴 구성요소는 동작 환경설정, 시스템 색상 구성표, 디스플레이의 색상 범위 기능을 사용합니다.
@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --HDcolor (dynamic-range: high);
다음 맞춤 속성은 색상 스킴을 관리하고 마우스 위치 값을 보유하여 게임 메뉴를 마우스 오버로 상호작용할 수 있도록 합니다. 맞춤 속성의 이름을 지정하면 값의 사용 사례나 값 결과의 사용자 친화적인 이름이 표시되므로 코드 가독성이 향상됩니다.
.threeD-button-set {
--y:;
--x:;
--distance: 1px;
--theme: hsl(180 100% 50%);
--theme-bg: hsl(180 100% 50% / 25%);
--theme-bg-hover: hsl(180 100% 50% / 40%);
--theme-text: white;
--theme-shadow: hsl(180 100% 10% / 25%);
--_max-rotateY: 10deg;
--_max-rotateX: 15deg;
--_btn-bg: var(--theme-bg);
--_btn-bg-hover: var(--theme-bg-hover);
--_btn-text: var(--theme-text);
--_btn-text-shadow: var(--theme-shadow);
--_bounce-ease: cubic-bezier(.5, 1.75, .75, 1.25);
@media (--dark) {
--theme: hsl(255 53% 50%);
--theme-bg: hsl(255 53% 71% / 25%);
--theme-bg-hover: hsl(255 53% 50% / 40%);
--theme-shadow: hsl(255 53% 10% / 25%);
}
@media (--HDcolor) {
@supports (color: color(display-p3 0 0 0)) {
--theme: color(display-p3 .4 0 .9);
}
}
}
밝은 테마 및 어두운 테마 배경 원뿔형 배경
밝은 테마에는 생생한 cyan~deeppink 원뿔형 그라데이션이 있고 어두운 테마에는 어두운 미묘한 원뿔형 그라데이션이 있습니다. 원추형 그라데이션으로 할 수 있는 작업에 대해 자세히 알아보려면 conic.style을 참고하세요.
html {
background: conic-gradient(at -10% 50%, deeppink, cyan);
@media (--dark) {
background: conic-gradient(at -10% 50%, #212529, 50%, #495057, #212529);
}
}
3D 원근법 사용 설정
요소가 웹페이지의 3D 공간에 존재하려면 원근법이 있는 표시 영역을 초기화해야 합니다. body 요소에 원근법을 적용하고 뷰포트 단위를 사용하여 원하는 스타일을 만들었습니다.
body {
perspective: 40vw;
}
이것이 관점이 미칠 수 있는 영향의 유형입니다.
<ul> 버튼 목록 스타일 지정
이 요소는 전체 버튼 목록 매크로 레이아웃을 담당하며, 대화형 3D 플로팅 카드이기도 합니다. 다음과 같은 방법으로 이를 달성할 수 있습니다.
버튼 그룹 레이아웃
Flexbox는 컨테이너 레이아웃을 관리할 수 있습니다. flex-direction를 사용하여 플렉스의 기본 방향을 행에서 열로 변경하고 align-items의 경우 stretch에서 start로 변경하여 각 항목이 콘텐츠 크기가 되도록 합니다.
.threeD-button-set {
/* remove <ul> margins */
margin: 0;
/* vertical rag-right layout */
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 2.5vh;
}
다음으로 컨테이너를 3D 공간 컨텍스트로 설정하고 CSS clamp() 함수를 설정하여 카드가 읽을 수 있는 회전 이상으로 회전하지 않도록 합니다. clamp의 중간 값은 맞춤 속성입니다. 이러한 --x 및 --y 값은 나중에 마우스 상호작용 시 JavaScript에서 설정됩니다.
.threeD-button-set {
…
/* create 3D space context */
transform-style: preserve-3d;
/* clamped menu rotation to not be too extreme */
transform:
rotateY(
clamp(
calc(var(--_max-rotateY) * -1),
var(--y),
var(--_max-rotateY)
)
)
rotateX(
clamp(
calc(var(--_max-rotateX) * -1),
var(--x),
var(--_max-rotateX)
)
)
;
}
그런 다음 방문 사용자가 모션을 허용하는 경우 이 항목의 변환이 will-change에 따라 지속적으로 변경된다는 힌트를 브라우저에 추가합니다.
또한 변환에 transition를 설정하여 보간을 사용 설정합니다. 이 전환은 마우스가 카드와 상호작용할 때 발생하며 회전 변경으로의 부드러운 전환을 지원합니다. 이 애니메이션은 마우스가 구성요소와 상호작용할 수 없거나 상호작용하지 않는 경우에도 카드가 있는 3D 공간을 보여주는 지속적인 실행 애니메이션입니다.
@media (--motionOK) {
.threeD-button-set {
/* browser hint so it can be prepared and optimized */
will-change: transform;
/* transition transform style changes and run an infinite animation */
transition: transform .1s ease;
animation: rotate-y 5s ease-in-out infinite;
}
}
브라우저에서 0% 및 100%을 요소의 기본 스타일로 설정하므로 rotate-y 애니메이션은 50%에 중간 키프레임만 설정합니다. 이는 동일한 위치에서 시작하고 끝나야 하는 교번 애니메이션의 약어입니다. 무한히 번갈아 재생되는 애니메이션을 표현하는 데 유용합니다.
@keyframes rotate-y {
50% {
transform: rotateY(15deg) rotateX(-6deg);
}
}
<li> 요소 스타일 지정
각 목록 항목 (<li>)에는 버튼과 테두리 요소가 포함됩니다. display 스타일이 변경되어 항목에 ::marker이 표시되지 않습니다. position 스타일이 relative로 설정되어 다음 버튼 가상 요소가 버튼이 사용하는 전체 영역 내에 배치될 수 있습니다.
.threeD-button-set > li {
/* change display type from list-item */
display: inline-flex;
/* create context for button pseudos */
position: relative;
/* create 3D space context */
transform-style: preserve-3d;
}

<button> 요소 스타일 지정
버튼 스타일 지정은 어려운 작업일 수 있습니다. 고려해야 할 상태와 상호작용 유형이 많기 때문입니다. 이러한 버튼은 의사 요소, 애니메이션, 상호작용의 균형을 맞추기 때문에 빠르게 복잡해집니다.
초기 <button> 스타일
아래는 다른 상태를 지원하는 기본 스타일입니다.
.threeD-button-set button {
/* strip out default button styles */
appearance: none;
outline: none;
border: none;
/* bring in brand styles via props */
background-color: var(--_btn-bg);
color: var(--_btn-text);
text-shadow: 0 1px 1px var(--_btn-text-shadow);
/* large text rounded corner and padded*/
font-size: 5vmin;
font-family: Audiowide;
padding-block: .75ch;
padding-inline: 2ch;
border-radius: 5px 20px;
}

버튼 가상 요소
버튼의 테두리는 일반적인 테두리가 아니라 테두리가 있는 절대 위치 가상 요소입니다.

이러한 요소는 설정된 3D 관점을 보여주는 데 중요합니다. 이러한 의사 요소 중 하나는 버튼에서 멀어지고 다른 하나는 사용자에게 더 가까워집니다. 이 효과는 상단 및 하단 버튼에서 가장 두드러집니다.
.threeD-button button {
…
&::after,
&::before {
/* create empty element */
content: '';
opacity: .8;
/* cover the parent (button) */
position: absolute;
inset: 0;
/* style the element for border accents */
border: 1px solid var(--theme);
border-radius: 5px 20px;
}
/* exceptions for one of the pseudo elements */
/* this will be pushed back (3x) and have a thicker border */
&::before {
border-width: 3px;
/* in dark mode, it glows! */
@media (--dark) {
box-shadow:
0 0 25px var(--theme),
inset 0 0 25px var(--theme);
}
}
}
3D 변환 스타일
transform-style 아래는 preserve-3d로 설정되어 있으므로 하위 요소가 z 축에서 자체적으로 간격을 둘 수 있습니다. transform은 --distance 맞춤 속성으로 설정되며, 이 속성은 마우스를 가져가거나 포커스를 두면 증가합니다.
.threeD-button-set button {
…
transform: translateZ(var(--distance));
transform-style: preserve-3d;
&::after {
/* pull forward in Z space with a 3x multiplier */
transform: translateZ(calc(var(--distance) / 3));
}
&::before {
/* push back in Z space with a 3x multiplier */
transform: translateZ(calc(var(--distance) / 3 * -1));
}
}
조건부 애니메이션 스타일
사용자가 동작을 허용하면 버튼은 변환 속성을 변경할 준비가 되어 있어야 하며 transform 및 background-color 속성에 전환이 설정되어야 함을 브라우저에 힌트로 알려줍니다. 지속 시간의 차이를 확인하세요. 미묘한 엇갈림 효과를 줍니다.
.threeD-button-set button {
…
@media (--motionOK) {
will-change: transform;
transition:
transform .2s ease,
background-color .5s ease
;
&::before,
&::after {
transition: transform .1s ease-out;
}
&::after { transition-duration: .5s }
&::before { transition-duration: .3s }
}
}
마우스 오버 및 포커스 상호작용 스타일
상호작용 애니메이션의 목표는 평면으로 표시되는 버튼을 구성하는 레이어를 펼치는 것입니다. --distance 변수를 처음에는 1px로 설정하여 이를 달성합니다. 다음 코드 예에 표시된 선택기는 포커스 표시기를 표시해야 하는 기기에서 버튼에 마우스를 가져가거나 포커스를 맞추고 있으며 활성화되지 않았는지 확인합니다. 이 경우 CSS를 적용하여 다음을 실행합니다.
- 마우스 오버 배경색을 적용합니다.
- 거리를 늘립니다 .
- 바운스 이즈 효과를 추가합니다.
- 가상 요소 전환을 지연시킵니다.
.threeD-button-set button {
…
&:is(:hover, :focus-visible):not(:active) {
/* subtle distance plus bg color change on hover/focus */
--distance: 15px;
background-color: var(--_btn-bg-hover);
/* if motion is OK, setup transitions and increase distance */
@media (--motionOK) {
--distance: 3vmax;
transition-timing-function: var(--_bounce-ease);
transition-duration: .4s;
&::after { transition-duration: .5s }
&::before { transition-duration: .3s }
}
}
}
3D 관점은 reduced 동작 환경설정에서 여전히 매우 유용했습니다.
상단 및 하단 요소는 미묘한 방식으로 효과를 보여줍니다.
JavaScript를 사용한 작은 개선사항
인터페이스는 이미 키보드, 스크린 리더, 게임패드, 터치, 마우스에서 사용할 수 있지만 몇 가지 시나리오를 쉽게 처리할 수 있도록 JavaScript를 약간 추가할 수 있습니다.
화살표 키 지원
탭 키는 메뉴를 탐색하는 데 적합하지만 방향 패드나 조이스틱이 게임패드에서 포커스를 이동할 것으로 예상됩니다. GUI 챌린지 인터페이스에 자주 사용되는 roving-ux 라이브러리가 화살표 키를 처리합니다. 아래 코드는 라이브러리에 .threeD-button-set 내에서 포커스를 트래핑하고 포커스를 버튼 하위 요소로 전달하도록 지시합니다.
import {rovingIndex} from 'roving-ux'
rovingIndex({
element: document.querySelector('.threeD-button-set'),
target: 'button',
})
마우스 시차 상호작용
마우스를 추적하고 메뉴를 기울이는 것은 마우스 대신 가상 포인터가 있을 수 있는 AR 및 VR 동영상 게임 인터페이스를 모방하기 위한 것입니다. 요소가 포인터를 매우 잘 인식하면 재미있을 수 있습니다.
이 기능은 작은 추가 기능이므로 사용자의 동작 환경설정 쿼리 뒤에 상호작용을 배치합니다. 또한 설정의 일부로 버튼 목록 구성요소를 querySelector를 사용하여 메모리에 저장하고 요소의 경계를 menuRect에 캐시합니다. 이 경계를 사용하여 마우스 위치에 따라 카드에 적용되는 회전 오프셋을 결정합니다.
const menu = document.querySelector('.threeD-button-set')
const menuRect = menu.getBoundingClientRect()
const { matches:motionOK } = window.matchMedia(
'(prefers-reduced-motion: no-preference)'
)
다음으로 마우스 x 및 y 위치를 허용하고 카드를 회전하는 데 사용할 수 있는 값을 반환하는 함수가 필요합니다. 다음 함수는 마우스 위치를 사용하여 마우스가 상자 안의 어느 쪽에 있는지, 얼마나 떨어져 있는지를 확인합니다. 델타는 함수에서 반환됩니다.
const getAngles = (clientX, clientY) => {
const { x, y, width, height } = menuRect
const dx = clientX - (x + 0.5 * width)
const dy = clientY - (y + 0.5 * height)
return {dx,dy}
}
마지막으로 마우스 움직임을 확인하고, 위치를 getAngles() 함수에 전달하고, 델타 값을 맞춤 속성 스타일로 사용합니다. 델타를 패딩하고 덜 불안정하게 만들기 위해 20으로 나눴습니다. 더 나은 방법이 있을 수도 있습니다. 처음부터 기억한다면 --x 및 --y 속성을 clamp() 함수 중간에 배치했습니다. 이렇게 하면 마우스 위치로 인해 카드가 읽을 수 없는 위치로 과도하게 회전하는 것을 방지할 수 있습니다.
if (motionOK) {
window.addEventListener('mousemove', ({target, clientX, clientY}) => {
const {dx,dy} = getAngles(clientX, clientY)
menu.attributeStyleMap.set('--x', `${dy / 20}deg`)
menu.attributeStyleMap.set('--y', `${dx / 20}deg`)
})
}
번역 및 안내
다른 쓰기 모드와 언어로 게임 메뉴를 테스트할 때 한 가지 주의사항이 있었습니다.
<button> 요소에는 사용자 에이전트 스타일시트의 writing-mode에 대한 !important 스타일이 있습니다. 따라서 원하는 디자인을 수용하도록 게임 메뉴 HTML을 변경해야 했습니다. 버튼 목록을 링크 목록으로 변경하면 <a> 요소에는 브라우저에서 제공하는 !important 스타일이 없으므로 논리적 속성을 사용하여 메뉴 방향을 변경할 수 있습니다.
결론
이제 제가 어떻게 했는지 아셨으니 어떻게 하시겠어요? 🙂 휴대전화를 기울이면 메뉴가 회전하도록 메뉴에 가속도계 상호작용을 추가해 주실 수 있나요? 동작이 없는 환경을 개선할 수 있나요?
다양한 접근 방식을 사용하고 웹에서 빌드하는 모든 방법을 알아보세요. 데모를 만들고 트윗으로 링크를 보내주세요. 아래 커뮤니티 리믹스 섹션에 추가해 드리겠습니다.
커뮤니티 리믹스
아직 표시할 내용이 없습니다.