یک نمای کلی از نحوه ساخت یک جزء کلیدی قابل دسترسی.
در این پست میخواهم فکری را در مورد روشی برای ساخت دکمه تقسیم به اشتراک بگذارم. نسخه ی نمایشی را امتحان کنید .
اگر ویدیو را ترجیح می دهید، در اینجا یک نسخه YouTube از این پست وجود دارد:
نمای کلی
دکمه های تقسیم دکمه هایی هستند که یک دکمه اصلی و لیستی از دکمه های اضافی را پنهان می کنند. آنها برای افشای یک عمل معمولی مفید هستند، در حالی که اقدامات ثانویه و کمتر مورد استفاده را تا زمانی که نیاز باشد، تودرتو میکنند. یک دکمه تقسیم می تواند برای کمک به یک طراحی شلوغ بسیار مهم باشد. یک دکمه تقسیم پیشرفته حتی ممکن است آخرین اقدام کاربر را به خاطر بسپارد و آن را به موقعیت اصلی ارتقا دهد.
یک دکمه تقسیم مشترک را می توان در برنامه ایمیل شما پیدا کرد. اقدام اصلی ارسال است، اما شاید بتوانید بعداً ارسال کنید یا به جای آن یک پیش نویس ذخیره کنید:
منطقه اقدام مشترک خوب است، زیرا کاربر نیازی به نگاه کردن به اطراف ندارد. آنها می دانند که اقدامات ضروری ایمیل در دکمه تقسیم وجود دارد.
قطعات
اجازه دهید قبل از بحث در مورد هماهنگی کلی و تجربه کاربر نهایی، بخشهای اساسی یک دکمه تقسیم را بشکنیم. ابزار بازرسی دسترسی VisBug در اینجا برای کمک به نمایش یک نمای کلان از مؤلفه، نمایاندن جنبههای HTML، سبک و دسترسی برای هر بخش اصلی استفاده میشود.
ظرف دکمه تقسیم سطح بالا
کامپوننت بالاترین سطح یک فلکس باکس درون خطی است، با یک کلاس از gui-split-button
، که شامل اکشن اصلی و .gui-popup-button
است.
دکمه اکشن اولیه
<button>
قابل رویت و قابل فوکوس اولیه در ظرف با دو شکل گوشه منطبق برای فوکوس ، شناور و فعل و انفعالات فعال قرار می گیرد تا در داخل .gui-split-button
ظاهر شوند.
دکمه جابجایی پاپ آپ
عنصر پشتیبانی "دکمه پاپ آپ" برای فعال کردن و اشاره به لیست دکمه های ثانویه است. توجه داشته باشید که یک <button>
نیست و قابل فوکوس نیست. با این حال، لنگر موقعیت یابی برای .gui-popup
و میزبان برای :focus-within
برای ارائه پنجره بازشو استفاده می شود.
کارت پاپ آپ
این یک کارت شناور به .gui-popup-button
لنگر آن است.
اقدام(های) ثانویه
یک <button>
قابل فوکوس با اندازه فونت کمی کوچکتر از دکمه عمل اصلی دارای یک نماد و یک سبک مکمل برای دکمه اصلی است.
خواص سفارشی
متغیرهای زیر به ایجاد هماهنگی رنگ و یک مکان مرکزی برای تغییر مقادیر استفاده شده در سراسر مؤلفه کمک می کنند.
@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --light (prefers-color-scheme: light);
.gui-split-button {
--theme: hsl(220 75% 50%);
--theme-hover: hsl(220 75% 45%);
--theme-active: hsl(220 75% 40%);
--theme-text: hsl(220 75% 25%);
--theme-border: hsl(220 50% 75%);
--ontheme: hsl(220 90% 98%);
--popupbg: hsl(220 0% 100%);
--border: 1px solid var(--theme-border);
--radius: 6px;
--in-speed: 50ms;
--out-speed: 300ms;
@media (--dark) {
--theme: hsl(220 50% 60%);
--theme-hover: hsl(220 50% 65%);
--theme-active: hsl(220 75% 70%);
--theme-text: hsl(220 10% 85%);
--theme-border: hsl(220 20% 70%);
--ontheme: hsl(220 90% 5%);
--popupbg: hsl(220 10% 30%);
}
}
طرح بندی و رنگ
نشانه گذاری
عنصر به صورت <div>
با نام کلاس سفارشی شروع می شود.
<div class="gui-split-button"></div>
دکمه اصلی و عناصر .gui-popup-button
اضافه کنید.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions"></span>
</div>
به ویژگی های aria aria-haspopup
و aria-expanded
توجه کنید. این نشانه ها برای خوانندگان صفحه نمایش برای آگاهی از قابلیت و وضعیت تجربه دکمه تقسیم بسیار مهم است. ویژگی title
برای همه مفید است.
یک نماد <svg>
و عنصر کانتینر .gui-popup
اضافه کنید.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup"></ul>
</span>
</div>
برای قرار دادن پنجره بازشو ساده، .gui-popup
فرزندی برای دکمه ای است که آن را گسترش می دهد. تنها موردی که در این استراتژی وجود دارد این است که ظرف .gui-split-button
نمی تواند overflow: hidden
، زیرا پنجره بازشو را از حضور بصری حذف می کند.
یک <ul>
پر از محتویات <li><button>
خود را بهعنوان یک «لیست دکمهها» به صفحهخوانها اعلام میکند، که دقیقاً رابطی است که ارائه میشود.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup">
<li>
<button>Schedule for later</button>
</li>
<li>
<button>Delete</button>
</li>
<li>
<button>Save draft</button>
</li>
</ul>
</span>
</div>
برای جذابیت و لذت بردن از رنگ، آیکون هایی را به دکمه های ثانویه از https://heroicons.com اضافه کرده ام. نمادها برای هر دو دکمه اصلی و ثانویه اختیاری هستند.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup">
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
Schedule for later
</button></li>
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
Delete
</button></li>
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
</svg>
Save draft
</button></li>
</ul>
</span>
</div>
سبک ها
با وجود HTML و محتوا، سبک ها آماده ارائه رنگ و طرح بندی هستند.
حالت دادن به ظرف دکمه تقسیم
یک نوع نمایشگر inline-flex
برای این جزء بستهبندی به خوبی کار میکند، زیرا باید با سایر دکمهها، اقدامات یا عناصر تقسیمبندی مطابقت داشته باشد.
.gui-split-button {
display: inline-flex;
border-radius: var(--radius);
background: var(--theme);
color: var(--ontheme);
fill: var(--ontheme);
touch-action: manipulation;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
یک ظاهر طراحی شده <button>
دکمه ها در پنهان کردن مقدار کد مورد نیاز بسیار خوب هستند. ممکن است لازم باشد سبکهای پیشفرض مرورگر را لغو یا جایگزین کنید، اما همچنین باید مقداری وراثت را اعمال کنید، حالتهای تعامل را اضافه کنید و با تنظیمات برگزیده و انواع ورودی کاربر سازگار شوید. سبک های دکمه ها به سرعت جمع می شوند.
این دکمهها با دکمههای معمولی متفاوت هستند، زیرا یک پسزمینه با یک عنصر والد به اشتراک میگذارند. معمولاً یک دکمه دارای پسزمینه و رنگ متن است. با این حال، اینها آن را به اشتراک می گذارند و تنها پس زمینه خود را در تعامل اعمال می کنند.
.gui-split-button button {
cursor: pointer;
appearance: none;
background: none;
border: none;
display: inline-flex;
align-items: center;
gap: 1ch;
white-space: nowrap;
font-family: inherit;
font-size: inherit;
font-weight: 500;
padding-block: 1.25ch;
padding-inline: 2.5ch;
color: var(--ontheme);
outline-color: var(--theme);
outline-offset: -5px;
}
اضافه کردن حالت های تعامل با چند کلاس شبه CSS و استفاده از ویژگی های سفارشی منطبق برای حالت:
.gui-split-button button {
…
&:is(:hover, :focus-visible) {
background: var(--theme-hover);
color: var(--ontheme);
& > svg {
stroke: currentColor;
fill: none;
}
}
&:active {
background: var(--theme-active);
}
}
دکمه اصلی برای تکمیل جلوه طراحی به چند سبک خاص نیاز دارد:
.gui-split-button > button {
border-end-start-radius: var(--radius);
border-start-start-radius: var(--radius);
& > svg {
fill: none;
stroke: var(--ontheme);
}
}
در نهایت، برای کمی استعداد، دکمه و نماد تم روشن سایه مییابند:
.gui-split-button {
@media (--light) {
& > button,
& button:is(:focus-visible, :hover) {
text-shadow: 0 1px 0 var(--theme-active);
}
& > .gui-popup-button > svg,
& button:is(:focus-visible, :hover) > svg {
filter: drop-shadow(0 1px 0 var(--theme-active));
}
}
}
یک دکمه عالی به ریز تعاملات و جزئیات کوچک توجه کرده است.
نکته ای در مورد :focus-visible
توجه کنید که چگونه استایل های دکمه از :focus-visible
به جای :focus
استفاده می کنند. :focus
برای ایجاد یک رابط کاربری قابل دسترسی بسیار مهم است، اما یک نقطه ضعف دارد: این که آیا کاربر باید آن را ببیند یا نه، هوشمندانه نیست، برای هر تمرکزی کاربرد دارد.
ویدئوی زیر تلاش میکند تا این ریز تعامل را از بین ببرد تا نشان دهد چگونه :focus-visible
یک جایگزین هوشمند است.
حالت دادن به دکمه بازشو
فلکس باکس 4ch
برای وسط یک نماد و لنگر انداختن لیست دکمه های بازشو. مانند دکمه اصلی، شفاف است تا زمانی که در غیر این صورت شناور شود یا با آن تعامل داشته باشد، و کشیده شود تا پر شود.
.gui-popup-button {
inline-size: 4ch;
cursor: pointer;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
border-inline-start: var(--border);
border-start-end-radius: var(--radius);
border-end-end-radius: var(--radius);
}
لایه در حالت شناور، فوکوس و فعال با CSS Nesting و انتخابگر تابعی :is()
:
.gui-popup-button {
…
&:is(:hover,:focus-within) {
background: var(--theme-hover);
}
/* fixes iOS trying to be helpful */
&:focus {
outline: none;
}
&:active {
background: var(--theme-active);
}
}
این سبک ها قلاب اصلی برای نمایش و پنهان کردن پنجره بازشو هستند. هنگامی که .gui-popup-button
روی هر یک از فرزندان خود focus
دارد، opacity
، موقعیت و pointer-events
روی نماد و پنجره بازشو تنظیم کنید.
.gui-popup-button {
…
&:focus-within {
& > svg {
transition-duration: var(--in-speed);
transform: rotateZ(.5turn);
}
& > .gui-popup {
transition-duration: var(--in-speed);
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
}
}
با تکمیل سبکهای درون و بیرون، آخرین قطعه بسته به ترجیح حرکت کاربر ، تبدیلها را به صورت شرطی تغییر میدهد :
.gui-popup-button {
…
@media (--motionOK) {
& > svg {
transition: transform var(--out-speed) ease;
}
& > .gui-popup {
transform: translateY(5px);
transition:
opacity var(--out-speed) ease,
transform var(--out-speed) ease;
}
}
}
با نگاه دقیق به کد متوجه میشوید که هنوز کدورت برای کاربرانی که حرکت کاهشیافته را ترجیح میدهند، تغییر میکند .
سبک دادن به پنجره بازشو
عنصر .gui-popup
یک لیست دکمه کارت شناور است که از ویژگیهای سفارشی و واحدهای نسبی استفاده میکند تا بهطور نامحسوس کوچکتر باشد، بهطور تعاملی با دکمه اصلی تطبیق داده شود، و در برند با استفاده از رنگ استفاده شود. توجه داشته باشید که نمادها کنتراست کمتری دارند، نازکتر هستند و سایه رنگ آبی کاملاً روی آن دیده میشود. مانند دکمه ها، UI و UX قوی نتیجه این جزئیات کوچک است.
.gui-popup {
--shadow: 220 70% 15%;
--shadow-strength: 1%;
opacity: 0;
pointer-events: none;
position: absolute;
bottom: 80%;
left: -1.5ch;
list-style-type: none;
background: var(--popupbg);
color: var(--theme-text);
padding-inline: 0;
padding-block: .5ch;
border-radius: var(--radius);
overflow: hidden;
display: flex;
flex-direction: column;
font-size: .9em;
transition: opacity var(--out-speed) ease;
box-shadow:
0 -2px 5px 0 hsl(var(--shadow) / calc(var(--shadow-strength) + 5%)),
0 1px 1px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 10%)),
0 2px 2px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 12%)),
0 5px 5px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 13%)),
0 9px 9px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 14%)),
0 16px 16px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 20%))
;
}
به نمادها و دکمهها رنگهای نام تجاری داده میشود تا در هر کارت با مضمون تیره و روشن به زیبایی استایل دهید:
.gui-popup {
…
& svg {
fill: var(--popupbg);
stroke: var(--theme);
@media (prefers-color-scheme: dark) {
stroke: var(--theme-border);
}
}
& button {
color: var(--theme-text);
width: 100%;
}
}
پنجره تم تیره دارای افزودنی های سایه متن و نماد، به علاوه یک سایه کادر کمی شدیدتر است:
.gui-popup {
…
@media (--dark) {
--shadow-strength: 5%;
--shadow: 220 3% 2%;
& button:not(:focus-visible, :hover) {
text-shadow: 0 1px 0 var(--ontheme);
}
& button:not(:focus-visible, :hover) > svg {
filter: drop-shadow(0 1px 0 var(--ontheme));
}
}
}
سبکهای آیکون <svg>
عمومی
همه نمادها با استفاده از واحد ch
بهعنوان inline-size
اندازهی نسبی به font-size
قلم دکمهای دارند که در آن استفاده میشود. همچنین به هر کدام از آنها سبک هایی داده شده است تا به نرم و صاف بودن آیکون ها کمک کنند.
.gui-split-button svg {
inline-size: 2ch;
box-sizing: content-box;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2px;
}
طرح بندی از راست به چپ
خواص منطقی تمام کارهای پیچیده را انجام می دهند. در اینجا لیستی از ویژگی های منطقی استفاده شده است: - display: inline-flex
یک عنصر انعطاف پذیر درون خطی ایجاد می کند. - padding-block
و padding-inline
به صورت جفتی، به جای padding
shorthand، از مزایای padding اضلاع منطقی بهره مند شوید. - border-end-start-radius
و دوستان بر اساس جهت سند، گوشه ها را گرد می کنند. - inline-size
به جای width
تضمین می کند که اندازه به ابعاد فیزیکی مرتبط نیست. - border-inline-start
یک حاشیه به شروع اضافه می کند که بسته به جهت اسکریپت ممکن است در سمت راست یا چپ باشد.
جاوا اسکریپت
تقریباً تمام جاوا اسکریپت زیر برای افزایش دسترسی است. دو تا از کتابخانههای کمکی من برای سادهتر کردن کارها استفاده میشوند. BlingBlingJS برای پرسشهای مختصر DOM و راهاندازی آسان شنونده رویداد استفاده میشود، در حالی که roving-ux کمک میکند تا تعاملات صفحه کلید و گیمپد در دسترس را برای پنجره بازشو تسهیل کند.
import $ from 'blingblingjs'
import {rovingIndex} from 'roving-ux'
const splitButtons = $('.gui-split-button')
const popupButtons = $('.gui-popup-button')
با وارد کردن کتابخانههای بالا و انتخاب عناصر و ذخیره شده در متغیرها، ارتقای تجربه چند تابع تا کامل شدن فاصله دارد.
شاخص روینگ
هنگامی که یک صفحهکلید یا صفحهخوان روی .gui-popup-button
متمرکز میشود، میخواهیم فوکوس را به اولین دکمه (یا اخیراً متمرکز شده) در .gui-popup
. منتقل کنیم. کتابخانه به ما کمک می کند تا این کار را با پارامترهای element
و target
انجام دهیم.
popupButtons.forEach(element =>
rovingIndex({
element,
target: 'button',
}))
این عنصر اکنون فوکوس را به کودکان <button>
هدف منتقل می کند و پیمایش کلید پیکان استاندارد را برای مرور گزینه ها فعال می کند.
تغییر وضعیت aria-expanded
در حالی که از نظر بصری آشکار است که یک پنجره بازشو نشان داده و پنهان می شود، یک صفحه خوان به چیزی بیش از نشانه های بصری نیاز دارد. جاوا اسکریپت در اینجا برای تمجید از تعامل مبتنی بر CSS :focus-within
با جابجایی ویژگی مناسب صفحهخوان استفاده میشود.
popupButtons.on('focusin', e => {
e.currentTarget.setAttribute('aria-expanded', true)
})
popupButtons.on('focusout', e => {
e.currentTarget.setAttribute('aria-expanded', false)
})
فعال کردن کلید Escape
تمرکز کاربر عمداً به یک تله فرستاده شده است، به این معنی که ما باید راهی برای خروج ارائه کنیم. رایج ترین راه، اجازه استفاده از کلید Escape
است. برای انجام این کار، مراقب فشار دادن کلید روی دکمه بازشو باشید، زیرا هر رویداد صفحهکلید روی کودکان برای این والدین حباب میشود.
popupButtons.on('keyup', e => {
if (e.code === 'Escape')
e.target.blur()
})
اگر دکمه پاپ آپ فشار دادن کلید Escape
را ببیند، با blur()
فوکوس را از روی خود حذف می کند.
تقسیم کلیک دکمه
در نهایت، اگر کاربر روی دکمه ها کلیک کند، ضربه بزند یا صفحه کلید با آن تعامل داشته باشد، برنامه باید اقدام مناسب را انجام دهد. حباب رویداد دوباره در اینجا استفاده میشود، اما این بار در محفظه .gui-split-button
، برای گرفتن کلیکهای دکمه از یک پنجره بازشوی فرزند یا عملکرد اصلی.
splitButtons.on('click', event => {
if (event.target.nodeName !== 'BUTTON') return
console.info(event.target.innerText)
})
نتیجه گیری
حالا که می دانید من چگونه این کار را انجام دادم، چگونه این کار را انجام می دهید‽🙂
بیایید رویکردهایمان را متنوع کنیم و همه راههای ساخت در وب را بیاموزیم. یک نسخه نمایشی ایجاد کنید، پیوندها را برای من توییت کنید ، و من آن را به بخش ریمیکس های انجمن در زیر اضافه می کنم!
ریمیکس های انجمن
- کدپن توسط Joost van der Schee
یک نمای کلی از نحوه ساخت یک جزء کلیدی قابل دسترسی.
در این پست میخواهم فکری را در مورد روشی برای ساخت دکمه تقسیم به اشتراک بگذارم. نسخه ی نمایشی را امتحان کنید .
اگر ویدیو را ترجیح می دهید، در اینجا یک نسخه YouTube از این پست وجود دارد:
نمای کلی
دکمه های تقسیم دکمه هایی هستند که یک دکمه اصلی و لیستی از دکمه های اضافی را پنهان می کنند. آنها برای افشای یک عمل معمولی مفید هستند، در حالی که اقدامات ثانویه و کمتر مورد استفاده را تا زمانی که نیاز باشد، تودرتو میکنند. یک دکمه تقسیم می تواند برای کمک به یک طراحی شلوغ بسیار مهم باشد. یک دکمه تقسیم پیشرفته حتی ممکن است آخرین اقدام کاربر را به خاطر بسپارد و آن را به موقعیت اصلی ارتقا دهد.
یک دکمه تقسیم مشترک را می توان در برنامه ایمیل شما پیدا کرد. اقدام اصلی ارسال است، اما شاید بتوانید بعداً ارسال کنید یا به جای آن یک پیش نویس ذخیره کنید:
منطقه اقدام مشترک خوب است، زیرا کاربر نیازی به نگاه کردن به اطراف ندارد. آنها می دانند که اقدامات ضروری ایمیل در دکمه تقسیم وجود دارد.
قطعات
اجازه دهید قبل از بحث در مورد هماهنگی کلی و تجربه کاربر نهایی، بخشهای اساسی یک دکمه تقسیم را بشکنیم. ابزار بازرسی دسترسی VisBug در اینجا برای کمک به نمایش یک نمای کلان از مؤلفه، نمایاندن جنبههای HTML، سبک و دسترسی برای هر بخش اصلی استفاده میشود.
ظرف دکمه تقسیم سطح بالا
کامپوننت بالاترین سطح یک فلکس باکس درون خطی است، با یک کلاس از gui-split-button
، که شامل اکشن اصلی و .gui-popup-button
است.
دکمه اکشن اولیه
<button>
قابل رویت و قابل فوکوس اولیه در ظرف با دو شکل گوشه منطبق برای فوکوس ، شناور و فعل و انفعالات فعال قرار می گیرد تا در داخل .gui-split-button
ظاهر شوند.
دکمه جابجایی پاپ آپ
عنصر پشتیبانی "دکمه پاپ آپ" برای فعال کردن و اشاره به لیست دکمه های ثانویه است. توجه داشته باشید که یک <button>
نیست و قابل فوکوس نیست. با این حال، لنگر موقعیت یابی برای .gui-popup
و میزبان برای :focus-within
برای ارائه پنجره بازشو استفاده می شود.
کارت پاپ آپ
این یک کارت شناور به .gui-popup-button
لنگر آن است.
اقدام(های) ثانویه
یک <button>
قابل فوکوس با اندازه فونت کمی کوچکتر از دکمه عمل اصلی دارای یک نماد و یک سبک مکمل برای دکمه اصلی است.
خواص سفارشی
متغیرهای زیر به ایجاد هماهنگی رنگ و یک مکان مرکزی برای تغییر مقادیر استفاده شده در سراسر مؤلفه کمک می کنند.
@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --light (prefers-color-scheme: light);
.gui-split-button {
--theme: hsl(220 75% 50%);
--theme-hover: hsl(220 75% 45%);
--theme-active: hsl(220 75% 40%);
--theme-text: hsl(220 75% 25%);
--theme-border: hsl(220 50% 75%);
--ontheme: hsl(220 90% 98%);
--popupbg: hsl(220 0% 100%);
--border: 1px solid var(--theme-border);
--radius: 6px;
--in-speed: 50ms;
--out-speed: 300ms;
@media (--dark) {
--theme: hsl(220 50% 60%);
--theme-hover: hsl(220 50% 65%);
--theme-active: hsl(220 75% 70%);
--theme-text: hsl(220 10% 85%);
--theme-border: hsl(220 20% 70%);
--ontheme: hsl(220 90% 5%);
--popupbg: hsl(220 10% 30%);
}
}
طرح بندی و رنگ
نشانه گذاری
عنصر به صورت <div>
با نام کلاس سفارشی شروع می شود.
<div class="gui-split-button"></div>
دکمه اصلی و عناصر .gui-popup-button
اضافه کنید.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions"></span>
</div>
به ویژگی های aria aria-haspopup
و aria-expanded
توجه کنید. این نشانه ها برای خوانندگان صفحه نمایش برای آگاهی از قابلیت و وضعیت تجربه دکمه تقسیم بسیار مهم است. ویژگی title
برای همه مفید است.
یک نماد <svg>
و عنصر کانتینر .gui-popup
اضافه کنید.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup"></ul>
</span>
</div>
برای قرار دادن پنجره بازشو ساده، .gui-popup
فرزندی برای دکمه ای است که آن را گسترش می دهد. تنها موردی که در این استراتژی وجود دارد این است که ظرف .gui-split-button
نمی تواند overflow: hidden
، زیرا پنجره بازشو را از حضور بصری حذف می کند.
یک <ul>
پر از محتویات <li><button>
خود را بهعنوان یک «لیست دکمهها» به صفحهخوانها اعلام میکند، که دقیقاً رابطی است که ارائه میشود.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup">
<li>
<button>Schedule for later</button>
</li>
<li>
<button>Delete</button>
</li>
<li>
<button>Save draft</button>
</li>
</ul>
</span>
</div>
برای جذابیت و لذت بردن از رنگ، آیکون هایی را به دکمه های ثانویه از https://heroicons.com اضافه کرده ام. نمادها برای هر دو دکمه اصلی و ثانویه اختیاری هستند.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup">
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
Schedule for later
</button></li>
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
Delete
</button></li>
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
</svg>
Save draft
</button></li>
</ul>
</span>
</div>
سبک ها
با وجود HTML و محتوا، سبک ها آماده ارائه رنگ و طرح بندی هستند.
حالت دادن به ظرف دکمه تقسیم
یک نوع نمایشگر inline-flex
برای این جزء بستهبندی به خوبی کار میکند، زیرا باید با سایر دکمهها، اقدامات یا عناصر تقسیمبندی مطابقت داشته باشد.
.gui-split-button {
display: inline-flex;
border-radius: var(--radius);
background: var(--theme);
color: var(--ontheme);
fill: var(--ontheme);
touch-action: manipulation;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
یک ظاهر طراحی شده <button>
دکمه ها در پنهان کردن مقدار کد مورد نیاز بسیار خوب هستند. ممکن است لازم باشد سبکهای پیشفرض مرورگر را لغو یا جایگزین کنید، اما همچنین باید مقداری وراثت را اعمال کنید، حالتهای تعامل را اضافه کنید و با تنظیمات برگزیده و انواع ورودی کاربر سازگار شوید. سبک های دکمه ها به سرعت جمع می شوند.
این دکمهها با دکمههای معمولی متفاوت هستند، زیرا یک پسزمینه با یک عنصر والد به اشتراک میگذارند. معمولاً یک دکمه دارای پسزمینه و رنگ متن است. با این حال، اینها آن را به اشتراک می گذارند و تنها پس زمینه خود را در تعامل اعمال می کنند.
.gui-split-button button {
cursor: pointer;
appearance: none;
background: none;
border: none;
display: inline-flex;
align-items: center;
gap: 1ch;
white-space: nowrap;
font-family: inherit;
font-size: inherit;
font-weight: 500;
padding-block: 1.25ch;
padding-inline: 2.5ch;
color: var(--ontheme);
outline-color: var(--theme);
outline-offset: -5px;
}
اضافه کردن حالت های تعامل با چند کلاس شبه CSS و استفاده از ویژگی های سفارشی منطبق برای حالت:
.gui-split-button button {
…
&:is(:hover, :focus-visible) {
background: var(--theme-hover);
color: var(--ontheme);
& > svg {
stroke: currentColor;
fill: none;
}
}
&:active {
background: var(--theme-active);
}
}
دکمه اصلی برای تکمیل جلوه طراحی به چند سبک خاص نیاز دارد:
.gui-split-button > button {
border-end-start-radius: var(--radius);
border-start-start-radius: var(--radius);
& > svg {
fill: none;
stroke: var(--ontheme);
}
}
در نهایت، برای کمی استعداد، دکمه و نماد تم روشن سایه مییابند:
.gui-split-button {
@media (--light) {
& > button,
& button:is(:focus-visible, :hover) {
text-shadow: 0 1px 0 var(--theme-active);
}
& > .gui-popup-button > svg,
& button:is(:focus-visible, :hover) > svg {
filter: drop-shadow(0 1px 0 var(--theme-active));
}
}
}
یک دکمه عالی به ریز تعاملات و جزئیات کوچک توجه کرده است.
نکته ای در مورد :focus-visible
توجه کنید که چگونه استایل های دکمه از :focus-visible
به جای :focus
استفاده می کنند. :focus
برای ایجاد یک رابط کاربری قابل دسترسی بسیار مهم است، اما یک نقطه ضعف دارد: این که آیا کاربر باید آن را ببیند یا نه، هوشمندانه نیست، برای هر تمرکزی کاربرد دارد.
ویدئوی زیر تلاش میکند تا این ریز تعامل را از بین ببرد تا نشان دهد چگونه :focus-visible
یک جایگزین هوشمند است.
حالت دادن به دکمه بازشو
فلکس باکس 4ch
برای وسط یک نماد و لنگر انداختن لیست دکمه های بازشو. مانند دکمه اصلی، شفاف است تا زمانی که در غیر این صورت شناور شود یا با آن تعامل داشته باشد، و کشیده شود تا پر شود.
.gui-popup-button {
inline-size: 4ch;
cursor: pointer;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
border-inline-start: var(--border);
border-start-end-radius: var(--radius);
border-end-end-radius: var(--radius);
}
لایه در حالت شناور، فوکوس و فعال با CSS Nesting و انتخابگر تابعی :is()
:
.gui-popup-button {
…
&:is(:hover,:focus-within) {
background: var(--theme-hover);
}
/* fixes iOS trying to be helpful */
&:focus {
outline: none;
}
&:active {
background: var(--theme-active);
}
}
این سبک ها قلاب اصلی برای نمایش و پنهان کردن پنجره بازشو هستند. هنگامی که .gui-popup-button
روی هر یک از فرزندان خود focus
دارد، opacity
، موقعیت و pointer-events
روی نماد و پنجره بازشو تنظیم کنید.
.gui-popup-button {
…
&:focus-within {
& > svg {
transition-duration: var(--in-speed);
transform: rotateZ(.5turn);
}
& > .gui-popup {
transition-duration: var(--in-speed);
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
}
}
با تکمیل سبکهای درون و بیرون، آخرین قطعه بسته به ترجیح حرکت کاربر ، تبدیلها را به صورت شرطی تغییر میدهد :
.gui-popup-button {
…
@media (--motionOK) {
& > svg {
transition: transform var(--out-speed) ease;
}
& > .gui-popup {
transform: translateY(5px);
transition:
opacity var(--out-speed) ease,
transform var(--out-speed) ease;
}
}
}
با نگاه دقیق به کد متوجه میشوید که هنوز کدورت برای کاربرانی که حرکت کاهشیافته را ترجیح میدهند، تغییر میکند .
سبک دادن به پنجره بازشو
عنصر .gui-popup
یک لیست دکمه کارت شناور است که از ویژگیهای سفارشی و واحدهای نسبی استفاده میکند تا بهطور نامحسوس کوچکتر باشد، بهطور تعاملی با دکمه اصلی تطبیق داده شود، و در برند با استفاده از رنگ استفاده شود. توجه داشته باشید که نمادها کنتراست کمتری دارند، نازکتر هستند و سایه رنگ آبی کاملاً روی آن دیده میشود. مانند دکمه ها، UI و UX قوی نتیجه این جزئیات کوچک است.
.gui-popup {
--shadow: 220 70% 15%;
--shadow-strength: 1%;
opacity: 0;
pointer-events: none;
position: absolute;
bottom: 80%;
left: -1.5ch;
list-style-type: none;
background: var(--popupbg);
color: var(--theme-text);
padding-inline: 0;
padding-block: .5ch;
border-radius: var(--radius);
overflow: hidden;
display: flex;
flex-direction: column;
font-size: .9em;
transition: opacity var(--out-speed) ease;
box-shadow:
0 -2px 5px 0 hsl(var(--shadow) / calc(var(--shadow-strength) + 5%)),
0 1px 1px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 10%)),
0 2px 2px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 12%)),
0 5px 5px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 13%)),
0 9px 9px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 14%)),
0 16px 16px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 20%))
;
}
به نمادها و دکمهها رنگهای نام تجاری داده میشود تا در هر کارت با مضمون تیره و روشن به زیبایی استایل دهید:
.gui-popup {
…
& svg {
fill: var(--popupbg);
stroke: var(--theme);
@media (prefers-color-scheme: dark) {
stroke: var(--theme-border);
}
}
& button {
color: var(--theme-text);
width: 100%;
}
}
پنجره تم تیره دارای افزودنی های سایه متن و نماد، به علاوه یک سایه کادر کمی شدیدتر است:
.gui-popup {
…
@media (--dark) {
--shadow-strength: 5%;
--shadow: 220 3% 2%;
& button:not(:focus-visible, :hover) {
text-shadow: 0 1px 0 var(--ontheme);
}
& button:not(:focus-visible, :hover) > svg {
filter: drop-shadow(0 1px 0 var(--ontheme));
}
}
}
سبکهای آیکون <svg>
عمومی
همه نمادها با استفاده از واحد ch
بهعنوان inline-size
اندازهی نسبی به font-size
قلم دکمهای دارند که در آن استفاده میشود. همچنین به هر کدام از آنها سبک هایی داده شده است تا به نرم و صاف بودن آیکون ها کمک کنند.
.gui-split-button svg {
inline-size: 2ch;
box-sizing: content-box;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2px;
}
طرح بندی از راست به چپ
خواص منطقی تمام کارهای پیچیده را انجام می دهند. در اینجا لیستی از ویژگی های منطقی استفاده شده است: - display: inline-flex
یک عنصر انعطاف پذیر درون خطی ایجاد می کند. - padding-block
و padding-inline
به صورت جفتی، به جای padding
shorthand، از مزایای padding اضلاع منطقی بهره مند شوید. - border-end-start-radius
و دوستان بر اساس جهت سند، گوشه ها را گرد می کنند. - inline-size
به جای width
تضمین می کند که اندازه به ابعاد فیزیکی مرتبط نیست. - border-inline-start
یک حاشیه به شروع اضافه می کند که بسته به جهت اسکریپت ممکن است در سمت راست یا چپ باشد.
جاوا اسکریپت
تقریباً تمام جاوا اسکریپت زیر برای افزایش دسترسی است. دو تا از کتابخانههای کمکی من برای سادهتر کردن کارها استفاده میشوند. BlingBlingJS برای پرسشهای مختصر DOM و راهاندازی آسان شنونده رویداد استفاده میشود، در حالی که roving-ux کمک میکند تا تعاملات صفحه کلید و گیمپد در دسترس را برای پنجره بازشو تسهیل کند.
import $ from 'blingblingjs'
import {rovingIndex} from 'roving-ux'
const splitButtons = $('.gui-split-button')
const popupButtons = $('.gui-popup-button')
با وارد کردن کتابخانههای بالا و انتخاب عناصر و ذخیره شده در متغیرها، ارتقای تجربه چند تابع تا کامل شدن فاصله دارد.
شاخص روینگ
هنگامی که یک صفحهکلید یا صفحهخوان روی .gui-popup-button
متمرکز میشود، میخواهیم فوکوس را به اولین دکمه (یا اخیراً متمرکز شده) در .gui-popup
. منتقل کنیم. کتابخانه به ما کمک می کند تا این کار را با پارامترهای element
و target
انجام دهیم.
popupButtons.forEach(element =>
rovingIndex({
element,
target: 'button',
}))
این عنصر اکنون فوکوس را به کودکان <button>
هدف منتقل می کند و پیمایش کلید پیکان استاندارد را برای مرور گزینه ها فعال می کند.
تغییر وضعیت aria-expanded
در حالی که از نظر بصری آشکار است که یک پنجره بازشو نشان داده و پنهان می شود، یک صفحه خوان به چیزی بیش از نشانه های بصری نیاز دارد. جاوا اسکریپت در اینجا برای تمجید از تعامل مبتنی بر CSS :focus-within
با جابجایی ویژگی مناسب صفحهخوان استفاده میشود.
popupButtons.on('focusin', e => {
e.currentTarget.setAttribute('aria-expanded', true)
})
popupButtons.on('focusout', e => {
e.currentTarget.setAttribute('aria-expanded', false)
})
فعال کردن کلید Escape
تمرکز کاربر عمداً به یک تله فرستاده شده است، به این معنی که ما باید راهی برای خروج ارائه کنیم. رایج ترین راه، اجازه استفاده از کلید Escape
است. برای انجام این کار، مراقب فشار دادن کلید روی دکمه بازشو باشید، زیرا هر رویداد صفحهکلید روی کودکان برای این والدین حباب میشود.
popupButtons.on('keyup', e => {
if (e.code === 'Escape')
e.target.blur()
})
اگر دکمه پاپ آپ فشار دادن کلید Escape
را ببیند، با blur()
فوکوس را از روی خود حذف می کند.
تقسیم کلیک دکمه
در نهایت، اگر کاربر روی دکمه ها کلیک کند، ضربه بزند یا صفحه کلید با آن تعامل داشته باشد، برنامه باید اقدام مناسب را انجام دهد. حباب رویداد دوباره در اینجا استفاده میشود، اما این بار در محفظه .gui-split-button
، برای گرفتن کلیکهای دکمه از یک پنجره بازشوی فرزند یا عملکرد اصلی.
splitButtons.on('click', event => {
if (event.target.nodeName !== 'BUTTON') return
console.info(event.target.innerText)
})
نتیجه گیری
حالا که می دانید من چگونه این کار را انجام دادم، چگونه این کار را انجام می دهید‽🙂
بیایید رویکردهایمان را متنوع کنیم و همه راههای ساخت در وب را بیاموزیم. یک نسخه نمایشی ایجاد کنید، پیوندها را برای من توییت کنید ، و من آن را به بخش ریمیکس های انجمن در زیر اضافه می کنم!
ریمیکس های انجمن
- کدپن توسط Joost van der Schee