@wanjapflueger/a11y-slider
TypeScript icon, indicating that this package has built-in type declarations

2.3.3 • Public • Published

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.

Pipeline status npm npm bundle size

a11y-slider screenshot

*Yes I know that the slider in the screenshot above has some example styles applied...


Table of contents


Requirements

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

  1. 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>
  2. Assign that reference element with JavaScript to a variable, initiate a new instance of Slider and call method Create.

    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 any Element 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:

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:

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:

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:

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:

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 a box-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.

  • autoplay

    <div id="my-list" data-a11y-slider-autoplay="4000">
      ...
    </div>
  • lang

    <div id="my-list" data-a11y-slider-lang="de">
      ...
    </div>
  • slideBy

    <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.

Package Sidebar

Install

npm i @wanjapflueger/a11y-slider

Weekly Downloads

0

Version

2.3.3

License

ISC

Unpacked Size

121 kB

Total Files

33

Last publish

Collaborators

  • wanjapflueger