ה-API של רכיבי select שניתנים להתאמה אישית מאפשר להוסיף אנימציה לרכיבי <select> ולתפריטים הנפתחים שלהם באמצעות CSS. כשמשלבים את appearance: base-select עם טכניקות מודרניות של אנימציה ב-CSS, כמו @starting-style והתנהגות המעבר allow-discrete, אפשר ליצור מעברים חלקים ואיכותיים בממשק המשתמש של רכיבים בשכבה העליונה בלי להסתמך על ספריות JavaScript כבדות.
בעבר, אי אפשר היה להוסיף אנימציה לתפריטים נפתחים מקוריים של בחירה כי ממשק המשתמש שלהם עבר רינדור מחוץ למגבלות של אזור התצוגה הנגיש. בעזרת appearance: base-select, אפשר לעצב את בורר הצבעים ולהוסיף לו אנימציה כמו לכל מרכיב דף אחר.
איך להטמיע
כדי להטמיע בורר אנימציה:
- הבעת הסכמה להתאמה אישית: מחילים את
appearance: base-selectגם על הרכיב<select>וגם על הרכיב המדומה::picker(select). - הפעלת מעברים עם שינוי גודל אוטומטי (אופציונלי): מגדירים את
interpolate-size: allow-keywords(בדרך כלל ב-:root) כדי לאפשר לדפדפן לעבור בין ערכי מדדים נפרדים כמוheight: autoו-height: 0. - הוספת אנימציה לקונטיינר של השכבה העליונה: מחילים סגנונות כניסה/יציאה רגילים על
::picker(select). כדי לוודא שהמעבר של האטימוּת פועל כשעוברים ביןdisplay: noneל-display: block, צריך להשתמש ב-transition-behavior: allow-discrete(לרוב נכתב בתוך השורה כ-transition: display 0.3s allow-discrete). - התחברות למצב הפתיחה באמצעות
@starting-style: משתמשים ב-@starting-styleכדי להגדיר את סגנונות הבסיס שהדפדפן צריך לחשב לפני שהמעבר מתחיל. לדוגמה, אם רוצים שהאפקט יהיה הדרגתי, מגדירים את האטימות ל-0בתוך הבלוק@starting-style. - סיבוב הסמל: משתמשים בבוררים פעילים או בבוררי מיקוד של רכיבי פסאודו כמו
: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: מנתחי 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.
}
});