Duyarlı, uyarlanabilir ve erişilebilir bir 3D oyun menüsünün nasıl oluşturulacağına dair temel bir genel bakış.
Bu yayında, 3D oyun menüsü bileşeni oluşturma yöntemleriyle ilgili düşüncelerimi paylaşmak istiyorum. Demoyu deneyin.
Video tercih ediyorsanız bu yayının YouTube versiyonunu aşağıda bulabilirsiniz:
Genel Bakış
Video oyunları genellikle kullanıcılara yaratıcı ve sıra dışı bir menü sunar. Bu menü, animasyonlu ve 3D uzaydadır. Menünün uzayda süzülüyormuş gibi görünmesini sağlamak için yeni AR/VR oyunlarında sıklıkla kullanılır. Bugün bu efektin temel özelliklerini yeniden oluşturacağız ancak uyarlanabilir bir renk şeması ve daha az hareket tercih eden kullanıcılar için düzenlemeler ekleyeceğiz.
HTML
Oyun menüsü, düğmelerin listesidir. Bunu HTML'de temsil etmenin en iyi yolu şöyledir:
<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>
Düğme listesi, ekran okuyucu teknolojilerine kendini iyi bir şekilde duyurur ve JavaScript veya CSS olmadan çalışır.

CSS
Düğme listesini stilize etme işlemi aşağıdaki üst düzey adımlara ayrılır:
- Özel özellikleri ayarlama.
- Flexbox düzeni.
- Dekoratif sözde öğeler içeren özel bir düğme.
- Öğeleri 3D uzaya yerleştirme
Özel özelliklere genel bakış
Özel özellikler, aksi takdirde rastgele görünecek değerlere anlamlı adlar vererek değerlerin netleştirilmesine yardımcı olur, tekrarlanan kodları önler ve değerleri alt öğeler arasında paylaşır.
Aşağıda, CSS değişkenleri olarak kaydedilmiş medya sorguları (özel medya olarak da bilinir) yer almaktadır. Bunlar geneldir ve kodu kısa ve okunabilir tutmak için çeşitli seçicilerde kullanılır. Oyun menüsü bileşeni, ekranın hareket tercihlerini, sistem renk şemasını ve renk aralığı özelliklerini kullanır.
@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --HDcolor (dynamic-range: high);
Aşağıdaki özel özellikler, renk şemasını yönetir ve fareyle üzerine gelindiğinde oyun menüsünü etkileşimli hale getirmek için fare konum değerlerini tutar. Özel özelliklerin adlandırılması, değerin kullanım alanını veya değerin sonucunun kolay anlaşılır adını ortaya çıkardığı için kodun okunabilirliğini artırır.
.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);
}
}
}
Açık ve koyu tema arka planları
Açık temada cyan ile deeppink arasında canlı bir konik gradyan, koyu temada ise koyu ve ince bir konik gradyan bulunur. Konik gradyanlarla neler yapılabileceği hakkında daha fazla bilgi için conic.style adresini ziyaret edin.
html {
background: conic-gradient(at -10% 50%, deeppink, cyan);
@media (--dark) {
background: conic-gradient(at -10% 50%, #212529, 50%, #495057, #212529);
}
}
3D perspektifi etkinleştirme
Öğelerin bir web sayfasının 3D alanında bulunabilmesi için perspektif içeren bir görüntü alanının başlatılması gerekir. Bakış açısını body öğesine yerleştirmeyi tercih ettim ve beğendiğim stili oluşturmak için görüntü alanı birimlerini kullandım.
body {
perspective: 40vw;
}
Bu, perspektifin sahip olabileceği etki türüdür.
<ul> düğme listesini stilize etme
Bu öğe, genel düğme listesi makro düzeninden ve etkileşimli bir 3D kayan kart olmaktan sorumludur. Bunu yapmanın bir yolu aşağıda açıklanmıştır.
Düğme grubu düzeni
Flexbox, kapsayıcı düzenini yönetebilir. flex-direction ile esnekliğin varsayılan yönünü satırlardan sütunlara değiştirin ve align-items için stretch değerini start olarak değiştirerek her öğenin içeriğinin boyutunda olmasını sağlayın.
.threeD-button-set {
/* remove <ul> margins */
margin: 0;
/* vertical rag-right layout */
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 2.5vh;
}
Ardından, kapsayıcıyı 3D alan bağlamı olarak belirleyin ve kartın okunabilir dönüşümlerin ötesinde dönmemesini sağlamak için CSS clamp() işlevlerini ayarlayın. Sınırlama için ortadaki değerin özel bir özellik olduğunu unutmayın. Bu --x ve --y değerleri, fareyle etkileşimde bulunulduktan sonra JavaScript'ten ayarlanır.
.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)
)
)
;
}
Ardından, ziyaret eden kullanıcı için hareket uygunsa tarayıcıya, bu öğenin dönüşümünün will-change ile sürekli değişeceği ipucunu ekleyin.
Ayrıca, dönüşümlerde transition ayarlayarak enterpolasyonu etkinleştirin. Bu geçiş, fare kartla etkileşime girdiğinde gerçekleşir ve dönüşüm değişikliklerine sorunsuz geçişler sağlar. Animasyon, kartın içinde bulunduğu 3D alanı gösteren sürekli çalışan bir animasyondur. Fare bileşenle etkileşimde bulunamasa veya bulunmasa bile bu animasyon gösterilir.
@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;
}
}
Tarayıcı 0% ve 100% öğelerini varsayılan stil olarak ayarlayacağından rotate-y animasyonu yalnızca orta animasyon karesini 50% olarak ayarlar. Bu, aynı konumda başlayıp bitmesi gereken, dönüşümlü animasyonlar için kullanılan bir kısaltmadır. Bu, sonsuz alternatif animasyonları ifade etmenin harika bir yoludur.
@keyframes rotate-y {
50% {
transform: rotateY(15deg) rotateX(-6deg);
}
}
<li> öğelerini biçimlendirme
Her liste öğesi (<li>), düğmeyi ve kenarlık öğelerini içerir. display stili, öğede ::marker gösterilmeyecek şekilde değiştirilir. position stili relative olarak ayarlandığından yaklaşan düğme sözde öğeleri, düğmenin kullandığı alanın tamamında konumlandırılabilir.
.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> öğelerini biçimlendirme
Düğmeleri stilize etmek zor olabilir. Dikkate alınması gereken çok sayıda durum ve etkileşim türü vardır. Bu düğmeler, sözde öğeler, animasyonlar ve etkileşimler dengelendiği için kısa sürede karmaşık hale gelir.
İlk <button> stilleri
Aşağıda, diğer durumları destekleyecek temel stiller yer almaktadır.
.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;
}

Düğme sözde öğeleri
Düğmenin kenarlıkları geleneksel kenarlıklar değildir. Kenarlıklı mutlak konumlu sözde öğelerdir.

Bu öğeler, oluşturulan 3D perspektifini göstermek için çok önemlidir. Bu sözde öğelerden biri düğmeden uzaklaştırılır, diğeri ise kullanıcıya yaklaştırılır. Etki en çok üst ve alt düğmelerde fark edilir.
.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 dönüştürme stilleri
transform-style, preserve-3d olarak ayarlanır. Böylece çocuklar z ekseninde aralıklı olarak yer alabilir. transform, fareyle üzerine gelindiğinde ve odaklanıldığında artırılacak olan --distance özel özelliğine ayarlanır.
.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));
}
}
Koşullu animasyon stilleri
Kullanıcı hareketle ilgili bir sorun yaşamıyorsa düğme, tarayıcıya transform özelliğinin değişime hazır olması gerektiğini ve transform ile background-color özellikleri için bir geçiş ayarlanması gerektiğini belirtir. Süre farkını fark ettiniz mi? Bu farkın, güzel ve ince bir kademeli efekt oluşturduğunu düşünüyorum.
.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 }
}
}
Fareyle üzerine gelme ve odaklanma etkileşim stilleri
Etkileşim animasyonunun amacı, düz görünümdeki düğmeyi oluşturan katmanları yaymaktır. Bunu, --distance değişkenini başlangıçta 1px olarak ayarlayarak yapabilirsiniz. Aşağıdaki kod örneğinde gösterilen seçici, düğmenin odak göstergesi görmesi gereken bir cihaz tarafından fareyle üzerine gelinip gelinmediğini veya odaklanılıp odaklanılmadığını ve etkinleştirilip etkinleştirilmediğini kontrol eder. Bu durumda, aşağıdaki işlemleri yapmak için CSS uygulanır:
- Fareyle üzerine gelindiğinde arka plan rengini uygular.
- Mesafeyi artırın .
- Sekme kolaylığı efekti ekleyin.
- Sözde öğe geçişlerini kademelendirin.
.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 perspektif, reduced hareket tercihi için hâlâ çok iyi.
Üst ve alt öğeler, efekti güzel ve zarif bir şekilde gösterir.
JavaScript ile küçük geliştirmeler
Arayüz; klavyeler, ekran okuyucular, gamepad'ler, dokunmatik ekranlar ve farelerle kullanılabilir. Ancak birkaç senaryoyu kolaylaştırmak için JavaScript'in bazı özelliklerini ekleyebiliriz.
Ok tuşlarını destekleme
Menüde gezinmek için sekme tuşunu kullanabilirsiniz ancak yön tuşları veya kontrol çubuklarıyla da odağı gamepad'e taşıyabilmeniz gerekir. GUI Challenge arayüzleri için sıklıkla kullanılan roving-ux kitaplığı, ok tuşlarını bizim için işler. Aşağıdaki kod, kitaplığa odağı .threeD-button-set içinde tutmasını ve odağı düğme alt öğelerine yönlendirmesini söyler.
import {rovingIndex} from 'roving-ux'
rovingIndex({
element: document.querySelector('.threeD-button-set'),
target: 'button',
})
Fare paralaks etkileşimi
Farenin izlenmesi ve menünün eğilmesi, AR ve VR video oyunu arayüzlerini taklit etmek için tasarlanmıştır. Bu arayüzlerde fare yerine sanal bir işaretçi olabilir. Öğeler işaretçinin farkında olduğunda eğlenceli olabilir.
Bu küçük bir ek özellik olduğundan etkileşimi, kullanıcının hareket tercihiyle ilgili bir sorgunun arkasına yerleştireceğiz. Ayrıca, kurulumun bir parçası olarak düğme listesi bileşenini querySelector ile bellekte saklayın ve öğenin sınırlarını menuRect ile önbelleğe alın. Fare konumuna göre karta uygulanan döndürme uzaklığını belirlemek için bu sınırları kullanın.
const menu = document.querySelector('.threeD-button-set')
const menuRect = menu.getBoundingClientRect()
const { matches:motionOK } = window.matchMedia(
'(prefers-reduced-motion: no-preference)'
)
Ardından, fare x ve y konumlarını kabul eden ve kartı döndürmek için kullanabileceğimiz bir değer döndüren bir işlev oluşturmamız gerekir. Aşağıdaki işlev, kutunun hangi tarafında ve ne kadar olduğunu belirlemek için fare konumunu kullanır. İşlevden delta döndürülür.
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}
}
Son olarak, farenin hareketini izleyin, konumu getAngles() işlevimize iletin ve delta değerlerini özel özellik stilleri olarak kullanın. Delta değerini doldurmak ve daha az titrek hale getirmek için 20'ye böldüm. Bunu yapmanın daha iyi bir yolu olabilir. Başlangıçta hatırladığınız gibi, --x ve --y özelliklerini clamp() işlevinin ortasına yerleştiriyoruz. Bu, fare konumunun kartı aşırı döndürerek okunaksız bir konuma getirmesini önler.
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`)
})
}
Çeviriler ve yol tarifleri
Oyun menüsünü diğer yazma modlarında ve dillerde test ederken bir sorunla karşılaştık.
<button> öğeleri, kullanıcı aracısı stil sayfasında writing-mode için !important stiline sahip. Bu nedenle, istenen tasarıma uyum sağlamak için oyun menüsü HTML'sinin değiştirilmesi gerekiyordu. Düğme listesini bağlantı listesine dönüştürmek, mantıksal özelliklerin menü yönünü değiştirmesini sağlar. Bunun nedeni, <a> öğelerinin tarayıcı tarafından sağlanan !important stiline sahip olmamasıdır.
Sonuç
Nasıl yaptığımı öğrendiğinize göre siz nasıl yapardınız? 🙂 Telefonunuzu eğdiğinizde menünün dönmesi için menüye ivmeölçer etkileşimi ekleyebilir misiniz? Hareketsizlik deneyimini iyileştirebilir miyiz?
Yaklaşımlarımızı çeşitlendirelim ve web'de içerik oluşturmanın tüm yollarını öğrenelim. Bir demo oluşturun, bağlantıları bana tweet atın. Ben de bu bağlantıları aşağıdaki topluluk remiksleri bölümüne ekleyeyim.
Topluluk remiksleri
Henüz burada gösterilecek bir şey yok.