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

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

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

إصدار تجريبي

في ما يلي إصدار 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. تصميم flexbox:
  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;
}

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

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

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

لقطة شاشة للوحة Chrome Devtools Elements (عناصر أدوات مطوّري البرامج في Chrome) ويظهر على زرّ يحتوي على العنصرَين
::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 و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 غالبًا ما تُستخدم في واجهات تحدي واجهة المستخدم الرسومية ستتعامل مع مفاتيح الأسهم نيابةً عنا. يطلب الرمز البرمجي أدناه من المكتبة حصر التركيز داخل .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 يوفّر متصفّحًا.

الخلاصة

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

دعونا ننويع أساليبنا ونتعلم جميع طرق الإنشاء على الويب. يمكنك إنشاء عرض توضيحي وروابط تغريدات لي وسنضيفها إلى قسم الريمكسات في المنتدى أدناه.

ريمكسات من المنتدى

لا يتوفّر أي محتوى بعد.