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