Duyarlı, uyarlanabilir ve erişilebilir bir 3D oyun menüsünün nasıl oluşturulacağına dair temel bilgiler.
Bu yayında, 3D oyun menüsü bileşeni oluşturmanın bir yolu üzerine düşünmek istiyorum. Demoyu deneyin.
Videoyu tercih ediyorsanız bu yayının YouTube sürümünü burada bulabilirsiniz:
Genel bakış
Video oyunları genellikle kullanıcılara animasyonlu ve 3D uzayda yaratıcı ve alışılmadık bir menü sunar. Yeni AR/VR oyunlarında menünün uzayda kayıyormuş gibi görünmesi için popülerdir. Bugün bu efektin temel unsurlarını yeniden oluşturacağız, ancak bu efektlere uyum sağlayan renk şeması ve daha az hareket etmeyi tercih eden kullanıcılar için konaklama olanağı daha eklenecek.
HTML
Oyun menüsü düğmelerden oluşan bir listedir. Bunu HTML'de göstermenin en iyi yolu şu şekildedir:
<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>
Bir düğme listesi, kendisini ekran okuyucu teknolojilerinde iyi bir şekilde duyurur ve JavaScript veya CSS olmadan çalışır.
CSS
Düğme listesinin stil özelliklerini ayarlama, aşağıdaki üst düzey adımlara bölünür:
- Özel mülk oluşturma.
- 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, tekrarlanan kodlardan ve değerler arasında değer paylaşmaktan kaçınarak değerlerin anlaşılmasına yardımcı olur.
Aşağıda, özel medya olarak da bilinen CSS değişkenleri olarak kaydedilmiş medya sorguları bulunmaktadır. Bunlar geneldir ve kodun kısa ve okunabilir olması 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 mülkler renk şemasını yönetir ve oyun menüsünü etkileşimli hale getirmek için fare konum değerlerini tutar. Özel özellikleri adlandırmak, değerin kullanım alanını veya değerin sonucu için bir kolay adı ortaya koyduğu için kodun okunabilirliğine 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 tema arka plan konik arka planlar
Açık temada canlı bir cyan
ila deeppink
konik gradyan bulunurken koyu temada koyu ve hafif bir konik gradyan vardır. Konik gradyanlarla neler yapılabileceğini görmek için conic.style konusuna 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 bir web sayfasının 3D alanında var olabilmesi için perspektifli bir görüntü alanının başlatılması gerekir. Perspektifi body
öğesine koymayı seçtim
ve beğendiğim stili oluşturmak için görüntü alanı birimlerini kullandım.
body {
perspective: 40vw;
}
Bu, bakış açısının sahip olabileceği etki türüdür.
<ul>
düğme listesinin stil özelliklerini ayarlama
Bu öğe, etkileşimli ve 3D kayan kart olmanın yanı sıra genel düğme listesi makro düzeninden de sorumludur. Bunu başarmanın bir yolunu burada bulabilirsiniz.
Düğme grubu düzeni
Flexbox, container düzenini yönetebilir. Varsayılan esneklik yönünü satırlardan flex-direction
içeren sütunlara değiştirin ve align-items
için stretch
değerini start
olarak değiştirerek her bir öğenin, içeriğinin 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 içeriği olarak sağlayın ve kartın okunabilir dönüşlerden fazla dönmemesini sağlamak için CSS clamp()
işlevlerini ayarlayın. Zaman sınırlaması 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)
)
)
;
}
Daha sonra, ziyaret eden kullanıcının hareket etmesi sorun yaratmazsa tarayıcıya bu öğenin dönüşümünün will-change
ile sürekli değişeceğine dair bir ipucu ekleyin.
Ayrıca, dönüşümlerde bir transition
ayarlayarak interpolasyonu etkinleştirin. Bu geçiş, fare ile kart etkileşime geçtiğinde meydana gelir ve dönüş değişikliklerine sorunsuz geçiş sağlar. Animasyon, bir fare bileşenle etkileşimde bulunamasa veya etkileşimde bulunmasa 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ı, 0%
ve 100%
öğelerini öğenin varsayılan stiline varsayılan olarak ayarlayacağından rotate-y
animasyonu, yalnızca ortadaki animasyon karesini 50%
olarak ayarlar. Bu, aynı konumda başlayıp bitmesi gereken, değişen animasyonların kısaltmasıdır. Sonsuz alternatif animasyonları ifade etmenin harika bir yoludur.
@keyframes rotate-y {
50% {
transform: rotateY(15deg) rotateX(-6deg);
}
}
<li>
öğelerinin stilini belirleme
Her liste öğesi (<li>
) düğmeyi ve onun kenarlık öğelerini içerir. display
stili değiştirildiğinde öğe ::marker
görünmeyecek. position
stili, relative
olarak ayarlanır. Böylece, yakında sunulacak düğme sözde öğeleri kendilerini düğmenin kapladığı tam alan içinde 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 stilini belirleme
Düğmelerin stilini belirlemek zor bir iş olabilir, hesaba katılması gereken pek çok durum ve etkileşim türü vardır. Bu düğmeler, yapay öğelerin, animasyonların ve etkileşimlerin dengelenmesi nedeniyle hızla karmaşık hale gelir.
İlk <button>
stilleri
Diğer eyaletleri destekleyecek temel stilleri aşağıda bulabilirsiniz.
.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ğil, kenarlıkları olan mutlak konumdur.
Bu öğeler, oluşturulan 3D perspektifi göstermede hayati önem taşır. Bu sözde öğelerden biri düğmeden uzağa itilir ve bir tanesi kullanıcıya yaklaştırılır. Efektin en iyi farkı, üst ve alt düğmelerde göze çarpmaktadı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
transform-style
öğesinin altı preserve-3d
olarak ayarlanır. Bu sayede çocuklar z
ekseninde kendilerini boşluk bırakabilir. transform
, --distance
özel özelliğine ayarlandı. Bu özellik, fareyle üzerine gelme ve odaklama ile 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 konusunda sorun değilse bu düğme, tarayıcıya dönüşüm özelliğinin değişime hazır olması gerektiğini ve transform
ile background-color
özellikleri için bir geçişin ayarlandığını bildirir. Süre farkına dikkat edince, hoş ve hoş bir aşamalı efekt
sağladığı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 }
}
}
Üzerine gelme ve odaklama etkileşim stilleri
Etkileşim animasyonunun amacı, düz görünen düğmeyi oluşturan katmanları yaymaktır. Bu işlemi, --distance
değişkenini başlangıçta 1px
olacak şekilde ayarlayarak yapabilirsiniz. Aşağıdaki kod örneğinde gösterilen seçici, düğmenin üzerine gelme veya odaklanma durumu göstergesi olması gereken bir cihazın düğme üzerinde durup durmadığını ve etkin olup olmadığını kontrol eder. Bu durumda aşağıdakileri yapmak için CSS uygulanır:
- Fareyle üzerine gelinen arka plan rengini uygulayın.
- Mesafeyi artırın .
- Hemen çıkma yumuşatma efekti ekleyin.
- Sözde öğe geçişlerini uzatı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 oldukça düzenliydi.
Üstteki ve alttaki öğeler efekti hoş ve hoş bir şekilde gösteriyor.
JavaScript ile küçük geliştirmeler
Arayüz zaten klavyelerden, ekran okuyuculardan, oyun kumandalarından, dokunmadan ve fareden kullanılabiliyor, ancak birkaç senaryoyu kolaylaştırmak için JavaScript'e biraz ufak dokunuşlar ekleyebiliriz.
Ok tuşlarını destekleme
Sekme tuşu, menüde gezinmenin iyi bir yoludur, ancak yön tuşları veya
kontrol çubuklarının odağı oyun kumandasına getirmesini beklerim. GUI Meydan Okuması arayüzleri için genellikle kullanılan roving-ux kitaplığı bizim için ok tuşlarını yönetir. Aşağıdaki kod, kütüphaneye odağı .threeD-button-set
içinde yakalamasını ve odağı düğme alt öğelerine yönlendirmesini bildirir.
import {rovingIndex} from 'roving-ux'
rovingIndex({
element: document.querySelector('.threeD-button-set'),
target: 'button',
})
Fare paralaks etkileşimi
Fareyi takip etmek ve menüyü yatırmak, artırılmış gerçeklik (AR) ve sanal gerçeklik (VR) video oyunu arayüzlerini taklit edecek şekilde tasarlanmıştır. Bu arayüzde, fare yerine sanal bir işaretçiye sahip olabilirsiniz. Öğeler işaretçiden tamamen haberdar olduğunda eğlenceli olabilir.
Bu ekstra küçük bir özellik olduğundan, etkileşimi kullanıcının hareket tercihiyle ilgili bir sorgunun arkasına koyacağız. Ayrıca, kurulumun bir parçası olarak düğme listesi bileşenini querySelector
ile belleğe depolayın ve öğenin sınırlarını menuRect
olarak ö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)'
)
Şimdi, 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şleve ihtiyacımız var. Aşağıdaki işlev kutunun içinde ve ne kadar yer aldığını belirlemek için farenin 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, farenin hareketini izleyin, konumu getAngles()
işlevimize geçirin ve delta değerlerini özel özellik stilleri olarak kullanın. Deltayı doldurmak ve titremeyi azaltmak için 20'ye
böldüm, bunu yapmanın daha iyi bir yolu olabilir. Başından beri --x
ve --y
özelliklerini bir clamp()
işlevinin ortasına yerleştirdiğimizi varsayalım. 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
Diğer yazma modlarında ve dillerde oyun menüsünü test ederken bir sorun oluştu.
<button>
öğeleri, kullanıcı aracısı stil sayfasında writing-mode
için !important
stiline sahip. Bu, oyun menüsü HTML'sinin istenen tasarıma uyacak şekilde
değiştirilmesi anlamına geliyordu. Düğme listesinin bağlantı listesine dönüştürülmesi, <a>
öğelerinin tarayıcı tarafından sağlanan bir !important
stiline sahip olmaması nedeniyle menü yönünü değiştirmek için mantıksal özelliklerin kullanılmasını sağlar.
Sonuç
Nasıl yapıldığını öğrendiniz. Peki, nasıl yapardınız? 🙂 Menüye ivme ölçer etkileşimi ekleyebilir misiniz? Böylece, telefonunuzu serpiştirdiğinizde menüyü döndürür müsünüz? Hareket yok deneyimini iyileştirebilir miyiz?
Yaklaşımlarımızı çeşitlendirelim ve web'de geliştirme yapmanın tüm yollarını öğrenelim. Bir demo oluşturun, bana tweet atın bağlantıları, aşağıdaki topluluk remiksleri bölümüne ekleyeceğim.
Topluluk remiksleri
Henüz burada görülecek bir şey yok.