إنشاء أحد مكونات المبدل

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

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

عرض توضيحي

إليك نسخة من هذه المشاركة على YouTube إذا كنت تفضّل ذلك:

نظرة عامة

يعمل مفتاح التحكّم بشكل مشابه لمربّع الاختيار. ولكنه يمثل بشكل صريح الحالات المنطقية للتفعيل والإيقاف.

يستخدم هذا الإصدار التجريبي "<input type="checkbox" role="switch">" في معظم أقسامه والتي تمتاز بعدم الحاجة إلى استخدام CSS أو JavaScript تعمل بكامل طاقتها ويمكن الوصول إليها. تحميل صفحات الأنماط المتتالية (CSS) تدعم الاتجاهات من اليمين إلى اليسار واللغات والوضع العمودي والرسوم المتحركة وغيرها. تحميل JavaScript يجعل عملية التبديل ملموسة وقابلة للسحب.

الخصائص المخصصة

تمثل المتغيرات التالية الأجزاء المختلفة من المبدل (switch) الخيارات. كفئة المستوى الأعلى، تحتوي .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 على الخصائص المخصصة الخاصة والعامة التي يستخدمها الأطفال لحساب وتخطيطات.

أدوات Flexbox DevTools متراكبة على تصنيف أفقي ومفتاح تحكّم لعرض التصميم
توزيع المساحة.

.gui-switch {
  display: flex;
  align-items: center;
  gap: 2ch;
  justify-content: space-between;
}

يشبه تمديد وتعديل تخطيط flexbox تغيير أي تخطيط flexbox. على سبيل المثال، لوضع التصنيفات أعلى أو أسفل مفتاح التحويل، أو لتغيير flex-direction:

أدوات Flexbox DevTools متراكبة على تصنيف عمودي ومفتاح تحكّم

<label for="light-switch" class="gui-switch" style="flex-direction: column">
  Default
  <input type="checkbox" role="switch" id="light-switch">
</label>

مسار

يتم نمط إدخال مربّع الاختيار كمسار تبديل من خلال إزالة appearance: checkbox وتقديم حجمها الخاص بدلاً من ذلك:

&quot;أدوات مطوّري البرامج في الشبكة&quot; تظهر على سطح مسار التبديل وتعرض مسار الشبكة المُسمّى
المناطق التي لها اسم &quot;track&quot;.

.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 أيضًا علامة الاختيار المرئية المقدمة من المتصفح. يستخدم هذا المكون العنصر الزائف و:checked من الفئة الزائفة على المدخل استبدال هذا المؤشر المرئي.

الإبهام هو عنصر ثانوي تابع للعنصر input[type="checkbox"] حزم فوق المقطع الصوتي بدلاً من أسفله من خلال المطالبة بمنطقة الشبكة track:

أدوات مطوّري البرامج تعرض رمز العنصر الزائف بأنّه موضوع داخل شبكة CSS

.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، والإبهام العنصر الزائف يستخدمه كموضع 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)
  );
}

اعتقدت أن هذه التوليفة المنفصلة تسير على ما يرام. عنصر الإبهام هو معنيّة بنمط واحد فقط، موضع translateX. يمكن للمدخل إدارة جميع التعقيد والعمليات الحسابية.

موضع الإعلان

تم تنفيذ الدعم باستخدام فئة مفتاح التعديل -vertical التي تضيف طريقة تدوير يتحول CSS إلى العنصر input.

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

الولايات

لن تكتمل استخدام ميزة "input[type="checkbox"]" المدمجة بدون نتعامل مع الحالات المختلفة التي يمكن أن تكون فيها: :checked و:disabled ":indeterminate" و":hover" تم ترك ":focus" بمفرده، مع وجود تعديل تم إجراؤه على إزاحته فقط كانت حلقة التركيز رائعة على Firefox متصفح Safari:

لقطة شاشة لحلقة التركيز تركّز على مفتاح تبديل في Firefox وSafari.

محدد

<label for="switch-checked" class="gui-switch">
  Default
  <input type="checkbox" role="switch" id="switch-checked" checked="true">
</label>

تمثِّل هذه الولاية الولاية on. في هذه الحالة، سيتم نقل الإدخال "track" الخلفية على اللون النشط وتم تعيين موضع الإبهام على " النهاية".

.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 مختلفًا من الناحية المرئية فحسب، بل يُظهر أيضًا عنصر immutable.عدم قابلية التغيير للتفاعل يكون مجانيًا من المتصفح، ولكن تحتاج الحالات المرئية إلى أنماط بسبب استخدام 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

بالنسبة لي، قد تبدو واجهة المبدل (switch) غريبة في محاولتها محاكاة واجهة مستخدم وواجهة خاصة، خاصة من هذا النوع التي تحتوي على دائرة داخل المسار. لقد ساعد نظام iOS هذا الإجراء بشكل صحيح. بمبدلها، ويمكنك سحبها جنبًا إلى جنب، ويكون الأمر مُرضيًا للغاية يكون لديك الخيار. وعلى العكس، يمكن أن يبدو عنصر واجهة المستخدم غير نشط إذا كانت إيماءة السحب حاول ولم يحدث شيء.

يمكن سحب الإبهام

يتلقّى العنصر الزائف موقع الإبهام من .gui-switch > input ذات نطاق var(--thumb-position)، يمكن لـ JavaScript توفير قيمة نمط مضمَّنة الإدخال لتعديل موضع الإبهام ديناميكيًا وجعله يبدو تابعًا إيماءة المؤشر. عند تحرير المؤشر، قم بإزالة الأنماط المضمنة تحديد ما إذا كان السحب أقرب إلى الإيقاف أو التشغيل باستخدام الخاصية المخصصة --thumb-position هذه هي العمود الفقري للحل؛ أحداث المؤشر تتبُّع مواضع المؤشر بشكل مشروط لتعديل خصائص CSS المخصّصة

بما أنّ المكوِّن كان يعمل بنسبة 100% قبل عرض هذا النص البرمجي. يتطلب الأمر قدرًا كبيرًا من العمل للحفاظ على السلوك الحالي، مثل النقر على التصنيف لتبديل الإدخال. يجب ألا تضيف لغة 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-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()
}

الخاتمة

أصبح مكون التبديل هذا للمراهقين الأكثر صعوبة بين جميع تحديات واجهة المستخدم الرسومية حتى الآن! الآن بعد أن تعرّفت على كيفية إجراء ذلك، كيف يمكنك‽ 🙂

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

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

الموارد

ابحث عن .gui-switch رمز المصدر على GitHub.