نظرة عامة أساسية حول كيفية إنشاء قائمة ألعاب ثلاثية الأبعاد متجاوبة ومتكيّفة وسهلة الاستخدام
في هذه المشاركة، أريد مشاركة أفكار حول طريقة إنشاء مكوّن قائمة لعبة ثلاثي الأبعاد. جرِّب الإصدار التجريبي.
إذا كنت تفضّل مشاهدة الفيديوهات، يمكنك الاطّلاع على نسخة من هذه المشاركة على 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
يمكن تقسيم تصميم قائمة الأزرار إلى الخطوات الأساسية التالية:
- إعداد الخصائص المخصّصة
- تنسيق flexbox
- زر مخصّص يتضمّن عناصر زائفة مزخرفة
- وضع العناصر في مساحة ثلاثية الأبعاد
نظرة عامة على الخصائص المخصّصة
تساعد السمات المخصّصة في إزالة الغموض عن القيم من خلال منح أسماء مفيدة للقيم التي تبدو عشوائية، ما يتجنّب تكرار الرموز ومشاركة القيم بين الأطفال.
في ما يلي طلبات البحث عن الوسائط المحفوظة كمتغيّرات 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%
لأنّ ال browser سيضبط 0%
و100%
تلقائيًا على النمط التلقائي للعنصر. هذا اختصار للصور المتحركة التي تتناوب، ويجب أن تبدأ وتنتهي في الموضع نفسه. وهي طريقة رائعة لعرض صور متحركة متغيّرة بلا حدود.
@keyframes rotate-y {
50% {
transform: rotateY(15deg) rotateX(-6deg);
}
}
تصميم عناصر <li>
يحتوي كل عنصر في القائمة (<li>
) على الزر وعناصر حدوده. تم تغيير نمط
display
لكي لا يعرض العنصر رمز
::marker
. تم ضبط position
style
على 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;
}
العناصر الزائفة للزر
إنّ حدود الزرّ ليست حدودًا تقليدية، بل هي عناصر وهمية بحدود لها موضع مطلق.
هذه العناصر ضرورية لعرض المنظور الثلاثي الأبعاد الذي تم تحديده. سيتم دفع أحد هذه العناصر الزائفة بعيدًا عن الزر، وسيتم سحب أحدها إلى أقرب نقطة من المستخدم. ويظهر التأثير بشكلٍ ملحوظ في الزرَّين العلوي والسفلي.
.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 البسيطة لتسهيل بعض السيناريوهات.
إتاحة استخدام مفاتيح الأسهم
يُعدّ مفتاح التبويب طريقة رائعة للتنقّل في القائمة، ولكنّني أتوقع أن يتم استخدام لوحة ألعاب متّجه
ة أو عصا ألعاب لنقل التركيز على لوحة ألعاب. ستتولى مكتبة
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
وتعرِض
قيمة يمكننا استخدامها لتدوير البطاقة. تستخدِم الدالة التالية ملفوظة الماوس
لتحديد جانب العلبة الذي تقع فيه وكم تبعد عنه. يتم عرض
delta من الدالة.
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()
واستخدِم قيم delta كأشكال مخصّصة للعناصر. لقد قسمت المقدار على 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
في ملف الأنماط الخاص بوكيل <button>
. وهذا يعني أنّه يجب تغيير رمز HTML لقائمة اللعبة لاستيعاب
التصميم المطلوب. يؤدي تغيير قائمة الأزرار إلى قائمة روابط إلى تفعيل خصائص
المنطقية لتغيير اتجاه القائمة، لأنّ عناصر <a>
لا تتضمّن نمط !important
يوفّره المتصفّح.
الخاتمة
الآن بعد أن عرفت كيف فعلت ذلك، كيف ستفعل ذلك؟ 🙂 هل يمكنك إضافة تفاعل مع مقياس التسارع في القائمة، بحيث يؤدي وضع الهاتف في الوضع الأفقي إلى تدوير القائمة؟ هل يمكننا تحسين تجربة "الصور بدون حركة"؟
لننوّع أساليبنا ونتعرّف على جميع الطرق لإنشاء تطبيقات على الويب. أنشئ عرضًا توضيحيًا وأرسِل إلينا رابطًا على Twitter، وسنضيفه إلى قسم الريمكسات التي أنشأها المستخدمون أدناه.
الريمكسات التي أنشأها المستخدمون
ما مِن عناصر للاطّلاع عليها هنا حتى الآن.