반응형, 적응형, 접근성 있는 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
값은 나중에 마우스 상호작용 시 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 }
}
}
}
reduced
모션 환경설정의 경우 3D 관점이 여전히 매우 깔끔했습니다.
상단 및 하단 요소는 효과를 섬세하게 보여줍니다.
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
스타일이 없으므로 논리적 속성에서 메뉴 방향을 변경할 수 있습니다.
결론
이제 제가 어떻게 했는지 알았으니, 괜찮을 건가요?‽ 입찰 휴대전화에 가속도계 상호작용을 추가하여 휴대전화에서 메뉴를 회전할 수 있나요? 모션이 없는 환경을 개선할 수 있을까요?
접근방식을 다각화하고 웹에서 빌드하는 방법을 모두 알아보겠습니다. 데모를 만들어 트윗해 주시면 아래의 커뮤니티 리믹스 섹션에 추가해 드리겠습니다.
커뮤니티 리믹스
아직 표시할 내용이 없습니다.