در این مقاله ، به بررسی نحوه راه اندازی گالری می پردازیم که قابل افزایش و دسترسی باشد.
یکی از موارد استفاده از CSS Grid نمایش گالری تصاویر است ، اما یک گالری به تنهایی ممکن است چندان هیجان انگیز نباشد. به عنوان مثال ، ما می توانیم یک اثر کلیک برای بزرگنمایی تصویر بدون تأثیر بر روی شبکه اضافه کنیم تا کمی سرگرم کننده تر شود. و البته ، برای اینکه کسی را از لذت بردن از این ویژگی محروم نکنیم ، باید آن را نیز در دسترس قرار دهیم.
در این مقاله ، نحوه ایجاد یک گالری قابل توسعه قابل دسترس را با چند نکته و ترفند در طول راه توضیح خواهم داد. در اینجا نتیجه نهایی به نظر می رسد:
قلم را ببینید [How to build accessible expandable gallery](https://codepen.io/smashingmag/pen/PojxvJr) توسط سیلوستار بیستروویچبه
HTML
ابتدا ، ما قصد داریم ساختار HTML را تنظیم کنیم. البته ، ما همیشه می توانیم آن را به روش های مختلف انجام دهیم ، اما اجازه دهید از لیستی از تصاویر پیچیده در دکمه ها استفاده کنیم.
<ul class="js-favs">
<li>
<button>
<img src="https://smashingmagazine.com/path/to/image" alt="" />
</button>
</li>
...
</ul>
اکنون ، برای دسترسی به گالری ، باید تعدادی تنظیمات را انجام دهیم:
- توصیفی را اضافه کنید
alt
نسبت دادن به هر تصویر برای کمک به افراد کم بینا برای درک آنچه در تصویر وجود دارد ؛ - استفاده کنید
aria-expanded
مشخصه ای که در صورت گسترش یا عدم گسترش تصویر ، فناوری های کمکی را اطلاع می دهد. - عبارتند از
role="list"
اطمینان حاصل کنید که فناوری های کمکی لیست را اعلام می کنند زیرا ممکن است برخی از خوانندگان صفحه اعلان لیست را حذف کنند.
“این فقط استفاده نیست
list-style: none
، اما هر CSS که نشانگر تعداد یا تعداد عناصر لیست را حذف کند ، معناشناسی را نیز حذف می کند. “– “رفع” لیست ها، اسکات اوهارا
در نهایت ، بیایید یک پاراگراف با متن مفید در مورد نحوه استفاده از گالری اضافه کنیم و کل کد را در یک نقطه عطف قرار دهیم (در این مورد ، main
عنصر)
<main>
<p>Use ESC to close larger picture.</p>
<ul class="js-favs" role=”list”>
<li>
<button aria-expanded="false">
<img src="https://smashingmagazine.com/path/to/image" alt="Description of the image." />
</button>
</li>
...
</ul>
</main>
برای سادگی نسخه ی نمایشی ، تصمیم گرفتم از تصاویر پیچیده شده با استفاده کنم aria-expanded
صفت. راه حل بهتر این است که فقط برچسب های تصویر را اضافه کنید و سپس از جاوا اسکریپت برای پیچاندن این تصاویر در یک دکمه با استفاده کنید aria-expanded
صفت. این ممکن است به عنوان پیشرفت پیشرونده در نظر گرفته شود زیرا به هر حال ، اثر گسترش بدون جاوا اسکریپت کار نمی کند.
CSS
برای تعریف طرح بندی شبکه ، می توانیم از CSS Grid استفاده کنیم. استفاده خواهیم کرد auto-fit
به طوری که اقلام می توانند در فضای موجود قرار بگیرند ، اما خود را از کوچک شدن در عرض معینی محدود می کنند. این بدان معناست که ما تعداد متفاوتی از موارد را در نماهای نمای مختلف بدون نوشتن درخواست های رسانه ای زیاد مشاهده می کنیم.
:root {
--gap: 4px;
}
ul {
display: grid;
grid-template-columns: repeat(1, 1fr);
grid-gap: var(--gap);
}
@media screen and (min-width: 640px) {
ul {
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
}
برای حفظ نسبت ابعاد صحیح تصویر ، می توانیم از aspect-ratio
ویژگیبه برای تنظیم مجدد سبک دکمه ، می توانیم دکمه را اضافه کنیم all: initial
اعلام. ما همچنین باید سرریز دکمه را پنهان کنیم.
برای اینکه تصویر درست در دکمه جا بگیرد ، از آن استفاده می کنیم object-fit: cover
اعلان و تنظیم هر دو width
و height
به 100%
:
button {
all: initial;
display: block;
width: 100%;
aspect-ratio: 2/1;
overflow: hidden;
cursor: pointer;
}
img {
height: 100%;
width: 100%;
object-fit: cover;
}
اثر گسترش با استفاده از scale
دگرگونی. انتقال به طور پیش فرض فعال است ، اما اگر کاربر انتقال و انیمیشن را ترجیح ندهد ، می توانیم از prefers-reduced-motion
پرس و جو رسانه ای و تنظیم transition-duration
مالکیت به 0s
به
:root {
--duration-shrink: .5s;
--duration-expand: .25s;
--no-duration: 0s;
}
li {
transition-property: transform, opacity;
transition-timing-function: ease-in-out;
transition-duration: var(--duration-expand);
}
li.is-zoomed {
transition-duration: var(--duration-shrink);
}
@media (prefers-reduced-motion) {
li,
li.is-zoomed {
transition-duration: var(--no-duration);
}
}
جاوا اسکریپت
آماده سازی
قبل از اینکه عنصر را بسط دهیم ، باید چند مورد را تهیه و محاسبه کنیم.
ابتدا ، ما باید مدت زمان انتقال را با خواندن ویژگی CSS Custom مشخص کنیم --duration-on
به
let timeout = 0
// Get the transition timeout from CSS
const getTimeouts = () => {
const durationOn = parseFloat(getComputedStyle(document.documentElement)
.getPropertyValue('--duration-on'));
timeout = parseFloat(durationOn) * 1000
}
بعد ، ما تنظیم ویژگی های داده برای محاسبه بعدی:
- شکاف عناصر شبکه ؛
- عرض یک عنصر واحد ؛
- تعداد موارد در هر ردیف.
دو مورد اول بسیار ساده هستند. ما می توانیم مقادیر را از سبک CSS محاسبه شده بدست آوریم.
برای یافتن تعداد ستون ها ، باید در هر کاشی تکرار کنیم و موقعیت بالای هر عنصر را مقایسه کنیم. هنگامی که موقعیت بالا تغییر می کند ، مورد در ردیف جدید قرار می گیرد ، که تعداد موارد را برای ما دریافت می کند.
// Set data attributes for calculations
const setDataAttrs = ($elems, $parent) => {
// Get the top offset of the first element
let top = getTop($elems[0])
// Set grid gap from CSS
const gridColumnGap = parseFloat(getComputedStyle(document.documentElement)
.getPropertyValue('--gap'))
$parent.setAttribute('data-gap', gridColumnGap)
// Set grid item width from CSS
const eStyle = getComputedStyle($elems[0])
$parent.setAttribute('data-width', eStyle.width)
// Iterate through grid items
for (let i = 0; i < $elems.length; i++) {
const t = getTop($elems[i])
// Check when top offset changes
if (t != top) {
// Set the number of columns and break stop the loop
$parent.setAttribute('data-cols', i)
break;
}
}
}
گسترش جهت
برای دستیابی به اثر قابل گسترش ، ابتدا باید برخی از بررسی ها و محاسبات را انجام دهیم. ابتدا باید بررسی کنیم که آیا آیتم در ردیف آخر و در انتهای ردیف است یا خیر. اگر مورد در ردیف آخر است ، باید به بالا گسترش یابد. یعنی باید داشته باشد transform-origin
ویژگی تنظیم شده بر bottom
ارزش.
مهم: اگر عنصر باید به یک جهت گسترش یابد ، خود transform-origin
ویژگی باید روی مقدار “مخالف” تنظیم شود. توجه داشته باشید که مقادیر عمودی و افقی باید با هم ترکیب شوند.
// Set active item
const activateElem = ($elems, $parent, $elem, $button, lengthOfElems, i) => {
// Get data attributes from parent
const cols = parseInt($parent.getAttribute('data-cols'))
const width = parseFloat($parent.getAttribute('data-width'))
const gap = parseFloat($parent.getAttribute('data-gap'))
// Calculate the number of rows
const rows = Math.ceil(lengthOfElems / cols) - 1
// Calculate if the item is in the last row
const isLastRow = i + 1 > rows * cols
// Set default transform direction to top (expand down)
let transformOrigin = 'top'
if (isLastRow) {
// If the item is in the last row, set transform direction to bottom (expand up)
transformOrigin = 'bottom'
}
// Calculate if the item is the most right
const isRight = (i + 1) % cols !== 0
if (isRight) {
// If the item is the most right, set transform direction to left (expand right)
transformOrigin += ' left'
} else {
// If the item is the most right, set transform direction to right (expand left)
transformOrigin += ' right'
}
$elem.style.transformOrigin = transformOrigin
}
گسترش اثر
برای بزرگنمایی تصویر بدون تأثیر بر شبکه ، می توانیم از تبدیل CSS استفاده کنیم. به طور خاص ، ما باید از تغییر مقیاس استفاده کنیم. من تصمیم گرفتم اندازه تصویر را دو برابر کنم ، یعنی عامل نسبت عرض دو برابر عنصر به علاوه شکاف شبکه است.
// Calculate the scale coefficient
const scale = (width * 2 + gap) / width
// Set item CSS transform
$elem.style.transform = `scale(${scale})`
پشتیبانی از صفحه کلید
کاربرانی که با استفاده از صفحه کلید در سایت ها حرکت می کنند باید بتوانند از گالری استفاده کنند. مرور فهرست به طور پیش فرض هنگام استفاده از کلید کار می کند برگهبه شبیه سازی کلیک به طور پیش فرض با فشار دادن کلید کار می کند وارد کلید در حالی که مورد متمرکز است. برای افزایش رفتار پیش فرض ، باید پشتیبانی را برای آن اضافه کنیم خروج و کلیدهای پیکان
هنگامی که مورد را گسترش می دهیم ، فشار دهید خروج باید آن را به اندازه استاندارد خود برگرداند. ما می توانیم این کار را با بررسی کد کلید فشرده انجام دهیم. در مورد کلیدهای پیکان نیز همینطور است ، اما عملکرد متفاوت است. هنگام فشار دادن کلیدهای جهت نما ، ما می خواهیم خواهر یا برادر قبلی خود را بدست آوریم و سپس کلیک روی آن عنصر را شبیه سازی کنیم.
// Set sibling as an active item
const activateSibling = ($sibling) => {
// Find anchor
const $siblingButton = $sibling.querySelector('button')
// Unset global active element
$activeElem = false
// Focus and click on current
$siblingButton.focus()
$siblingButton.click()
}
// Set keyboard events
const setKeyboardEvents = () => {
document.addEventListener('keydown', (e) => {
// Take action only if global active element exists
if ($activeElem) {
// If key is “escape”, emulate the click on the global active element
if (e.code === 'Escape') {
$activeElem.click()
}
// If key is “left arrow”, activate the previous sibling
if (e.code === 'ArrowLeft') {
const $previousSibling = $activeElem.parentNode.previousElementSibling
if($previousSibling) {
activateSibling($previousSibling)
}
}
// If key is “right arrow”, activate the next sibling
if (e.code === 'ArrowRight') {
const $nextSibling = $activeElem.parentNode.nextElementSibling
if($nextSibling) {
activateSibling($nextSibling)
}
}
}
})
}
جابجا کردن
برای گسترش عنصر گالری ، ابتدا باید همه عناصر دیگر را غیرفعال کنیم. سپس ، اگر روی عنصر گسترش یافته کلیک کنیم ، باید به اندازه استاندارد بازگردد.
let $activeElem = false
// Deactivate grid items
const deactiveElems = ($elems, $parent, $currentElem, $button) => {
// Unset parent class
$parent.classList.remove('is-zoomed')
for (let i = 0; i < $elems.length; i++) {
// Unset item class
$elems[i].classList.remove('is-zoomed')
// Unset item CSS transform
$elems[i].style.transform = 'none'
// Skip the rest if the item is the current item
if ($elems[i] === $currentElem) {
continue
}
// Unset item aria expanded if element exists
if($button) {
$button.setAttribute('aria-expanded', false)
}
}
}
// Set active item
const activateElem = ($elems, $parent, $elem, $button, lengthOfElems, i) => {
...
// Reset all elements
deactiveElems($elems, $parent, $elem, $button)
if ($activeElem) {
$activeElem = false
return
}
$activeElem = $button
...
}
// Set click events on anchors
const setClicks = ($elems, $parent) => {
$elems.forEach(($elem, i) => {
// Find anchor
const $button = $elem.querySelector('button')
$button.addEventListener('click', (e) => {
// Set active item on click
activateElem($elems, $parent, $elem, $button, $elems.length, i)
})
})
}
مسائل مربوط به شاخص Z
برای جلوگیری از مشکلات با z-index
و زمینه stacking ، ما باید از timeout برای به تاخیر انداختن تغییر استفاده کنیم. این همان تایم اوت است که در مرحله آماده سازی محاسبه کردیم.
// Deactivate grid items
const deactiveElems = ($elems, $parent, $currentElem, $button) => {
for (let i = 0; i < $elems.length; i++) {
...
// After a half of the timeout, reset CSS z-index to avoid overlay issues
setTimeout(() => {
$elems[i].style.zIndex = 0
}, timeout)
}
}
// Set active item
const activateElem = ($elems, $parent, $elem, $button, lengthOfElems, i) => {
...
setTimeout(() => {
// Set parent class
$parent.classList.add('is-zoomed')
// Set item class
$elem.classList.add('is-zoomed')
// Set item CSS transform
$elem.style.transform = `scale(${scale})`
// Set item aria expanded
$button.setAttribute('aria-expanded', true)
// Set global active item
$activeElem = $button
}, timeout)
}
تغییر اندازه Viewport
اگر نمای نمایش اندازه را تغییر دهد ، باید پیش فرض ها را دوباره محاسبه کنیم زیرا یک شبکه سیال تعریف کردیم که به اقلام اجازه می دهد فضای موجود را پر کرده و از سطر به سطر حرکت کنند.
// Set resize events
const setResizeEvents = ($elems, $parent) => {
window.addEventListener('resize', () => {
// Set data attributes for calculations
setDataAttrs($elems, $parent)
// Deactivate grid items
deactiveElems($elems, $parent)
})
}
چند کلمه در مورد دسترسی و اعتبار
من در ساخت این نسخه ی نمایشی هیچ مشکلی نداشتم جز در قسمت دسترسی. در ابتدا مطمئن نبودم که باید چه کار کنم و از کدام ویژگی آریا استفاده کنم. حتی پس از فهمیدن اینکه از چه ویژگیهایی استفاده کنم ، نمی توانم 100٪ مطمئن باشم که درست است. بنابراین اولین قدم این بود که همه چیز را با صفحه کلید آزمایش کنیدبه آن قسمت آسان بود. سپس از برنامه VoiceOver (زیرا از Mac استفاده می کنم) برای آزمایش نحوه عملکرد آن برای افراد کم بینا استفاده کردم. به اندازه کافی خوب به نظرم رسید
با این حال ، حتی پس از آن همه آزمایش ، هنوز 100 sure مطمئن نبودم. بنابراین تصمیم گرفتم از او کمک بخواهم. من بخشی از یک جامعه Slack برای طراحان و توسعه دهندگان هستم (BoagWorld) ، و من یک سوال در آنجا ارسال کردم. خوشبختانه ، متخصصان دسترسی مانند Todd Libby به من کمک کردند تا نسخه ی نمایشی را روی دستگاه های مختلف آزمایش کنم و کد را تصحیح کنم. من همچنین از مانوئل ماتوزوویچ کمک خواستم و او به من کمک کرد تا کد را پاک کنم.
من از داشتن اینترنت و جوامع توسعه دهنده که در آن همه ما می توانیم کمک بخواهیم ، از متخصصان پاسخ بگیریم و مشکلات را با هم حل کنیدبه این امر به ویژه در مورد مسائل حساس مانند دسترس پذیری صادق است. دسترسی سخت است و اشتباه کردن آن زیاد طول نمی کشد. کمتر بیشتر است – حداقل در مورد من بود.
و در نهایت ، می خواستم بزرگترین درس را به اشتراک بگذارم:
“اگر می توانید از یک عنصر HTML بومی استفاده کنید [HTML51] یا با معانی و رفتار مورد نیاز خود نسبت دهید در حال حاضر ساخته شده است، به جای هدفگذاری مجدد یک عنصر و افزودن نقش ، حالت یا ویژگی ARIA برای دسترسی به آن ، سپس این کار را انجام دهید. »
– اولین قانون استفاده از ARIA، W3C Working Draft 27 (سپتامبر 2018)
ادامه مطلب را در مجله Smashing بخوانید
