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

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

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

عرض توضيحي

إليك نسخة من هذه المشاركة على 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;
}

بعد ذلك، يمكنك إعداد الحاوية كسياق مساحة ثلاثية الأبعاد وإعداد دوال clamp() في CSS لضمان عدم تدوير البطاقة خارج نطاق عمليات التدوير الواضحة. يُرجى العلم أنّ القيمة المتوسطة للتثبيت هي سمة مخصّصة، وسيتم ضبط قيمتَي --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;عناصر أدوات مطوّري البرامج في Chrome&quot; مع زر يظهر فيه العنصران ::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 التي تُستخدم غالبًا مع واجهات GUI مع مفاتيح الأسهم. يطلب الرمز البرمجي أدناه من المكتبة رصد التركيز داخل .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 مضمَّنًا في المتصفح.

الخلاصة

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

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

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

ما من محتوى جديد حتى الآن.