أداة اختيار الصور المتحركة

توفّر واجهة برمجة التطبيقات القابلة للتخصيص طريقة تعريفية مستندة إلى CSS لتحريك عناصر <select> وأدوات اختيار القوائم المنسدلة. من خلال الجمع بين appearance: base-select وأساليب CSS الحديثة للحركة، مثل @starting-style وسلوك الانتقال allow-discrete، يمكنك إنشاء انتقالات سلسة ومميزة لواجهة المستخدم لعناصر الطبقة العليا بدون الاعتماد على مكتبات JavaScript الكبيرة.

في السابق، كان من المستحيل تحريك القوائم المنسدلة الأصلية لأنّ واجهة المستخدم الخاصة بها كانت تُعرض خارج قيود إطار العرض الذي يمكن الوصول إليه. باستخدام appearance: base-select، تصبح أداة الاختيار قابلة للتنسيق والتحريك مثل أي عنصر صفحة آخر.

كيفية الإعداد

لتنفيذ أداة اختيار متحركة:

  1. الموافقة على التخصيص: طبِّق السمة appearance: base-select على كلّ من العنصر <select> والعنصر الزائف ::picker(select).
  2. تفعيل انتقالات تغيير الحجم تلقائيًا (اختياري): حدِّد interpolate-size: allow-keywords (عادةً في :root) للسماح للمتصفّح بالانتقال بين قيم المقاييس المنفصلة، مثل height: auto وheight: 0.
  3. تحريك حاوية الطبقة العليا: يمكنك تطبيق أنماط الدخول/الخروج العادية على ::picker(select). للتأكّد من عمل تأثير الانتقال في مستوى التعتيم عند الانتقال بين display: none وdisplay: block، يجب استخدام transition-behavior: allow-discrete (غالبًا ما تتم كتابته مضمّنًا على النحو التالي: transition: display 0.3s allow-discrete).
  4. الربط بالحالة الافتتاحية باستخدام @starting-style: استخدِم @starting-style لتحديد الأنماط الأساسية التي يجب أن يحسبها المتصفّح قبل بدء عملية الانتقال. على سبيل المثال، إذا أردت أن يظهر النص تدريجيًا، اضبط مستوى التعتيم على 0 داخل الكتلة @starting-style.
  5. تدوير الرمز: استخدِم محدّدات التركيز أو المحدّدات النشطة للعناصر الزائفة، مثل :open::picker-icon، لتطبيق تأثيرات الانتقال (مثل التدوير أو الترجمة) على مؤشر السهم.

مثال على الرمز البرمجي: التحديد السلس للحجم والتلاشي

يوضّح المثال التالي قائمة اختيار مخصّصة تم تصميمها باستخدام تأثيرات متحركة عادية للصفحة لحاوية أداة الاختيار.

<!-- Always use a <label> linked via 'for' to the select for accessibility -->
<label for="theme-select">Visual Theme</label>
<select id="theme-select" class="animated-select" name="theme">
  <!-- The <button> inside <select> becomes the visible trigger when appearance: base-select is used -->
  <button>
    <!-- <selectedcontent> automatically displays the content of the chosen <option> -->
    <selectedcontent></selectedcontent>
  </button>
  <option value="system">
    <!-- MANDATORY: Decorative inline SVGs MUST set aria-hidden="true" to prevent redundant screen reader announcement -->
    <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
      <rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
      <line x1="8" y1="21" x2="16" y2="21"></line>
      <line x1="12" y1="17" x2="12" y2="21"></line>
    </svg>
    System Default
  </option>
  <option value="light">
    <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
      <circle cx="12" cy="12" r="5"></circle>
      <line x1="12" y1="1" x2="12" y2="3"></line>
      <line x1="12" y1="21" x2="12" y2="23"></line>
      <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
      <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
      <line x1="1" y1="12" x2="3" y2="12"></line>
      <line x1="21" y1="12" x2="23" y2="12"></line>
      <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
      <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
    </svg>
    Light UI
  </option>
</select>
/* Opt-in to customizable select */
.animated-select,
.animated-select::picker(select) {
  appearance: base-select;
}

/* Enable auto-keyword transitions (usually set globally at :root) */
:root {
  interpolate-size: allow-keywords;
}

/* Style the visible trigger and icon rotation */
.animated-select {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 0.875rem 1rem;
  font-size: 1rem;
  border-radius: 8px;
  cursor: pointer;
  transition: border-color 0.2s ease;
}

.animated-select::picker-icon {
  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}

.animated-select:open::picker-icon {
  transform: rotate(180deg);
}

/*
 * The Picker Container
 * Uses top-layer animations with `allow-discrete` visibility hooks
 */
.animated-select::picker(select) {
  background: white;
  border-radius: 12px;
  box-shadow: 0 10px 25px -3px rgba(0,0,0,0.1);
  padding: 0.5rem;
  margin-top: 0.25rem;
  width: anchor-size(width);
  overflow: hidden;

  /* The crucial transition setting for popover animations */
  transition:
    display 0.4s allow-discrete,
    overlay 0.4s allow-discrete,
    opacity 0.4s cubic-bezier(0.16, 1, 0.3, 1),
    height 0.4s cubic-bezier(0.16, 1, 0.3, 1);
  opacity: 0;
  height: 0;
}

/* Open State */
.animated-select:open::picker(select) {
  opacity: 1;
  height: auto;
}

/* @starting-style to hook the transition on initial popover open */
@starting-style {
  .animated-select:open::picker(select) {
    opacity: 0;
    height: 0;
  }
}

/* Support for SVG inside Options and Selected Content */
.animated-select option svg,
.animated-select selectedcontent svg {
  flex-shrink: 0; /* Prevent icons from shrinking */
  width: 1.25rem;
  height: 1.25rem;
}

/* MANDATORY: Provide multiple indicators (e.g. bold font and distinct background) for the checked state to avoid color-only state communication */
.animated-select option:checked {
  font-weight: 700;
  background-color: #f1f5f9;
}

/* Ensure copy-paste safety for users with motion sensitivities */
@media (prefers-reduced-motion: reduce) {
  .animated-select::picker(select),
  .animated-select::picker-icon {
    transition: none !important;
  }
}

التنفيذ الاستراتيجي وأفضل الممارسات

  • يُفضَّل استخدام @starting-style عندما تريد أن تبدأ الحركات بالضبط عند انتقال عنصر من الحالة display: none إلى الحالة المرئية.
  • لا تستخدِم قفل التمرير المخصّص. يجب أن تسمح عناصر الطبقة العليا التي يديرها "base-select" بسلوكيات الإغلاق الطبيعية للخلفية.
  • يجب التحقّق من إعدادات تقليل الحركة المفضّلة. يجب دائمًا تضمين قيود الصور المتحركة في الاستعلام عن الوسائط prefers-reduced-motion لضمان توفير بيئات ملائمة للأشخاص الذين يعانون من دوار الحركة.
  • اختبار سلوك التنسيق يؤدي ضبط السمة appearance: base-select إلى إزالة السلوك التلقائي للمتصفّح المتمثّل في تحديد حجم القائمة المنسدلة استنادًا إلى عرض أطول خيار فيها. قد تحتاج إلى ضبط عرض ثابت أو استخدام قيود flex/grid لمنع متغيّرات التصميم.
  • تأكَّد من أنّ <select> يتضمّن سمة name و<label> مرتبطة بها. يضمن ذلك أن يظلّ المكوّن متاحًا لبرامج قراءة الشاشة ويعمل بشكل صحيح مع عمليات إرسال النماذج العادية، حتى مع واجهة مستخدم مخصّصة.
  • استخدِم سمات value العادية في الخيارات لضمان عمل عملية إرسال النموذج تمامًا كما في السابق.

استراتيجيات احتياطية

<select> القابل للتخصيص: البدائل والمتصفّحات المتوافقة

بالنسبة إلى المتصفّحات التي لا تتيح استخدام appearance: base-select بعد، يتم الرجوع إلى استخدام عنصر <select> كقائمة منسدلة عادية في نظام التشغيل.

  • تجاهل المحتوى غير النصي: تزيل المتصفحات القديمة علامات HTML (مثل <svg> أو <div>) داخل علامات <option> وتعرض فقط عُقد النص. تأكَّد من أنّ المحتوى النصي لـ <option> واضح ومفهوم بحد ذاته.
  • التعامل مع بنية HTML: قد تتجاهل المحللات اللغوية العادية علامتَي <button> و<selectedcontent> داخل <select> أو تعتبرهما غير صالحتَين. لا يلزم استخدام polyfills لـ JavaScript ثقيلة من أجل التحسين التدريجي إذا كنت تعرض نصًا عاديًا كبديل قابل للقراءة.
document.addEventListener("DOMContentLoaded", () => {
  // Check if browser supports base-select value
  if (!CSS.supports("appearance", "base-select")) {
    // Custom select overrides are not supported natively.
  }
});