إنشاء مكوِّن قائمة للعبة ثلاثية الأبعاد

نظرة عامة أساسية حول كيفية إنشاء قائمة ألعاب ثلاثية الأبعاد متجاوبة وتكيُّفية وسهلة الاستخدام

في هذه المشاركة، أريد أن أشارك أفكاري حول طريقة إنشاء مكوّن قائمة ألعاب ثلاثية الأبعاد. جرِّب العرض التوضيحي.

العرض التوضيحي

إذا كنت تفضّل مشاهدة فيديو، إليك نسخة من هذا المنشور على YouTube:

نظرة عامة

تقدّم ألعاب الفيديو غالبًا للمستخدمين قائمة إبداعية وغير عادية، وتكون متحركة وفي مساحة ثلاثية الأبعاد. وهي شائعة في ألعاب الواقع المعزّز والواقع الافتراضي الجديدة لجعل القائمة تبدو وكأنّها تطفو في الفضاء. سنعيد اليوم إنشاء العناصر الأساسية لهذا التأثير، ولكن مع إضافة لمسة جمالية من خلال نظام ألوان متكيّف وخيارات للمستخدمين الذين يفضّلون تقليل الحركة.

HTML

قائمة الألعاب هي قائمة أزرار. أفضل طريقة لتمثيل ذلك في HTML هي كما يلي:

<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 أو CSS.

قائمة نقطية عامة جدًا تتضمّن أزرارًا عادية كعناصر

CSS

يمكن تقسيم عملية تصميم قائمة الأزرار إلى الخطوات العالية المستوى التالية:

  1. إعداد الخصائص المخصّصة
  2. تصميم مربّع مرن
  3. زر مخصّص يحتوي على عناصر زائفة مزخرفة
  4. وضع العناصر في مساحة ثلاثية الأبعاد

نظرة عامة على الخصائص المخصّصة

تساعد الخصائص المخصّصة في إزالة الغموض عن القيم من خلال منح أسماء ذات معنى للقيم التي تبدو عشوائية، وتجنُّب تكرار الرموز ومشاركة القيم بين العناصر التابعة.

في ما يلي طلبات بحث عن وسائط تم حفظها كمتغيرات CSS، تُعرف أيضًا باسم وسائط مخصّصة. هذه المتغيرات عامة وسيتم استخدامها في مختلف أدوات الاختيار للحفاظ على التعليمات البرمجية موجزة وسهلة القراءة. يستخدم مكوّن قائمة الألعاب إعدادات الحركة المفضّلة ونظام الألوان وإمكانات نطاق الألوان في الشاشة.

@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);
  }
}
عرض توضيحي لتغيير الخلفية بين إعدادات الألوان الفاتحة والداكنة المفضّلة

تفعيل المنظور الثلاثي الأبعاد

لكي تظهر العناصر في المساحة الثلاثية الأبعاد لصفحة ويب، يجب تهيئة إطار عرض يتضمّن منظورًا. لقد اخترت وضع المنظور على العنصر body واستخدمت وحدات إطار العرض لإنشاء النمط الذي أفضّله.

body {
  perspective: 40vw;
}

هذا هو نوع التأثير الذي يمكن أن تحدثه وجهة النظر.

تنسيق قائمة الأزرار <ul>

هذا العنصر مسؤول عن التنسيق العام لوحدة الماكرو الخاصة بقائمة الأزرار، كما أنّه عبارة عن بطاقة تفاعلية ثلاثية الأبعاد عائمة. إليك طريقة لتحقيق ذلك.

تنسيق مجموعة الأزرار

يمكن لـ Flexbox إدارة تخطيط الحاوية. غيِّر الاتجاه التلقائي للعناصر المرنة من صفوف إلى أعمدة باستخدام flex-direction وتأكَّد من أنّ حجم كل عنصر هو حجم محتواه من خلال تغيير القيمة من stretch إلى start في align-items.

.threeD-button-set {
  /* remove <ul> margins */
  margin: 0;

  /* vertical rag-right layout */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2.5vh;
}

بعد ذلك، حدِّد الحاوية كسياق مساحة ثلاثية الأبعاد وأعِدّ وظائف CSS 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 على عمليات التحويل. سيحدث هذا الانتقال عندما يتفاعل المؤشر مع البطاقة، ما يتيح انتقالات سلسة إلى تغييرات الدوران. الصورة المتحركة هي صورة متحركة تعمل باستمرار توضّح المساحة الثلاثية الأبعاد التي تقع فيها البطاقة، حتى إذا كان يتعذّر على الماوس التفاعل مع المكوّن أو لم يتفاعل معه.

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

لقطة شاشة للقائمة التي تم تدويرها في مساحة ثلاثية الأبعاد لعرض المنظور، ولم يعُد كل عنصر من عناصر القائمة يتضمّن تعدادًا نقطيًا

تنسيق عناصر <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;
}

لقطة شاشة لقائمة الأزرار بمنظور ثلاثي الأبعاد، هذه المرة مع أزرار منمّقة

العناصر الزائفة للأزرار

حدود الزر ليست حدودًا تقليدية، بل هي عناصر زائفة ذات موضع مطلق مع حدود.

لقطة شاشة للوحة &quot;العناصر&quot; في Chrome DevTools مع ظهور زر يتضمّن العنصرين
::before و ::after.

هذه العناصر ضرورية لعرض المنظور الثلاثي الأبعاد الذي تم إنشاؤه. سيتم إبعاد أحد هذين العنصرين الزائفين عن الزر، وسيتم تقريب الآخر من المستخدم. ويظهر التأثير بشكل ملحوظ في الأزرار العلوية والسفلية.

.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);
    }
  }
}

أنماط التحويل الثلاثي الأبعاد

تم ضبط قيمة 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 جاهزة للتغيير، ويتم ضبط انتقال للخاصيتين 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 في البداية. يتحقّق أداة الاختيار المعروضة في مثال الرمز التالي مما إذا كان الزر يتم تمرير مؤشر الماوس فوقه أو التركيز عليه بواسطة جهاز من المفترض أن يعرض مؤشر تركيز، وما إذا كان يتم تفعيله. وفي هذه الحالة، يتم تطبيق CSS لتنفيذ ما يلي:

  • تطبيق لون الخلفية عند التمرير فوق العنصر
  • زيادة المسافة
  • أضِف تأثيرًا للارتداد السلس.
  • تداخل انتقالات العنصر الزائف
.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 الحركة المفضّل. تعرض العناصر العلوية والسفلية التأثير بطريقة لطيفة ودقيقة.

تحسينات بسيطة باستخدام JavaScript

يمكن استخدام الواجهة من خلال لوحات المفاتيح وبرامج قراءة الشاشة وأذرع التحكّم ولوحات اللمس والماوس، ولكن يمكننا إضافة بعض اللمسات الخفيفة من JavaScript لتسهيل بعض السيناريوهات.

إتاحة مفاتيح الأسهم

يُعدّ مفتاح Tab طريقة جيدة للتنقّل في القائمة، ولكن أتوقّع أن يؤدي الضغط على لوحة الاتجاهات أو عصا التحكم إلى نقل التركيز على وحدة التحكّم في الألعاب. ستتعامل مكتبة roving-ux، التي تُستخدَم غالبًا لواجهات GUI Challenge، مع مفاتيح الأسهم. يطلب الرمز البرمجي أدناه من المكتبة حصر التركيز ضمن .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> على نمط !important لـ writing-mode في ورقة أنماط وكيل المستخدم. وهذا يعني أنّه كان يجب تغيير رمز HTML الخاص بقائمة اللعبة لاستيعاب التصميم المطلوب. يؤدي تغيير قائمة الأزرار إلى قائمة روابط إلى إتاحة تغيير اتجاه القائمة باستخدام الخصائص المنطقية، لأنّ عناصر <a> لا تتضمّن نمط !important يوفّره المتصفّح.

الخاتمة

بعد أن عرفت كيف فعلت ذلك، كيف ستفعلها أنت؟ 🙂 هل يمكنك إضافة تفاعل مقياس التسارع إلى القائمة، بحيث يؤدي تحريك هاتفك إلى تدوير القائمة؟ هل يمكننا تحسين تجربة عدم الحركة؟

لنستكشف الطرق المختلفة لإنشاء مواقع إلكترونية على الويب. يمكنك إنشاء عرض توضيحي، إرسال تغريدة إليّ تتضمّن رابطًا إليه، وسأضيفه إلى قسم "ريمكسات من المنتدى" أدناه.

ريمكسات من إنشاء المنتدى

ما مِن عناصر للاطّلاع عليها هنا حتى الآن.