2.3.3 • Published 2 years ago

@wanjapflueger/a11y-slider v2.3.3

Weekly downloads
-
License
ISC
Repository
gitlab
Last release
2 years ago

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

  • 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

  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
ParameterRequiredTypeDefault value
slides✅ YesHTMLElement

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
ParameterRequiredTypeDefault value
arrows❌ Noboolean or Arrowsfalse

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
ParameterRequiredTypeDefault value
autoplay❌ Nonumberundefined

Autoplay speed in milliseconds.

Example:

const slider = new Slider()
slider.Create({
  // ...
  autoplay: 4000 | undefined
})
loop
ParameterRequiredTypeDefault value
loop❌ Nobooleanfalse

Go from the first to the last slide and the other way around.

Example:

const slider = new Slider()
slider.Create({
  // ...
  loop: true | false
})
caption
ParameterRequiredTypeDefault value
caption❌ Nostring'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
ParameterRequiredTypeDefault value
lang❌ Nostringdocument.documentElement.lang (the documents language)

Language Code ISO 639-1.

Example:

const slider = new Slider()
slider.Create({
  // ...
  lang: 'de' | 'es' | 'en' | undefined
})
pagination
ParameterRequiredTypeDefault value
pagination❌ Noboolean or Paginationfalse

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
ParameterRequiredTypeDefault value
slideBy❌ Nonumber1

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
ParameterRequiredTypeDefault value
syncWith❌ NoSliderundefined

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

KeyDescription
ArrowRightNext slide
ArrowLeftPrevious slide
Digit1Slide 1
Digit2Slide 2
Digit3Slide 3
Digit4Slide 4
Digit5Slide 5
Digit6Slide 6
Digit7Slide 7
Digit8Slide 8
Digit9Slide 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

BrowserPlatformScreen readerPassed
Chrome 90.0.4430.212MacOS 10.15.7VoiceOver
Chrome 90.0.4430.210Android 10Talkback

^1: This information refers only to the technical aspects of the component, not to the design or the editorial handling of any content.

2.2.0

2 years ago

2.3.0

2 years ago

2.3.2

2 years ago

2.3.1

2 years ago

2.3.3

2 years ago

2.1.0

2 years ago

1.6.4

2 years ago

1.6.3

2 years ago

1.6.2

2 years ago

1.6.1

2 years ago

2.0.3

2 years ago

2.0.2

2 years ago

2.0.4

2 years ago

2.0.1

2 years ago

2.0.0

2 years ago

1.7.0

2 years ago

1.5.2

2 years ago

1.6.0

2 years ago

1.5.1

2 years ago

1.5.0

2 years ago

1.4.3

3 years ago

1.4.2

3 years ago

1.4.1

3 years ago

1.4.0

3 years ago

1.3.1

3 years ago

1.3.0

3 years ago

1.2.0

3 years ago

1.1.2

3 years ago

1.1.1

3 years ago

1.0.5

3 years ago

1.0.4

3 years ago

1.0.2

3 years ago

1.0.1

3 years ago

1.0.0

3 years ago

0.0.25

3 years ago

1.0.3

3 years ago

0.0.26

3 years ago

0.0.20

3 years ago

0.0.21

3 years ago

0.0.22

3 years ago

0.0.23

3 years ago

0.0.24

3 years ago

0.0.18

3 years ago

0.0.19

3 years ago

0.0.17

3 years ago

0.0.16

3 years ago

0.0.15

3 years ago

0.0.14

3 years ago

0.0.13

3 years ago

0.0.12

3 years ago

0.0.11

3 years ago

0.0.10

3 years ago

0.0.9

3 years ago

0.0.8

3 years ago

0.0.7

3 years ago

0.0.6

3 years ago

0.0.5

3 years ago

0.0.4

3 years ago

0.0.3

3 years ago

0.0.2

3 years ago

0.0.1

3 years ago