Настраиваемый API выбора предлагает декларативный способ анимации элементов <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, чтобы применить к индикатору стрелки переходы (например, поворот или перемещение).
Пример кода: плавное масштабирование и затухание при выборе
В следующем примере показан настраиваемый элемент select, оформленный с использованием стандартных анимаций страницы для контейнера средства выбора.
<!-- 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отменяет стандартное поведение браузера, при котором размер элемента select определяется по ширине самого длинного варианта. Чтобы предотвратить смещение макета, может потребоваться задать фиксированную ширину или использовать ограничения flex/grid. - Убедитесь, что у элемента
<select>есть атрибутnameи связанный с ним элемент<label>. Это гарантирует, что даже при наличии пользовательского интерфейса компонент останется доступным для программ чтения с экрана и будет корректно работать со стандартными отправками форм. - Используйте стандартные атрибуты
valueдля вариантов, чтобы отправка формы работала так же, как и раньше.
Резервные стратегии
Резервные объявления и поддержка браузеров для настраиваемых <select>
В браузерах, которые пока не поддерживают appearance: base-select, элемент <select> преобразуется в стандартный раскрывающийся список операционной системы.
- Игнорирование нетекстового контента. Старые браузеры удаляют HTML-теги (например,
<svg>или<div>) внутри тегов<option>и отображают только текстовые узлы. Убедитесь, что текстовый контент элемента<option>понятен и имеет смысл сам по себе. - Обработка структуры HTML. Стандартные синтаксические анализаторы могут игнорировать теги
<button>и<selectedcontent>внутри<select>или считать их недопустимыми. Если вы рассматриваете стандартный текст как резервный вариант, то для прогрессивного улучшения не требуются тяжелые полифилы JavaScript.
document.addEventListener("DOMContentLoaded", () => {
// Check if browser supports base-select value
if (!CSS.supports("appearance", "base-select")) {
// Custom select overrides are not supported natively.
}
});