Duyarlı, uyarlanabilir ve erişilebilir bir 3D oyun menüsü oluşturmaya dair temel bilgiler.
Bu yayında, 3D oyun menüsü bileşeni oluşturma konusundaki düşüncelerimi paylaşmak 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 bir mekanda yaratıcı ve sıra dışı bir menü sunar. Yeni AR/VR oyunlarında menünün havada süzülüyormuş gibi görünmesi popüler bir özelliktir. Bugün bu efektin temel özelliklerini yeniden oluşturacağız ancak bu kez uyarlanabilir bir renk şeması ve hareketi azaltmayı tercih eden kullanıcılar için düzenlemeler de yapacağız.
HTML
Oyun menüsü, düğmelerin listesidir. 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üğme listesi, ekran okuyucu teknolojilerine iyi bir şekilde duyurulur ve JavaScript veya CSS olmadan çalışır.
CSS
Düğme listesine stil uygulama işlemi aşağıdaki genel adımlara ayrılır:
- Özel mülkler oluşturma.
- Flexbox düzeni.
- Dekoratif sözde öğeler içeren özel bir düğme.
- Öğeleri 3D uzaya yerleştirme.
Özel özelliklere genel bakış
Özel mülkler, rastgele görünen değerlere anlamlı adlar vererek değerlerin anlamını netleştirmeye yardımcı olur, tekrarlanan koddan ve değerler arasında paylaşımdan kaçınır.
Aşağıda, özel medya olarak da bilinen CSS değişkenleri olarak kaydedilen medya sorguları verilmiştir. Bunlar geneldir ve kodun kısa ve okunaklı kalması için çeşitli seçicilerde kullanılır. Oyun menüsü bileşeni, hareket tercihlerini, sistem renk şemasını ve ekranın 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 düzenini yönetir ve fareyle üzerine gelindiğinde oyun menüsünü etkileşimli hale getirmek için fare konum değerlerini tutar. Özel mülkleri adlandırmak, değerin kullanım alanını veya sonucunun kullanıcı dostu adını gösterdiğinden 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 temalı arka plan koni arka planları
Açık temada cyan
ile deeppink
arasında canlı bir konik degrade bulunur. Koyu temada ise koyu ve ince bir konik degrade bulunur. Konik degradelerle neler yapılabileceği hakkında daha fazla bilgi edinmek 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 perspektif içeren bir görüntü alanının başlatılması gerekir. Perspektifi body
öğesine yerleştirmeyi seçtim ve beğendiğim stili oluşturmak için ekran alanı birimlerini kullandım.
body {
perspective: 40vw;
}
Bu, bakış açısının sahip olabileceği etki türüdür.
<ul>
düğme listesini biçimlendirme
Bu öğe, genel düğme listesi makro düzeninden ve etkileşimli ve 3D yüzen bir 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 esnek öğenin varsayılan yönünü satırlardan sütunlara değiştirin ve align-items
için stretch
yerine start
değerini kullanarak her öğ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;
}
Ardından, kapsayıcıyı 3D alan bağlamı olarak oluşturun ve kartın okunabilir rotasyonlardan daha fazla dönmemesini sağlamak için CSS clamp()
işlevleri ayarlayın. Sınırın orta değerinin özel bir özellik olduğunu unutmayın. Bu --x
ve --y
değerleri, daha sonra fare etkileşimi üzerine 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ı hareketi kabul ediyorsa tarayıcıya bu öğenin dönüştürme işleminin will-change
ile sürekli değişeceğini belirten bir ipucu ekleyin.
Ayrıca, dönüştürmelerde transition
ayarlayarak ara değer hesaplamayı etkinleştirin. Bu geçiş, fare kartla etkileşime geçtiğinde gerçekleşir ve döndürme değişikliklerine sorunsuz geçişler sağlar. Fare bileşenle etkileşime geçemese veya etkileşime girmese bile kartın 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%
'i varsayılan olarak öğenin varsayılan stiline ayarlayacağından rotate-y
animasyonu yalnızca orta animasyon karesini 50%
olarak ayarlar. Bu, aynı konumda başlaması ve bitmesi gereken, sırayla oynatılan animasyonlar için kısayoldur. Bu, sonsuz olarak değişen animasyonları ifade etmenin mükemmel bir yoludur.
@keyframes rotate-y {
50% {
transform: rotateY(15deg) rotateX(-6deg);
}
}
<li>
öğelerine stil uygulama
Her liste öğesi (<li>
), düğmeyi ve kenar öğelerini içerir. Öğede ::marker
gösterilmemesi için display
stili değiştirilir. position
stili, sonraki düğme sözde öğelerinin kendilerini düğmenin kapladığı alanın tamamında konumlandırabilmesi için relative
olarak ayarlanır.
.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>
öğelerine stil uygulama
Düğmelere stil uygulamak zor olabilir. Dikkate alınması gereken çok sayıda durum ve etkileşim türü vardır. Sözde öğeler, animasyonlar ve etkileşimlerin dengelenmesi nedeniyle bu düğmeler hızla 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 kenarları geleneksel kenarlık değildir, kenarlık içeren mutlak konumlu sözde öğelerdir.
Bu öğeler, oluşturulan 3D perspektifi göstermek için çok önemlidir. Bu sözde öğelerden biri düğmeden uzağa, diğeri ise kullanıcıya daha yakın çekilir. Bu 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
Aşağıda, çocukların z
ekseninde birbirlerinden uzaklaşabilmeleri için transform-style
preserve-3d
olarak ayarlanmıştır. transform
, --distance
özel özelliğine ayarlanır. Bu özellik, fareyle üzerine gelindiğinde ve odaklanıldığında artar.
.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ı hareketi kabul ederse düğme, tarayıcıya dönüştürme özelliğinin değişikliğe hazır olması gerektiğini ve transform
ile background-color
mülkleri için bir geçiş ayarlandığını belirtir. Süredeki farklılığa dikkat edin. Bu, hoş ve zarif bir kademeli efekt oluşturdu.
.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. Bunu yapmak için --distance
değişkenini başlangıçta 1px
olarak ayarlayın. Aşağıdaki kod örneğinde gösterilen seçici, düğmenin fareyle üzerine gelinip gelinmediğini veya odak göstergesi görmesi gereken bir cihaz tarafından odaklanıp odaklanmadığını ve etkinleştirilip etkinleştirilmediğini kontrol eder. Bu durumda, aşağıdakileri yapmak için CSS uygular:
- Fareyle üzerine gelindiğinde gösterilecek arka plan rengini uygulayın.
- Mesafeyi artırın .
- Sıçrama kolaylığı efekti ekleyin.
- Sanal öğe geçişlerini kademeli olarak ayarlayı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 }
}
}
}
reduced
hareket tercihi için 3D perspektif yine de çok şıktı.
Üst ve alttaki öğeler, efekti hoş ve ince bir şekilde gösterir.
JavaScript ile küçük iyileştirmeler
Arayüz şu anda klavye, ekran okuyucu, oyun kumandası, dokunmatik ekran ve fare ile kullanılabilir. Ancak bazı senaryoları kolaylaştırmak için JavaScript'e bazı küçük dokunuşlar ekleyebiliriz.
Ok tuşlarını destekleme
Sekme tuşu, menüde gezinmek için iyi bir yöntemdir ancak gamepad'de odağı taşımak için yön çubuğu veya kontrol çubuklarının kullanılmasını beklerdim. GUI Challenge arayüzleri için genellikle kullanılan roving-ux kitaplığı, ok tuşlarını bizim için yönetir. Aşağıdaki kod, kitaplığa odaklanmayı .threeD-button-set
içinde yakalamasını ve düğme alt öğelerine iletmesini söyler.
import {rovingIndex} from 'roving-ux'
rovingIndex({
element: document.querySelector('.threeD-button-set'),
target: 'button',
})
Fare paralaks etkileşimi
Fareyi takip edip menüyü yatırmak, fare yerine sanal bir işaretçiniz olabileceği AR ve VR video oyunu arayüzlerini taklit etmeyi amaçlar. Öğelerin işaretçiye karşı aşırı duyarlı olması eğlenceli olabilir.
Bu küçük bir ek özellik olduğundan etkileşimi, kullanıcının hareket tercihine yönelik bir sorgunun arkasına yerleştireceğiz. Ayrıca, kurulum kapsamında düğme listesi bileşenini querySelector
ile bellekte depolayın ve öğenin sınırlarını menuRect
içinde ö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, 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, fare konumunu kullanarak farenin kutunun hangi tarafında olduğunu ve ne kadar uzakta olduğunu belirler. Değişiklik 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 iletin ve delta değerlerini özel mülk stilleri olarak kullanın. Deltayı doldurmak ve daha az seğirme yapması için 20'ye böldüm. Bunu yapmanın daha iyi bir yolu olabilir. Başlangıçta --x
ve --y
öğelerini bir clamp()
işlevinin ortasına koyduğumuzu hatırlıyorsanız. Bu, fare konumunun kartı okunamayacak bir konuma döndürmesini engeller.
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 yazım 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 sahiptir. Bu nedenle, oyun menüsü HTML'sinin istenen tasarıma uygun şekilde değiştirilmesi gerekiyordu. Düğme listesini bağlantı listesine dönüştürmek, <a>
öğelerinde tarayıcı tarafından sağlanan bir !important
stili olmadığından mantıksal özelliklerin menü yönünü değiştirmesini sağlar.
Sonuç
Bunu nasıl yaptığımı öğrendiğinize göre, siz nasıl yapardınız? 🙂 Telefonunuzu kaydırarak menüyü döndürmek için menüye ivmeölçer etkileşimi ekleyebilir misiniz? Hareketsiz görüntü deneyimini iyileştirebilir miyiz?
Yaklaşımlarımızı çeşitlendirelim ve web'de uygulama geliştirmenin tüm yollarını öğrenelim. Bir demo oluşturun, bağlantılarını bana tweetleyin. Ardından, aşağıdaki topluluk remiksleri bölümüne ekleyeceğim.
Topluluk remiksleri
Henüz burada gösterilecek bir şey yok.