반응형, 적응형, 접근성을 갖춘 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
버튼 목록의 스타일 지정은 다음과 같은 대략적인 단계로 나뉩니다.
- 맞춤 속성을 설정하는 중입니다.
- Flexbox 레이아웃
- 장식용 유사 요소가 있는 맞춤 버튼
- 요소를 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
를 사용하여 flex의 기본 방향을 행에서 열로 변경하고 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()
함수를 설정합니다. 클램프의 중간 값은 맞춤 속성이며, 이러한 --x
및 --y
값은 나중에 마우스 상호작용 시 자바스크립트에서 설정됩니다.
.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>
)에는 버튼과 테두리 요소가 포함되어 있습니다. 항목에 ::marker
가 표시되지 않도록 display
스타일이 변경되었습니다. 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를 약간 추가하여 몇 가지 시나리오를 완화할 수 있습니다.
지원 화살표 키
Tab 키는 메뉴를 탐색하는 좋은 방법이지만 방향 패드 또는 조이스틱을 사용하여 게임패드에서 포커스를 이동할 수 있습니다. 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
스타일이 없기 때문입니다.
결론
이제 제가 어떻게 했는지 알았으니 어떻게 해야 할까요?‽ 🙂 메뉴에 가속도계 상호작용을 추가하여 휴대전화를 타일식으로 배치하면 메뉴가 회전할 수 있나요? 모션이 없는 환경을 개선할 수 있을까요?
접근 방식을 다양화하고 웹에서 빌드하는 모든 방법을 알아보겠습니다. 데모를 만들고 링크를 트윗해 주세요. 그러면 아래의 커뮤니티 리믹스 섹션에 추가하겠습니다.
커뮤니티 리믹스
표시할 항목이 없습니다.