@wanjapflueger/a11y-slider
v2.3.3
Published
A fully accessible slider
Downloads
14
Maintainers
Readme
a11y-slider
A slider carousel that is fully accessible. It has no design or theme applied. That way you can be certain the slider is usable and accessible and you can apply your own styles.
*Yes I know that the slider in the screenshot above has some example styles applied...
Table of contents
- a11y-slider
Requirements
- You need to be able to import or require JavaScript files
- You need to be able to import SCSS with SASS (since v2.0.0).
Browser support
- Chrome 84
- Firefox 63
- Edge 84
- Opera 73
- Safari 14
Changelog
See changelog.md.
Install
npm i @wanjapflueger/a11y-slider
Example
example.wanjapflueger.de/a11y-slider/
Usage
Import class Slider
:
import { Slider } from "@wanjapflueger/a11y-slider";
// var a11ySlider = require("@wanjapflueger/a11y-slider")
Create a new instance:
const mySlider = new Slider();
// const mySlider = new a11ySlider.Slider();
Methods
Method Create
Create any reference element containing child elements. The contents of the each child element will be a single slide.
<div id="my-list"> <div>1</div> <div>2</div> ... </div>
Assign that reference element with JavaScript to a variable, initiate a new instance of
Slider
and call methodCreate
.import { Slider } from "@wanjapflueger/a11y-slider"; const mySlider = new Slider(); mySlider.Create({ slides: document.getElementById('my-list') });
Parameters
See interface SliderParameters
in src/index.ts.
slides
| Parameter | Required | Type | Default value |
| --------- | -------- | ------------- | ------------- |
| slides
| ✅ Yes | HTMLElement
| – |
Any HTMLElement
containing child elements (HTMLUListElement
in the example below). Of each child element (HTMLLIElement
in the example below) the innerHTML
will be placed inside a slide. This means the HTMLUListElement
and HTMLLIElement[]
will not be transferred to the slider, only the HTMLDivElement[]
will, because they are inside the children of the HTMLUListElement
.
Example:
<ul>
<li><div class="item">A</div></li>
<li><div class="item">B</div></li>
<li><div class="item">C</div></li>
</ul>
const slider = new Slider()
slider.Create({
slides: document.querySelector('ul')
})
arrows
| Parameter | Required | Type | Default value |
| --------- | -------- | --------------------- | ------------- |
| arrows
| ❌ No | boolean
or Arrows
| false
|
Arrows configuration. You may use a custom Element
for the previous and/or next button. Do not use a HTMLButtonElement
. You may change the text on the arrows.
<span class="next">Next slide</span>
<span class="prev">Previous slide</span>
const slider = new Slider()
slider.Create({
// ...
arrows: true | false | {
label: 'Slider controls' | undefined,
next: {
icon: document.querySelector('.next') | '→' | undefined,
label: document.querySelector('.next').innerText
},
prev: {
icon: document.querySelector('.prev') | '←' | undefined,
label: document.querySelector('.prev').innerText
}
}
})
See also:
autoplay
| Parameter | Required | Type | Default value |
| ---------- | -------- | -------- | ------------- |
| autoplay
| ❌ No | number
| undefined
|
Autoplay speed in milliseconds.
Example:
const slider = new Slider()
slider.Create({
// ...
autoplay: 4000 | undefined
})
loop
| Parameter | Required | Type | Default value |
| --------- | -------- | --------- | ------------- |
| loop
| ❌ No | boolean
| false
|
Go from the first to the last slide and the other way around.
Example:
const slider = new Slider()
slider.Create({
// ...
loop: true | false
})
caption
| Parameter | Required | Type | Default value |
| --------- | -------- | -------- | ------------- |
| caption
| ❌ No | string
| 'Slider'
|
Slider caption for screen readers and bots. If empty will use:
- the
[aria-label]
attribute text on slides or - the
innerText
of anyElement
that is referenced with[aria-labelledby]
or - the
[title]
attribute text on slides.
Example:
const slider = new Slider()
slider.Create({
// ...
caption: 'My Slider' | undefined
})
lang
| Parameter | Required | Type | Default value |
| --------- | -------- | -------- | -------------------------------------------------------- |
| lang
| ❌ No | string
| document.documentElement.lang
(the documents language) |
Language Code ISO 639-1.
Example:
const slider = new Slider()
slider.Create({
// ...
lang: 'de' | 'es' | 'en' | undefined
})
pagination
| Parameter | Required | Type | Default value |
| ------------ | -------- | ------------------------- | ------------- |
| pagination
| ❌ No | boolean
or Pagination
| false
|
Pagination configuration. You can change the label for the pagination element. You can change the innerHTML
of the HTMLButtonElement[]
elements that are the paginations bullets. Each bullet displays a number by default (starting with '1'
). Each Bullet will always have the current index as text for screen readers and bots.
<span class="bullet">⦿</span>
const slider = new Slider()
slider.Create({
// ...
pagination: true | false | {
icon: document.querySelector('.bullet') | '⦿' | undefined,
label: 'Slider pagination' | undefined
}
})
See also:
- interface
Pagination
slideBy
| Parameter | Required | Type | Default value |
| --------- | -------- | -------- | ------------- |
| slideBy
| ❌ No | number
| 1
|
Number of slides to scroll when using prev/next arrows, arrow keys or autoplay.
Example:
const slider = new Slider()
slider.Create({
// ...
slideBy: 2 | undefined
})
syncWith
| Parameter | Required | Type | Default value |
| ---------- | -------- | -------- | ------------- |
| syncWith
| ❌ No | Slider
| undefined
|
Sync a slider with other sliders.
Example:
const sliderA = new Slider()
const sliderB = new Slider()
const sliderC = new Slider()
sliderA.Create({
// ...
syncWith: [sliderB, sliderC]
})
sliderB.Create({
// ...
syncWith: [sliderA, sliderC]
})
sliderC.Create({
// ...
syncWith: [sliderA, sliderB]
})
See also:
- interface
Slider
onMoveStart
| Parameter | Required | Type | Default value |
| ------------- | -------- | ------------------------ | ------------- |
| onMoveStart
| ❌ No | (status: Status) => {}
| undefined
|
Callback function once the sliders movement starts.
Example:
const slider = new Slider()
slider.Create({
// ...
onMoveStart: (status) => {
console.log('Slider has started moving', status)
} | undefined
})
See also:
- interface
Status
onMoveEnd
| Parameter | Required | Type | Default value |
| ----------- | -------- | ------------------------ | ------------- |
| onMoveEnd
| ❌ No | (status: Status) => {}
| undefined
|
Callback function once the sliders movement stops.
Example:
const slider = new Slider()
slider.Create({
// ...
onMoveEnd: (status) => {
console.log('Slider has stopped moving', status)
} | undefined
})
See also:
- interface
Status
onCreated
| Parameter | Required | Type | Default value |
| ----------- | -------- | ---------- | ------------- |
| onCreated
| ❌ No | () => {}
| undefined
|
Callback function after the slider was created. Function is called once, when the slider is successfully created. Use this to apply event listeners to elements in any slide (read more).
Example:
const slider = new Slider()
slider.Create({
// ...
onCreated: () => {
console.log('Slider was created')
} | undefined
})
onDestroyed
| Parameter | Required | Type | Default value |
| ------------- | -------- | ---------- | ------------- |
| onDestroyed
| ❌ No | () => {}
| undefined
|
Callback function after the slider was destroyed. Function is called once, when the slider is successfully destroyed.
Example:
const slider = new Slider()
slider.Create({
// ...
onDestroyed: () => {
console.log('Slider was destroyed')
} | undefined
})
onUpdate
| Parameter | Required | Type | Default value |
| ---------- | -------- | ------------------------ | ------------- |
| onUpdate
| ❌ No | (status: Status) => {}
| undefined
|
Callback function on update. Update is called, whenever the slider changes or moves.
Example:
const slider = new Slider()
slider.Create({
// ...
onUpdate: (status) => {
console.log('Slider is moving or updating', status)
} | undefined
})
See also:
- interface
Status
Method Destroy
Destroy the slider.
mySlider.Destroy()
Once destroyed you can call Method Create again at any time, for example on a window resize event.
Breakpoints
To destroy the slider for a specific breakpoint, whilst maintaining a solid performance, one could use the following code.
myEfficientFn(window, 'resize', () => {
if(window.innerWidth >= 1024) {
mySlider.Destroy()
} else {
mySlider.Create()
}
}, 100, true);
myEfficientFn.js
/**
* Very efficient function with debounce and throttle events
* @param {HTMLElement} target Target element for eventListener
* @param {EventListener} eventListener EventListener
* @param {function} func Callback function
* @param {number} time Wait for miliseconds after each call
* @param {boolean} callOnLoad Call func when this function is called
* @returns {void}
* @example
* myEfficientFn(window, 'scroll', () => { return console.log('hello'); }, 100, true);
*/
function myEfficientFn(target, eventListener, func, time, callOnLoad) {
/**
* Throttle function
* @see {@link https://codeburst.io/throttling-and-debouncing-in-javascript-b01cad5c8edf}
* @param {function} f Callback function
* @param {number} w Wait for miliseconds until next execution
* @returns {function} Calls callback function
* @example
* window.addEventListener('scroll', throttle(() => { return console.log('hello') }, 100));
*/
function throttle(f, w) {
/** @type {boolean} Is throtteling */
let t;
return function () {
/** @type {*} Arguments */
const a = arguments;
/** @type {*} Context */
const c = this;
if (!t) {
f.apply(c, a);
t = true;
setTimeout(() => (t = false), w);
}
};
}
/**
* Returns a function, that, as long as it continues to be invoked, will not be triggered. The function will be called after it stops being called for N milliseconds. If `immediate` is passed, trigger the function on the leading edge, instead of the trailing.
* @see {@link https://davidwalsh.name/javascript-debounce-function}
* @param {function} f Callback function
* @param {number} w Wait for miliseconds until next execution
* @param {boolean} i Do not wait
* @returns {function} func
* @example
* window.addEventListener('scroll', debounce(() => { console.log('hello'); }, 100));
*/
function debounce(f, w, i) {
/** @type {Number|null} Timeout */
let t;
return () => {
/** @type {*} Arguments */
const a = arguments;
/** @type {*} Context */
const c = this;
const later = () => {
t = null;
if (!i) f.apply(c, a);
};
/** @type {Boolean} Call now */
const n = i && !t;
clearTimeout(t);
t = setTimeout(later, w);
if (n) f.apply(c, a);
};
}
// Call on load
if (callOnLoad) {
func.call();
}
// Throttle
target.addEventListener(
eventListener,
throttle(() => {
return func.call();
}, time)
);
// Debounce
target.addEventListener(
eventListener,
debounce(() => {
func.call();
}, time)
);
}
Method Prev
Go to previous slide.
mySlider.Prev();
Method Next
Go to next slide.
mySlider.Next();
Method GoTo
Go to a specific slide.
mySlider.GoTo(3); // go to slide 3
GET
Get elements
Get all the sliders HTML-elements.
mySlider.elements.arrowNext // The HTMLButtonElement of the next arrow
mySlider.elements.arrowPrev // The HTMLButtonElement of the previous arrow
mySlider.elements.arrows // The element containing the arrows
mySlider.elements.caption // The element containing the caption
mySlider.elements.items // All slides as list items
mySlider.elements.list // The unordered list element containing all slides
mySlider.elements.pagination // The element containing the pagination
mySlider.elements.paginationBullets // All pagination bullets as HTMLButtonElement
Get config
Get slider configuration.
mySlider.config.arrows // `true` if the slider has arrows
mySlider.config.autoplay // `true` if autoplay is currently active
mySlider.config.autoplaySpeed // Autoplay speed
mySlider.config.caption // The slider caption
mySlider.config.slides // The number of slides
mySlider.config.pagination // `true` if the slider has a pagination
Styles
The slider does not come with any design or theme applied. It has only required functional styles. You may however apply some example styles.
Import
Some functional CSS is required to make the slider work. Make sure to import the SCSS from the node module.
@import "path-to/node_modules/@wanjapflueger/a11y-slider/lib/index";
Space between slides
Overwrite the default value for $space-between-slides
from src/partials/slide/_index.scss globally before you import the SCSS.
$space-between-slides: 80px;
@import "path-to/node_modules/@wanjapflueger/a11y-slider/lib/index";
If you do not want to set this value globally but per slider, continue reading Slides per view.
Hide every nth bullet
A single bullet exists for each slide. However if you have lots of slides this will look silly. You can hide some of those bullets like shown in the example below.
[data-a11y-slider] [data-a11y-slider-pagination] > nav > ul > li {
// Show only every fourth bullet (1, 5, 9 etc.) for tablet
&:not(:nth-child(4n + 1)) {
@media (max-width: 767px) {
display: none;
}
}
// Show only every second bullet (1, 3, 5, etc.) for tablet
&:not(:nth-child(2n + 1)) {
@media (min-width: 768px) and (max-width: 1024px) {
display: none;
}
}
}
Slides per view
Use @mixin slide-sizing
from src/partials/slide/_index.scss to define how many slides should be visible for each breakpoint.
[data-a11y-slider] {
@include slide-sizing(1); // Mobile: show 1 slide, space between slides is $space-between-slides
@media (min-width: 1280px) {
@include slide-sizing(2.5, 80); // Desktop: show 2 and a half slides, space between slides is 80px
}
}
Pro Tip: Even if you display only one slide with slide-sizing(1)
you can still provide a grid gap.
Tips and Tricks
Add a
margin
when using abox-shadow
:[data-a11y-slider-slide] { margin: 2em 0; // set margin according to the shadows dimensions }
Keyboard support
In src/partials/keys.ts several keys have been assigned a function. The keyboard support starts when a slider is active. A slider is active when:
- the user hovers the slider
- the focus is on the slider (
[data-a11y-slider]
) - any child element inside the slider has the focus (
[data-a11y-slider]
)
The slider will have [aria-current="true"]
while active.
| Key | Description |
| ------------ | -------------- |
| ArrowRight
| Next slide |
| ArrowLeft
| Previous slide |
| Digit1
| Slide 1 |
| Digit2
| Slide 2 |
| Digit3
| Slide 3 |
| Digit4
| Slide 4 |
| Digit5
| Slide 5 |
| Digit6
| Slide 6 |
| Digit7
| Slide 7 |
| Digit8
| Slide 8 |
| Digit9
| Slide 9 |
Dataset attributes
You may overwrite some JavaScript parameters with the following HTML attributes. Set these attributes on your reference element.
<div id="my-list" data-a11y-slider-autoplay="4000"> ... </div>
<div id="my-list" data-a11y-slider-lang="de"> ... </div>
<div id="my-list" data-a11y-slider-slide-by="2"> ... </div>
Issues
Event Listeners do not work
Issue: https://gitlab.com/wanjapflueger/a11y-slider/-/issues/1
As stated in How to copy a DOM node with event listeners?, event listeners applied to elements within the slider cannot be copied when creating a new slider instance.
To workaround this issue, apply any event listeners in a callback function, that can be passed with the parameter onCreated
.
Example:
slider.Create({
// ...
onCreated: () => {
const buttons = document.querySelectorAll('button');
buttons.forEach((button) => {
button.addEventListener('click', () => {
console.log('clicked on a button inside a slide');
})
});
}
// ...
});
Accessibility Compliance Report
WCAG Level: AAA [^1]
| Browser | Platform | Screen reader | Passed | | -------------------- | ------------- | -------------------------------------------------------------------------- | ------ | | Chrome 90.0.4430.212 | MacOS 10.15.7 | VoiceOver | ✅ | | Chrome 90.0.4430.210 | Android 10 | Talkback | ✅ |
[^1]: This information refers only to the technical aspects of the component, not to the design or the editorial handling of any content.