انتخاب‌گر پویانمایی‌شده

‫API انتخاب سفارشی‌سازی‌پذیر روشی اعلانی و مبتنی بر 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 برای تعریف سبک‌های پایه استفاده کنید که مرورگر باید قبل‌از شروع انتقال محاسبه کند. برای مثال، اگر می‌خواهید به‌تدریج ظاهر شود، میزان ماتی را در بلوک @starting-style روی 0 تنظیم کنید.
  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> نادیده بگیرند یا آن‌ها را نامعتبر تلقی کنند. اگر نوشتار استاندارد را به‌عنوان جایگزین خوانا درنظر بگیرید، برای ارتقای پیش‌رو به شبیه‌سازهای مرورگر جاوا اسکریپت سنگین نیازی نیست.
document.addEventListener("DOMContentLoaded", () => {
  // Check if browser supports base-select value
  if (!CSS.supports("appearance", "base-select")) {
    // Custom select overrides are not supported natively.
  }
});