Duyarlı, uyarlanabilir ve erişilebilir bir 3D oyun menüsünün nasıl oluşturulacağına ilişkin temel bir genel bakış.
Bu gönderide, 3D oyun menüsü bileşeni oluşturma konusunda düşüncelerimi paylaşmak istiyorum. Demoyu deneyin.
Video kullanmayı tercih ederseniz bu gönderinin YouTube versiyonunu kullanabilirsiniz:
Genel bakış
Video oyunları genellikle kullanıcılara animasyonlu ve 3B uzayda yaratıcı ve alışılmadık bir menü sunar. Yeni artırılmış gerçeklik (AR)/sanal gerçeklik (VR) oyunlarında menünün uzayda süzülüyormuş gibi görünmesini sağlamak için çok popüler. Bugün bu efektin temel özelliklerini yeniden oluşturacağız, ancak uyarlanabilen renk şeması ve daha az hareketi tercih eden kullanıcılar için konaklama olanağı ekleyeceğiz.
HTML
Oyun menüsü, düğmelerden oluşan bir listedir. Bunu HTML'de göstermenin en iyi yolu aşağıdaki gibidir:
<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üğmelerden oluşan bir liste kendisini ekran okuyucu teknolojilerine iyice tanıtır ve JavaScript veya CSS olmadan çalışır.
CSS
Düğme listesinin stilini belirlemek 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 alana yerleştirme.
Özel özelliklere genel bakış
Özel özellikler, aksi halde rastgele görünen değerlere anlamlı adlar vererek, yinelenen kodlardan ve alt öğeler arasında değer paylaşmaktan kaçınarak değerleri netleştirmeye yardımcı olur.
Aşağıda, custom media olarak da bilinen, CSS değişkenleri olarak kaydedilen medya sorguları verilmiştir. Bunlar geneldir ve kodun kısa ve okunaklı olması için çeşitli seçicilerde kullanılır. Oyun menüsü bileşeni, ekranın hareket tercihleri, sistem renk şeması 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, oyun menüsünü etkileşimli hale getirmek için renk şemasını yönetir ve fare konum değerlerini tutar. Özel özellikleri adlandırmak, değerin kullanım alanını veya değerin sonucuna ilişkin kolay bir adı ortaya koyduğu için kod okunaklılığına yardımcı olur.
.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 temalı arka plan konik arka planlar
Açık temada canlı cyan
-deeppink
konik gradya, koyu temada ise koyu ve ince bir konik gradyan vardır. Konik gradyanlarla neler yapılabileceği hakkında daha fazla bilgi için conic.style öğesine bakın.
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 web sayfasının 3D alanında yer alması için perspektif içeren bir görüntü alanının başlatılması gerekir. Perspektifi body
öğesine koymayı seçtim
ve sevdiğim stili oluşturmak için görüntü alanı birimleri kullandım.
body {
perspective: 40vw;
}
Bu, bakış açısının sunabileceği etki türünü ifade eder.
<ul>
düğme listesinin stilini belirleme
Bu öğe, genel düğme listesi makro düzeninin yanı sıra etkileşimli ve 3D kayan bir kart olmasından sorumludur. Bunu aşağıdaki şekilde yapabilirsiniz.
Düğme grubu düzeni
Flexbox, kapsayıcı düzenini yönetebilir. flex-direction
kullanarak esnek özelliğin varsayılan yönünü satırlardan sütunlara çevirin ve align-items
için stretch
olan değeri start
olarak değiştirerek her bir öğenin içerik boyutunda olduğundan emin olun.
.threeD-button-set {
/* remove <ul> margins */
margin: 0;
/* vertical rag-right layout */
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 2.5vh;
}
Daha sonra, kapsayıcıyı 3D alan bağlamı olarak oluşturun ve kartın okunabilir döndürmeler dışında dönmediğinden emin olmak için CSS clamp()
işlevleri ayarlayın. Sabitleme için orta değerin özel bir özellik olduğuna dikkat edin. Bu --x
ve --y
değerleri daha sonra fare etkileşimi olduğunda 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ı harekette bir sorun değilse tarayıcıya will-change
ile bu öğenin dönüşümünün sürekli değişeceğine dair bir ipucu ekleyin.
Ayrıca, dönüşümlerde transition
ayarlayarak interpolasyonu etkinleştirin. Bu geçiş, fare kartla etkileşime girdiğinde gerçekleşerek rotasyon değişikliklerine sorunsuz geçiş sağlar. Animasyon, farenin bileşenle etkileşimde bulunamadığı veya etkileşiminin olmadığı durumlarda bile kartın içinde bulunduğu 3D alanı gösteren, sürekli çalışan bir animasyondur.
@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ı varsayılan olarak 0%
ve 100%
değerlerini öğenin varsayılan stiline ayarlayacağı için rotate-y
animasyonu yalnızca 50%
konumunda orta animasyon karesini ayarlar. Bu, birbiriyle değişen ve aynı konumda başlayıp bitmesi gereken animasyonların kısaltılmış halidir. Bu, sonsuz sayıda alternatif animasyonun ifade edilmesi için harika bir yoldur.
@keyframes rotate-y {
50% {
transform: rotateY(15deg) rotateX(-6deg);
}
}
<li>
öğelerinin stil özelliklerini ayarlama
Her liste öğesi (<li>
) düğmeyi ve kenarlık öğelerini içerir. display
stili değiştirildiğinde öğede ::marker
gösterilmez. position
stili, relative
olarak ayarlandı. Böylece, yaklaşan düğme sözde öğeleri, düğmenin tükettiği tüm alan içinde kendilerini konumlandırabilir.
.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>
öğelerinin stil özelliklerini ayarlama
Düğmelerin stilini belirlemek zor olabilir. Hesaba katılacak birçok durum ve etkileşim türü vardır. Bu düğmeler, sözde öğelerin, animasyonların ve etkileşimlerin dengelenmesi nedeniyle hızlı bir şekilde karmaşık hale gelir.
İlk <button>
stilleri
Diğer eyaletleri destekleyecek temel stiller aşağıda verilmiştir.
.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, mutlak konumda kenarlıklı sözde öğelerdir.
Bu öğeler, oluşturulan 3D perspektifi sergilemede büyük önem taşır. Bu sözde öğelerden biri düğmeden uzağa itilir ve biri kullanıcıya yaklaştırılır. Bu efekt en çok üstteki ve alttaki düğmelerde görünür.
.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
Çocukların z
ekseninde boşluk bırakabilmeleri için transform-style
altındaki değerin preserve-3d
değerine ayarlanması gerekir. transform
, --distance
özel mülkü olarak ayarlandı. Bu mülk, fareyle üzerine gelindiğinde ve odaklanıldığında artırılacak.
.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ı hareket edebilirse düğme, tarayıcıya dönüşüm özelliğinin değişim için hazır olması gerektiğini ve transform
ile background-color
özellikleri için bir geçişin ayarlandığını belirtir. Süredeki farka dikkat edin. Farkı, hoş, hafif, kademeli bir etki yarattığını hissettim.
.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ünen düğmeyi oluşturan katmanları yaymaktır. --distance
değişkenini başlangıçta 1px
değerine ayarlayarak bunu başarabilirsiniz. Aşağıdaki kod örneğinde gösterilen seçici, düğmenin üzerine gelip gelmediğini veya odak göstergesi görmesi gereken bir cihazın bu düğmeye odaklanıp odaklanmadığını ve etkinleştirilmediğini kontrol eder. Bu durumda, aşağıdakileri yapmak için CSS'yi uygular:
- Fareyle üzerine gelme arka plan rengini uygulayın.
- Mesafeyi artırın .
- Zıplama yumuşatma efekti ekleyin.
- Sözde öğe geçişlerini bölümlere ayırın.
.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 yine de çok düzgündu.
Üst ve alt öğeler efekti hoş ve ince bir şekilde gösteriyor.
JavaScript'teki küçük geliştirmeler
Arayüz klavye, ekran okuyucu, oyun kumandası, dokunmatik ekran ve fare ile kullanılabilir. Ancak birkaç senaryoyu kolaylaştırmak için JavaScript'e küçük dokunuşlar ekleyebiliriz.
Destekleyici ok tuşları
Sekme tuşu menüde gezinmenin iyi bir yoludur ancak yön tuşlarının
veya kontrol çubuklarının odağı bir oyun kumandasına taşımasını beklerim. Genellikle GUI Yarışma arayüzleri için kullanılan roving-ux kitaplığı, ok tuşlarını bizim için yönetir. Aşağıdaki kod, kitaplığa odağı .threeD-button-set
içinde yakalaması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
Fareyi takip etme ve menüyü yatırmayı sağlama amacı, AR ve VR video oyunu arayüzlerini taklit etmektir. Bu arayüzde, fare yerine sanal bir işaretçi bulunabilir. Öğeler işaretçinin çok farkında olduğunda eğlenceli olabilir.
Bu küçük, ekstra bir özellik olduğundan etkileşimi kullanıcının hareket tercihi sorgusunun arkasına koyacağız. Ayrıca kurulumun bir parçası olarak düğme listesi bileşenini querySelector
ile belleğe kaydedin ve öğenin sınırlarını menuRect
üzerinde önbelleğe alın. Fare konumuna göre karta uygulanan döndürme ofsetini 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, farenin x
ve y
konumlarını kabul eden ve kartı döndürmek için kullanabileceğimiz bir değer döndüren bir işleve ihtiyacımız var. Aşağıdaki işlev, kutunun içinde hangi tarafın olduğunu ve ne kadarının olduğunu belirlemek için fare konumunu kullanır. Delta, işlevden 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, fare hareketini izleyin, konumu getAngles()
işlevimize iletin ve delta değerlerini özel mülk stilleri olarak kullanın. 20'ye bölerek deltayı doldurmayı ve titremeyi azaltıyorum. Bunu yapmanın daha iyi bir yolu olabilir. Baştan hatırlarsanız --x
ve --y
öğelerini bir clamp()
işlevinin ortasına koyarız. Böylece fare pozisyonunun kartı okunaklı olmayan bir konuma aşırı döndürmesi önlenmiş olur.
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 talimatlar
Oyun menüsünü diğer yazma modlarında ve dillerde test ederken bir sorun oldu.
<button>
öğeleri, kullanıcı aracısı stil sayfasında writing-mode
için !important
stiline sahip. Bu da oyun menüsü HTML'sinin istenen tasarıma
uymak için değişmesi gerektiği anlamına geliyordu. <a>
öğelerinin tarayıcı tarafından sağlanan bir !important
stili olmadığından, düğme listesini bir bağlantı listesiyle değiştirmek mantıksal özelliklerin menü yönünü değiştirebilmesini sağlar.
Sonuç
Artık bunu nasıl yaptığımı biliyorsunuz. Hareketsiz deneyimi iyileştirebilir miyiz?
Gelin, yaklaşımlarımızı çeşitlendirelim ve web'de içerik geliştirmenin tüm yollarını öğrenelim. Demo oluşturup beni tweet'le bağlantıları oluşturduğumda bunu aşağıdaki topluluk remiksleri bölümüne ekleyeceğim.
Topluluk remiksleri
Henüz burada görülecek bir şey yok.