3D गेम मेन्यू कॉम्पोनेंट बनाना

रिस्पॉन्सिव (स्क्रीन के हिसाब से साइज़ बदलने वाला), अडैप्टिव, और ऐक्सेस किया जा सकने वाला 3D गेम मेन्यू बनाने के तरीके की बुनियादी जानकारी.

इस पोस्ट में, मैं 3D गेम मेन्यू कॉम्पोनेंट बनाने के बारे में सोचना चाहता हूं. डेमो आज़माएं.

डेमो

अगर आप वीडियो पसंद करते हैं, तो यहां इस पोस्ट का YouTube वर्शन दिया गया है:

खास जानकारी

वीडियो गेम में अक्सर उपयोगकर्ताओं को एक क्रिएटिव और असामान्य मेन्यू, ऐनिमेशन और 3D स्पेस में दिखाया जाता है. यह नए एआर/वीआर गेम में काफ़ी लोकप्रिय है, जिससे मेन्यू स्पेस में तैरता हुआ दिखता है. आज हम इस इफ़ेक्ट की ज़रूरी चीज़ों पर फिर से काम करेंगे. हालांकि, कम मोशन वाली सेटिंग का इस्तेमाल करने वाले उपयोगकर्ताओं के लिए अडैप्टिव कलर स्कीम के इस्तेमाल और ठहरने की अलग-अलग सुविधाओं की मदद से ऐसा किया जा सकता है.

एचटीएमएल

गेम मेन्यू, बटन की सूची होती है. इसे एचटीएमएल में दिखाने का सबसे अच्छा तरीका ये हैं:

<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 या सीएसएस के बिना काम करता है.

आइटम के रूप में
सामान्य बटन वाली काफ़ी सामान्य बुलेट वाली सूची.

सीएसएस

बटन की सूची को स्टाइल करने पर, ये चरण पूरे हो जाते हैं:

  1. कस्टम प्रॉपर्टी सेट अप की जा रही हैं.
  2. फ़्लेक्सबॉक्स लेआउट.
  3. सजावटी नकली एलिमेंट वाला कस्टम बटन.
  4. एलिमेंट को 3D स्पेस में रखना.

कस्टम प्रॉपर्टी की खास जानकारी

कस्टम प्रॉपर्टी, किसी भी क्रम में दिखने वाली वैल्यू को सही नाम देकर, वैल्यू को साफ़ तौर पर समझाने में मदद करती हैं. साथ ही, कोड को दोहराने से बचाती हैं और वैल्यू बच्चों के बीच शेयर करती हैं.

यहां दी गई मीडिया क्वेरी, सीएसएस वैरिएबल के तौर पर सेव की गई हैं. इन्हें कस्टम मीडिया भी कहा जाता है. ये ग्लोबल होते हैं और इनका इस्तेमाल अलग-अलग सिलेक्टर में किया जाएगा, ताकि कोड को छोटा और पढ़ा जा सके. गेम मेन्यू कॉम्पोनेंट, डिसप्ले की मोशन सेटिंग, सिस्टम की कलर स्कीम, और कलर रेंज की क्षमताओं का इस्तेमाल करता है.

@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 की मदद से, फ़्लेक्सिबल की डिफ़ॉल्ट दिशा को पंक्तियों से कॉलम में बदलें. साथ ही, 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 स्पेस के कॉन्टेक्स्ट के तौर पर सेट करें और सीएसएस 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;
  }
}

rotate-y ऐनिमेशन, 50% पर सिर्फ़ बीच वाला मुख्य-फ़्रेम सेट करता है, क्योंकि ब्राउज़र 0% और 100% को डिफ़ॉल्ट एलिमेंट की डिफ़ॉल्ट स्टाइल पर सेट कर देगा. यह वैकल्पिक ऐनिमेशन के लिए शॉर्टहैंड है. इनका इस्तेमाल, एक ही पोज़िशन पर शुरू और खत्म होने के लिए किया जाना चाहिए. यह एक के बाद एक कई ऐनिमेशन बनाने का शानदार तरीका है.

@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;
}

जानकारी दिखाने के लिए सूची का स्क्रीनशॉट 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 व्यू में बटन की सूची का स्क्रीनशॉट, इस बार स्टाइल बटन के साथ.

बटन के स्युडो एलिमेंट

बटन के बॉर्डर पारंपरिक बॉर्डर नहीं होते हैं, बल्कि ये बॉर्डर वाले छद्म-तत्व होते हैं.

Chrome Devtools एलिमेंट पैनल का स्क्रीनशॉट, जिसमें एलिमेंट
::before और ::बाद के एलिमेंट दिखाए गए हैं.

ये एलिमेंट, पहले से मौजूद 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 पर सेट करें. कोड के नीचे दिए गए उदाहरण में दिखाया गया सिलेक्टर, यह जांच करता है कि बटन को किसी ऐसे डिवाइस पर घुमाया जा रहा है या नहीं जिस पर फ़ोकस दिखाने वाला इंंडिकेटर है और जिसे चालू नहीं किया गया है. अगर ऐसा है, तो सीएसएस इन कामों को करता है:

  • कर्सर घुमाने पर दिखने वाले बैकग्राउंड का रंग लागू करें.
  • दूरी बढ़ाएं .
  • बाउंस ईज़िंग इफ़ेक्ट जोड़ें.
  • छद्म-तत्व के ट्रांज़िशन को अलग करें.
.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 के कुछ हल्के-फुल्के बदलाव भी जोड़ सकते हैं.

साथ काम करने वाली तीर कुंजियां

मेन्यू को नेविगेट करने के लिए Tab बटन दबाने का अच्छा तरीका है, लेकिन मुझे उम्मीद है कि दिशा देने वाले पैड या जॉयस्टिक से गेमपैड पर फ़ोकस होगा. जीयूआई चैलेंज इंटरफ़ेस के लिए अक्सर इस्तेमाल की जाने वाली roving-ux लाइब्रेरी, हमारे लिए ऐरो कुंजियों को हैंडल करेगी. यहां दिया गया कोड, लाइब्रेरी को .threeD-button-set में फ़ोकस को कैप्चर करने और उस बटन पर फ़ोकस करने के बारे में बताता है.

import {rovingIndex} from 'roving-ux'

rovingIndex({
  element: document.querySelector('.threeD-button-set'),
  target: 'button',
})

माउस पैरालैक्स इंटरैक्शन

माउस को ट्रैक करने और मेन्यू को झुकाने का मकसद एआर और वीआर वीडियो गेम इंटरफ़ेस की नकल करना है, जिसमें माउस के बजाय आपके पास वर्चुअल पॉइंटर हो सकता है. यह मज़ेदार हो सकता है जब एलिमेंट को पॉइंटर के बारे में अच्छी तरह पता हो.

यह एक छोटी सी अतिरिक्त सुविधा है, इसलिए हम इंटरैक्शन को उपयोगकर्ता की मोशन प्राथमिकता की क्वेरी के पीछे डाल देंगे. इसके अलावा, सेटअप के हिस्से के तौर पर, बटन सूची वाले कॉम्पोनेंट को 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 स्टाइल होती है. इसका मतलब था कि गेम मेन्यू के एचटीएमएल को मनचाहे डिज़ाइन के मुताबिक बनाने के लिए बदलाव करना ज़रूरी था. बटन की सूची को लिंक की सूची में बदलने पर, लॉजिकल प्रॉपर्टी चालू हो जाती हैं. इससे मेन्यू की दिशा बदली जा सकती है, क्योंकि <a> एलिमेंट में ब्राउज़र के लिए !important स्टाइल नहीं है.

नतीजा

अब आपको पता चल गया है कि मैंने यह कैसे किया, तो आपको किस तरह करना होगा‽ 🙂 क्या नो मोशन अनुभव को बेहतर बनाया जा सकता है?

चलिए, इसे अलग-अलग तरीके से समझें और वेब पर सभी के काम करने के तरीके सीखें. एक डेमो तैयार करें, मुझे ट्वीट करें वाले लिंक, और मैं उसे नीचे दिए गए कम्यूनिटी रीमिक्स सेक्शन में जोड़ दूंगी!

कम्यूनिटी रीमिक्स

अभी यहां देखने के लिए कुछ नहीं है!