نظرة عامة أساسية حول كيفية إنشاء مكوّن تبديل متجاوب وسهل الاستخدام
في هذه المشاركة، أريد مشاركة أفكار حول طريقة إنشاء مكوّنات التبديل. جرِّب العرض التجريبي.
إذا كنت تفضّل مشاهدة الفيديوهات، يمكنك الاطّلاع على نسخة من هذه المشاركة على YouTube:
نظرة عامة
يعمل مفتاح التبديل بشكل مشابه لمربّع الاختيار، ولكنه يمثّل بشكل صريح الحالتَين المنطقيتَين "مفعّل" و"غير مفعّل".
يستخدم هذا العرض الترويجي <input type="checkbox" role="switch">
لمعظم
وظائفه، ما يمنح ميزة عدم الحاجة إلى CSS أو JavaScript ليعمل بشكلٍ كامل ويكون متاحًا للجميع. يتيح تحميل CSS استخدام اللغات من اليمين إلى اليسار
والعرض العمودي والصور المتحركة وغير ذلك. يؤدي تحميل JavaScript إلى جعل مفتاح التبديل قابلاً للتحريك واللمس.
الخصائص المخصّصة
تمثّل المتغيّرات التالية الأجزاء المختلفة للتبديل
وخياراتها. بصفتها فئة المستوى الأعلى، تحتوي .gui-switch
على خصائص مخصّصة يتم استخدامها
في جميع العناصر الفرعية للمكونات، ونقاط دخول للقيام بعملية تخصيص
مركزية.
مسار
الطول (--track-size
) والتباعد واللونان:
.gui-switch {
--track-size: calc(var(--thumb-size) * 2);
--track-padding: 2px;
--track-inactive: hsl(80 0% 80%);
--track-active: hsl(80 60% 45%);
--track-color-inactive: var(--track-inactive);
--track-color-active: var(--track-active);
@media (prefers-color-scheme: dark) {
--track-inactive: hsl(80 0% 35%);
--track-active: hsl(80 60% 60%);
}
}
صورة مصغرة
حجم التفاعل ولونه ولون الخلفية:
.gui-switch {
--thumb-size: 2rem;
--thumb: hsl(0 0% 100%);
--thumb-highlight: hsl(0 0% 0% / 25%);
--thumb-color: var(--thumb);
--thumb-color-highlight: var(--thumb-highlight);
@media (prefers-color-scheme: dark) {
--thumb: hsl(0 0% 5%);
--thumb-highlight: hsl(0 0% 100% / 25%);
}
}
الحدّ من الحركة
لإضافة عنوان بديل واضح وتقليل التكرار، يمكن وضع طلب ملف شخصي وسائط يفضّل استخدام الصور ذات الحركة المنخفضة في موقع مخصّص باستخدام مكوّن PostCSS الإضافي استنادًا إلى مسودة المواصفات في طلبات ملفات الوسائط 5:
@custom-media --motionOK (prefers-reduced-motion: no-preference);
Markup
اختَرتُ لفّ عنصر <input type="checkbox" role="switch">
بعنصر
<label>
، ما يجمع علاقتهما لتجنُّب غموض ربط مربّع الاختيار بالتصنيف، مع منح المستخدم إمكانية التفاعل مع التصنيف لقلب الإدخال.
<label for="switch" class="gui-switch">
Label text
<input type="checkbox" role="switch" id="switch">
</label>
<input type="checkbox">
مُعدّ مسبقًا باستخدام
واجهة برمجة تطبيقات
وحالة. يدير المتصفّح
checked
الخاصية وأحداث
الإدخال
مثل oninput
وonchanged
.
التنسيقات
إنّ Flexbox وشبكة وسمات المخصّصة مهمة للحفاظ على أنماط هذا المكوّن. وهي تُجمِّع القيم وتمنح أسماء للعمليات الحسابية أو المناطق التي تكون غير واضحة، وتُفعِّل واجهة برمجة تطبيقات خاصة بموقع مخصّص صغير لتسهيل تخصيص المكوّنات.
.gui-switch
تنسيق المستوى الأعلى للتبديل هو flexbox. تحتوي الفئة .gui-switch
على
السمات المخصّصة الخاصة والعامة التي يستخدمها الأطفال لاحتساب
التنسيقات.
.gui-switch {
display: flex;
align-items: center;
gap: 2ch;
justify-content: space-between;
}
إنّ توسيع تنسيق المربّع المرن وتعديله يشبه تغيير أي تنسيق مربّع مرن.
على سبيل المثال، لوضع تصنيفات فوق مفتاح تبديل أو تحته، أو لتغيير
flex-direction
:
<label for="light-switch" class="gui-switch" style="flex-direction: column">
Default
<input type="checkbox" role="switch" id="light-switch">
</label>
مسار
تمّ تصميم إدخال مربّع الاختيار على أنّه مسار تبديل من خلال إزالة appearance: checkbox
العادي وتقديم حجمه الخاص بدلاً من ذلك:
.gui-switch > input {
appearance: none;
inline-size: var(--track-size);
block-size: var(--thumb-size);
padding: var(--track-padding);
flex-shrink: 0;
display: grid;
align-items: center;
grid: [track] 1fr / [track] 1fr;
}
ينشئ المقطع الصوتي أيضًا منطقة مسار شبكة خلية واحدة لكل إصبع لتحديده بهدف المطالبة به.
صورة مصغرة
يزيل النمط appearance: none
أيضًا علامة الاختيار المرئية التي يوفّرها browser. يستخدم هذا المكوّن
عنصرًا زائفًا و:checked
الفئة الزائفة في الإدخال بهدف
استبدال هذا المؤشر المرئي.
مؤشر التمرير هو عنصر زائف تابع لعنصر input[type="checkbox"]
ويتم تجميعه فوق المقطع الصوتي بدلاً من تحته من خلال الاستيلاء على مساحة الشبكة
track
:
.gui-switch > input::before {
content: "";
grid-area: track;
inline-size: var(--thumb-size);
block-size: var(--thumb-size);
}
الأنماط
تتيح السمات المخصّصة مكوّن تبديل متعدد الاستخدامات يتكيّف مع أنظمة الألوان واللغات من اليمين إلى اليسار والإعدادات المفضّلة للحركة.
أنماط التفاعل باللمس
على الأجهزة الجوّالة، تضيف المتصفّحات ميزات تمييز النقرات واختيار النص إلى التصنيفات
والمدخلات. وقد أثّرت هذه التغييرات سلبًا في ملاحظات التفاعل المرئي والأسلوب التي كانت تحتاج إليها عملية التبديل هذه. باستخدام بضعة أسطر من CSS، يمكنني إزالة هذه التأثيرات وإضافة
أسلوب cursor: pointer
الخاص بي:
.gui-switch {
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
لا يُنصح دائمًا بإزالة هذه الأنماط، لأنّها قد توفّر ملاحظات قيّمة بشأن التفاعل البصري. احرص على تقديم بدائل مخصّصة في حال إزالتها.
مسار
ترتبط أنماط هذا العنصر بشكله ولونه في أغلب الأحيان، ويحصل عليها
من العنصر الرئيسي .gui-switch
من خلال
التسلسل.
.gui-switch > input {
appearance: none;
border: none;
outline-offset: 5px;
box-sizing: content-box;
padding: var(--track-padding);
background: var(--track-color-inactive);
inline-size: var(--track-size);
block-size: var(--thumb-size);
border-radius: var(--track-size);
}
تأتي مجموعة كبيرة من خيارات التخصيص لمسار التبديل من أربع
خصائص مخصّصة. تتم إضافة border: none
لأنّ appearance: none
لا يزيل الحدود من مربّع الاختيار في جميع المتصفّحات.
صورة مصغرة
عنصر الإبهام موجود على اليمين track
ولكنّه يحتاج إلى أنماط الدوائر:
.gui-switch > input::before {
background: var(--thumb-color);
border-radius: 50%;
}
التفاعل
استخدِم السمات المخصّصة للاستعداد للتفاعلات التي ستُظهر تمييزات عند التمرير فوقها وتغييرات موضع إصبعَي الإبهام. يتم أيضًا التحقّق من إعدادات المستخدم المفضّلة قبل الانتقال إلى انماط تمييز العناصر أثناء التمرير أو أثناء الحركة.
.gui-switch > input::before {
box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);
@media (--motionOK) { & {
transition:
transform var(--thumb-transition-duration) ease,
box-shadow .25s ease;
}}
}
موضع الإبهام
توفّر السمات المخصّصة آلية مصدر واحدة لوضع إصبع الماوس في
المقطع الصوتي. يتوفّر لدينا أحجام المقطع الصوتي والملصق التي سنستخدمها في
الحسابات للحفاظ على وضع الملصق بشكل صحيح بين الإطارات ضمن المقطع الصوتي:
0%
و100%
.
يملك العنصر input
متغيّر الموضع --thumb-position
، ويستخدمه العنصر النائب
thumb كقيمة لموضع translateX
:
.gui-switch > input {
--thumb-position: 0%;
}
.gui-switch > input::before {
transform: translateX(var(--thumb-position));
}
أصبح بإمكاننا الآن تغيير --thumb-position
من CSS والفئات الصورية
المتوفّرة في عناصر مربّعات الاختيار. بما أنّنا ضبطنا transition: transform
var(--thumb-transition-duration) ease
بشكل مشروط في وقت سابق على هذا العنصر، قد تؤدي هذه التغييرات
إلى ظهور صورة متحركة عند تغييرها:
/* positioned at the end of the track: track length - 100% (thumb width) */
.gui-switch > input:checked {
--thumb-position: calc(var(--track-size) - 100%);
}
/* positioned in the center of the track: half the track - half the thumb */
.gui-switch > input:indeterminate {
--thumb-position: calc(
(var(--track-size) / 2) - (var(--thumb-size) / 2)
);
}
أعتقد أنّ عملية التنسيق هذه التي تم فصلها عن بعضها قد نجحت بشكل جيد. لا يهتم عنصر Thumb سوى بنمط واحد، وهو موضع translateX
. يمكن أن تعالج المدخلات كل الصعوبات والحسابات.
موضع الإعلان
تم إجراء عملية التحسين باستخدام فئة معدِّل -vertical
التي تضيف دورانًا باستخدام
عمليات تحويل CSS إلى عنصر input
.
ومع ذلك، لا يؤدي العنصر المُدار بتقنية 3D إلى تغيير الارتفاع الإجمالي للمكوّن،
مما قد يؤدي إلى تغيير تنسيق الكتلة. يمكنك مراعاة ذلك باستخدام المتغيّرين --track-size
و
--track-padding
. احتسِب الحد الأدنى للمساحة المطلوبة لترتيب زر عمودي في التنسيق على النحو المتوقّع:
.gui-switch.-vertical {
min-block-size: calc(var(--track-size) + calc(var(--track-padding) * 2));
& > input {
transform: rotate(-90deg);
}
}
(RTL) من اليمين إلى اليسار
تعاونت مع صديقي إيلات شيكرت، وهو خبير في CSS، في إنشاء نموذج أولي لقائمة جانبية قابلة للانزلاق باستخدام عمليات تحويل CSS التي تتعامل مع اللغات التي تُقرأ من اليمين إلى اليسار من خلال قلب متغيّر واحد. لقد فعلنا ذلك لأنّه لا تتوفّر عمليات تحويل منطقية للخصائص في CSS، وقد لا تتوفّر أبدًا. لقد اقترح "إيلات" استخدام قيمة مخصّصة للموقع لعكس النسب المئوية، وذلك للسماح بإدارة موقع جغرافي واحد من خلال منطقنا المخصّص للتحويلات المنطقية. لقد استخدمتُ هذه الطريقة نفسها في عملية التبديل هذه وأعتقد أنّه تم تنفيذها على أكمل وجه:
.gui-switch {
--isLTR: 1;
&:dir(rtl) {
--isLTR: -1;
}
}
تحتوي الخاصية المخصّصة التي تُسمّى --isLTR
في البداية على القيمة 1
، ما يعني أنّها
true
لأنّ تنسيقنا من اليمين إلى اليسار تلقائيًا. بعد ذلك، باستخدام فئة CSS المزوّدة بقيمة زائفة :dir()
،
يتم ضبط القيمة على -1
عندما يكون المكوّن ضمن تنسيق من اليمين إلى اليسار.
يمكنك استخدام --isLTR
من خلال تضمينها في calc()
ضمن عملية تحويل:
.gui-switch.-vertical > input {
transform: rotate(-90deg);
transform: rotate(calc(90deg * var(--isLTR) * -1));
}
يراعي الآن دوران مفتاح التبديل العمودي موضع الجانب المقابل الذي يتطلّبه التنسيق من اليمين إلى اليسار.
يجب أيضًا تعديل عمليات التحويل translateX
في العنصر النائب للإصبع للتمكّن من مراعاة متطلبات الجانب المقابل:
.gui-switch > input:checked {
--thumb-position: calc(var(--track-size) - 100%);
--thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}
.gui-switch > input:indeterminate {
--thumb-position: calc(
(var(--track-size) / 2) - (var(--thumb-size) / 2)
);
--thumb-position: calc(
((var(--track-size) / 2) - (var(--thumb-size) / 2))
* var(--isLTR)
);
}
على الرغم من أنّ هذا النهج لن يحلّ جميع الاحتياجات المتعلقة بمفهوم مثل عمليات تحويل CSS المنطقية، إلا أنّه يقدّم بعض مبادئ DRY للعديد من حالات الاستخدام.
الولايات
لا يكتمل استخدام input[type="checkbox"]
المضمّن بدون
معالجة الحالات المختلفة التي يمكن أن يكون فيها: :checked
و:disabled
:indeterminate
و:hover
. تمّ عمدًا عدم إجراء أي تعديل على :focus
، باستثناء تعديل التنسيق، وظهرت حلقة التركيز بشكل رائع على Firefox و
Safari:
محدد
<label for="switch-checked" class="gui-switch">
Default
<input type="checkbox" role="switch" id="switch-checked" checked="true">
</label>
تمثّل هذه الحالة حالة on
. في هذه الحالة، يتم ضبط
خلفية الإدخال "المسار" على اللون النشط ويتم ضبط موضع شريط التمرير على "
النهاية".
.gui-switch > input:checked {
background: var(--track-color-active);
--thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}
غير مفعّل
<label for="switch-disabled" class="gui-switch">
Default
<input type="checkbox" role="switch" id="switch-disabled" disabled="true">
</label>
لا يختلف زر :disabled
فقط من الناحية المرئية، بل يجب أن يجعل
العنصر غير قابل للتغيير.لا يعتمد عدم قابلية التفاعل للتغيير على المتصفّح، ولكن تحتاج
الحالات المرئية إلى أنماط بسبب استخدام appearance: none
.
.gui-switch > input:disabled {
cursor: not-allowed;
--thumb-color: transparent;
&::before {
cursor: not-allowed;
box-shadow: inset 0 0 0 2px hsl(0 0% 100% / 50%);
@media (prefers-color-scheme: dark) { & {
box-shadow: inset 0 0 0 2px hsl(0 0% 0% / 50%);
}}
}
}
هذه الحالة صعبة لأنّها تحتاج إلى مظهرَين داكن وفاتح مع حالتَي إيقاف و تفعيل. لقد اخترتُ أنماطًا بسيطة لهذه الحالات لتخفيف عبء صيانة مجموعات الأنماط.
غير محدَّد
إنّ الحالة :indeterminate
التي غالبًا ما يتم تجاهلها هي الحالة التي لا يكون فيها مربّع الاختيار
محدَّدًا أو غير محدَّد. هذه حالة ممتعة، وهي أسلوب ترحيب غير متكلف. نذكّرك بأنّه يمكن أن تتضمّن الحالات المنطقية حالات غامضة بين الحالات.
من الصعب ضبط مربّع اختيار على "غير محدّد"، ولا يمكن ضبطه إلا باستخدام JavaScript:
<label for="switch-indeterminate" class="gui-switch">
Indeterminate
<input type="checkbox" role="switch" id="switch-indeterminate">
<script>document.getElementById('switch-indeterminate').indeterminate = true</script>
</label>
بما أنّ الحالة تبدو لي متواضعة وداعمة، رأيت أنّه من المناسب وضع موضع إصبع الإبهام للتبديل في المنتصف:
.gui-switch > input:indeterminate {
--thumb-position: calc(
calc(calc(var(--track-size) / 2) - calc(var(--thumb-size) / 2))
* var(--isLTR)
);
}
تمرير مؤشر الماوس
يجب أن توفّر تفاعلات التمرير فوق العناصر دعمًا مرئيًا لواجهة المستخدم المرتبطة، ويجب أن ترشدك أيضًا إلى واجهة المستخدم التفاعلية. يُبرز هذا المفتاح الإبهام باستخدام حلقة شبه شفافة عند تمرير مؤشر الماوس فوق التصنيف أو الإدخال. يقدّم هذا المؤثر المتحرك عند التمرير فوق العنصر توجيهًا نحو عنصر الصورة المصغّرة التفاعلي.
يتم تطبيق تأثير "التمييز" باستخدام box-shadow
. عند تمرير مؤشر الماوس فوق حقل إدخال غير مُعطَّل، يجب زيادة حجم --highlight-size
. إذا كان المستخدم لا يمانع استخدام الصور المتحركة، ننقل الرمز box-shadow
ونراقب نموه. وإذا كان المستخدم لا يوافق على استخدام الصور المتحركة، يظهر التمييز على الفور:
.gui-switch > input::before {
box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);
@media (--motionOK) { & {
transition:
transform var(--thumb-transition-duration) ease,
box-shadow .25s ease;
}}
}
.gui-switch > input:not(:disabled):hover::before {
--highlight-size: .5rem;
}
JavaScript
بالنسبة إليّ، قد تبدو واجهة التبديل غريبة في محاولتها محاكاة واجهة حسية، خاصةً هذا النوع الذي يتضمّن دائرة داخل مسار. لقد عالج نظام التشغيل iOS هذا الأمر بشكلٍ جيد باستخدام مفتاح التبديل، حيث يمكنك سحبه من جانب إلى آخر، ويُسعدني للغاية توفّر هذا الخيار. في المقابل، قد يبدو عنصر واجهة المستخدم غير نشط إذا تم محاولة استخدام لفتة سحب ولم يحدث شيء.
الإبهام القابل للسحب
يتلقّى العنصر النائب للمؤشر موضعَه من .gui-switch > input
var(--thumb-position)
الذي يحدّد النطاق، ويمكن أن يوفّر JavaScript قيمة نمط مضمّنة في
الإدخال لتعديل موضع المؤشر ديناميكيًا بحيث يبدو أنّه يتّبع
إيماءة المؤشر. عند إزالة المؤشر، أزِل الأنماط المضمّنة وحدد ما إذا كان السحب أقرب إلى إيقاف أو تشغيل باستخدام السمة المخصّصة
--thumb-position
. هذا هو العمود الفقري للحلّ، وهو أحداث المؤشر
التي تتبّع بشكل مشروط مواضع المؤشر لتعديل السمات المخصّصة لـ CSS.
بما أنّ المكوّن كان يعمل بشكل كامل قبل ظهور هذا النص البرمجي، يتطلّب الحفاظ على السلوك الحالي قدرًا كبيرًا من العمل، مثل النقر على تصنيف لتبديل الإدخال. يجب ألا تضيف JavaScript ميزات على حساب الميزات الحالية.
touch-action
السحب هو إيماءة مخصّصة، ما يجعله مرشحًا رائعًا للاستفادة من مزايا
touch-action
. في حال هذا التبديل، يجب أن يعالج النص البرمجي لدينا إيماءة أفقية، أو أن يتم تسجيل إيماءة عمودية لإصدار التبديل العمودي. باستخدام touch-action
، يمكننا إخبار المتصفّح بالإيماءات التي يجب معالجتها في
هذا العنصر، حتى يتمكّن نص برمجي من معالجة إيماءة بدون تداخل.
يوجّه ملف CSS التالي المتصفّح إلى أنّه عند بدء إيماءة مؤشر من داخل مسار التبديل هذا، يجب معالجة الإيماءات الرأسية وعدم اتّخاذ أي إجراء بشأن الإيماءات الأفقية:
.gui-switch > input {
touch-action: pan-y;
}
النتيجة المطلوبة هي إيماءة أفقية لا تؤدي أيضًا إلى تمرير الصفحة أو تنقلها. يمكن أن ينتقل المؤشر عموديًا من داخل الإدخال إلى الصفحة، ولكن يتم التعامل مع المؤشرات الأفقية بشكل مخصّص.
أدوات تنسيق قيم وحدات البكسل
عند الإعداد وأثناء السحب، يجب الحصول على قيم أرقام مختلفة تم احتسابها
من العناصر. تعرض دوال JavaScript التالية قيمًا محسوبة للبكسل
استنادًا إلى سمة CSS. ويتم استخدامه في نص الإعداد على النحو التالي:
getStyle(checkbox, 'padding-left')
.
const getStyle = (element, prop) => {
return parseInt(window.getComputedStyle(element).getPropertyValue(prop));
}
const getPseudoStyle = (element, prop) => {
return parseInt(window.getComputedStyle(element, ':before').getPropertyValue(prop));
}
export {
getStyle,
getPseudoStyle,
}
لاحظ أنّ window.getComputedStyle()
تقبل مَعلمة ثانية، وهي عنصر زائف للهدف. من الرائع أنّ JavaScript يمكنه قراءة العديد من القيم من العناصر، حتى من العناصر الزائفة.
dragging
هذه لحظة أساسية لمنطق السحب، وهناك بعض الأمور التي يجب ملاحظتها من معالِج أحداث الدالة:
const dragging = event => {
if (!state.activethumb) return
let {thumbsize, bounds, padding} = switches.get(state.activethumb.parentElement)
let directionality = getStyle(state.activethumb, '--isLTR')
let track = (directionality === -1)
? (state.activethumb.clientWidth * -1) + thumbsize + padding
: 0
let pos = Math.round(event.offsetX - thumbsize / 2)
if (pos < bounds.lower) pos = 0
if (pos > bounds.upper) pos = bounds.upper
state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
}
عنصر النص البرمجي الرئيسي هو state.activethumb
، وهي الدائرة الصغيرة التي يحدد موقعها النص البرمجي مع مؤشر. عنصر switches
هو Map()
حيث تكون
المفاتيح من النوع .gui-switch
والقيم هي الحدود والأحجام المخزّنة مؤقتًا التي تحافظ على فعالية
النص البرمجي. يتم التعامل مع النص من اليمين إلى اليسار باستخدام السمة المخصّصة نفسها
التي تستخدمها CSS وهي --isLTR
، ويمكن استخدامها لعكس المنطق ومواصلة
إتاحة النص من اليمين إلى اليسار. يُعدّ event.offsetX
مفيدًا أيضًا، لأنّه يحتوي على قيمة
فرقة مفيدة لتحديد موضع الإبهام.
state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
يضبط هذا السطر الأخير من CSS السمة المخصّصة المستخدَمة من قِبل عنصر Thumb. كان من الممكن أن يتمّ تبديل عملية تحديد القيمة هذه بمرور الوقت، ولكنّ حدث مؤشر سابق حدد مؤقتًا --thumb-transition-duration
على 0s
، ما أدى إلى إزالة ما كان يمكن أن يكون تفاعلًا بطيئًا.
dragEnd
للسماح للمستخدم بسحب العنصر بعيدًا خارج مفتاح التبديل ثم تركه، يجب تسجيل حدث نافذة عام:
window.addEventListener('pointerup', event => {
if (!state.activethumb) return
dragEnd(event)
})
أعتقد أنّه من المهم جدًا أن يتمتع المستخدم بحرية السحب بشكل غير محكم وأن تكون ال واجهة ذكية بما يكفي لمراعاة ذلك. لم يستغرق حلّ هذه المشكلة باستخدام هذا التبديل الكثير من الوقت، ولكنّه كان يتطلّب عناية دقيقة أثناء عملية التطوير.
const dragEnd = event => {
if (!state.activethumb) return
state.activethumb.checked = determineChecked()
if (state.activethumb.indeterminate)
state.activethumb.indeterminate = false
state.activethumb.style.removeProperty('--thumb-transition-duration')
state.activethumb.style.removeProperty('--thumb-position')
state.activethumb.removeEventListener('pointermove', dragging)
state.activethumb = null
padRelease()
}
اكتمل التفاعل مع العنصر، وحان وقت ضبط سمة الإدخال التي تم وضع علامة عليها
وإزالة جميع أحداث الإيماءات. تم تغيير مربّع الاختيار باستخدام
state.activethumb.checked = determineChecked()
.
determineChecked()
تحدّد هذه الدالة التي تستدعيها dragEnd
موضع إصبع الإبهام الحالي
ضمن حدود مساره وتعرض القيمة true إذا كان يساوي
نصف المسار أو أكثر:
const determineChecked = () => {
let {bounds} = switches.get(state.activethumb.parentElement)
let curpos =
Math.abs(
parseInt(
state.activethumb.style.getPropertyValue('--thumb-position')))
if (!curpos) {
curpos = state.activethumb.checked
? bounds.lower
: bounds.upper
}
return curpos >= bounds.middle
}
ملاحظات إضافية
أدّت إيماءة السحب إلى بعض المشاكل في الرمز البرمجي بسبب بنية HTML الأولية
التي تم اختيارها، ولعلّ أبرزها تضمين الإدخال في تصنيف. سيتلقّى التصنيف، بصفته عنصرًا родительским
، تفاعلات النقر بعد الإدخال. في نهاية حدث
dragEnd
، ربما لاحظت أنّ padRelease()
هي دالّة
تبدو غريبة.
const padRelease = () => {
state.recentlyDragged = true
setTimeout(_ => {
state.recentlyDragged = false
}, 300)
}
يُعدّ ذلك لتوضيح أنّ التصنيف يتلقّى هذا النقر لاحقًا، لأنّه سيلغي وضع علامة في المربّع بجانب التفاعل الذي أجراه المستخدِم أو سيضع علامة فيه.
إذا أردت إجراء ذلك مرة أخرى، قد أفكر في تعديل DOM باستخدام JavaScript أثناء ترقية تجربة المستخدم، وذلك لإنشاء عنصر يعالج النقرات على التصنيفات بنفسه ولا يتعارض مع السلوك المضمّن.
إنّ هذا النوع من JavaScript هو الأقلّ تفضيلًا بالنسبة إليّ، ولا أريد إدارة تدفّق الأحداث الشَرطية:
const preventBubbles = event => {
if (state.recentlyDragged)
event.preventDefault() && event.stopPropagation()
}
الخاتمة
لقد كان مكوّن التبديل الصغير هذا هو الأكثر استهلاكًا للوقت من بين جميع تحدّيات واجهتها في واجهة المستخدم حتى الآن. الآن بعد أن عرفت كيف فعلت ذلك، كيف ستفعل ذلك؟ 🙂
لننوّع أساليبنا ونتعرّف على جميع الطرق لإنشاء تطبيقات على الويب. أنشئ عرضًا توضيحيًا وأرسِل إلينا رابطًا على Twitter، وسنضيفه إلى قسم الريمكسات التي أنشأها المستخدمون أدناه.
الريمكسات التي أنشأها المستخدمون
- @KonstantinRouda باستخدام عنصر مخصّص: demo وcode.
- @jhvanderschee باستخدام زر: Codepen.
الموارد
يمكنك العثور على .gui-switch
الرمز المصدر على
GitHub.