Auto-Rotating Image Carousel Example
-- NOTE: This is a new example as of the January 2019 release of WAI-ARIA Authoring Practices 1.1. - Please provide feedback in - issue 971. -
The following example implementation of the carousel design pattern @@ -43,36 +46,62 @@
Auto-Rotating Image Carousel Example
The accessibility features section that follows the example describes these features in detail. +Example View Options
+-
+
- Carousel controls and captions displayed on image: This option is less accessible than the other option because the controls are harder to perceive and captions are more difficult to read with rotating images behind them. +
- Carousel controls and captions displayed above and below the image: This option is preferable because controls and captions are easier to perceive. +
Example
- Survey confirms that TV is America’s most trusted institution
+ Great Children's Programming on Public TV
@@ -159,7 +188,7 @@
- 8 pm Sunday, March 8, on TV: Sneak peak at the final season.
+ 8 pm Sunday, March 8, on TV: Sneak peak at the final season.
@@ -200,7 +229,7 @@
@@ -221,38 +250,54 @@
Accessibility Features
+ Controlling Automatic Slide Rotation
+
+ Users can stop and start slide rotation, which is an essential aspect of accessibility of the carousel for a variety of people with disabilities.
+ People with low vision or a cognitive disability that effects visual processing or reading benefit from being able to control slide rotation so they have sufficient time to explore slide content.
+ Similarly, since screen reader users cannot perceive automatic rotation, it can make reading the page confusing and disorienting.
+ For example, when slides are automatically rotating, a screen reader user may read an element on slide one, execute a screen reader command to read the next element, and, instead of hearing the next element on slide one, hear an element from slide 2 without any knowledge that the element just announced is from an entirely new context.
+
+ This example includes the following features for giving users control over slide rotation:
- - Automatic rotation of slides in the carousel can be easily controlled by any user.
+
-
+ Hovering the mouse over any carousel content pauses automatic rotation.
+ Automatic rotation resumes when the mouse moves away from the carousel unless another condition, such as keyboard focus, that prevents rotation has been triggered.
+
+ -
+ Moving keyboard focus to any of the carousel content, including the next and previous slide elements, pauses automatic rotation.
+ Automatic rotation resumes when keyboard focus moves out of the carousel content unless another condition, such as mouse hover, that prevents rotation has been triggered.
+
+ - The carousel also contains a rotation control button that can stop and start automatic rotation.
- -
- Hovering the mouse over any carousel content pauses automatic rotation.
- Automatic rotation resumes when the mouse moves away from the carousel unless another condition, such as keyboard focus, that prevents rotation has been triggered.
-
- -
- Moving keyboard focus to any of the carousel content, including the next and previous slide elements, pauses automatic rotation.
- Automatic rotation resumes when keyboard focus moves out of the carousel content unless another condition, such as mouse hover, that prevents rotation has been triggered.
-
- - The carousel also contains a rotation control button that can stop and start automatic rotation.
-
- - The rotation control button is the first element in the screen reader reading order.
- - The button is hidden off-screen when it does not have keyboard focus, and becomes visible when users move focus to it with Tab.
- -
- When not visible, the button is positioned off-screen, instead of being hidden with
display:none
, so it is always available to screen reader users.
- This ensures the rotation control is accessible to people using a screen reader that does not move the mouse or keyboard focus when the user is moving its reading cursor.
-
- - If the carousel is rotating, the button is labeled "Stop Automatic Slide Show," informing screen reader users that the slides are changing in addition to providing a way to stop the changes.
- - If the carousel is not rotating, the button is labeled "Start Automatic Slide Show."
- - If a user has activated the button to stop the show, the rotation will only restart if the button is activated. Moving keyboard focus or the mouse out of the carousel will not restart rotation.
- - If keyboard focus is inside the carousel, or if the mouse is hovering over the carousel, the button is disabled; it cannot be used to start rotation.
-
-
+ - The rotation control button is the first element in the screen reader reading order.
+ - The rotation control button is always visible so it is available to all users whether they are interacting via a mouse, keyboard, assistive technology, or touch.
+ - If the carousel is rotating, its accessible name is
Stop Automatic Slide Show
, informing screen reader users that the slides are changing in addition to providing a way to stop the changes.
+ - If the carousel is not rotating, the accessible name of the button is
Start Automatic Slide Show
.
+ - If a user has activated the button to stop the show, the rotation will only restart if the button is activated. Moving keyboard focus or the mouse out of the carousel will not restart rotation.
+ - If keyboard focus is inside the carousel, or if the mouse is hovering over the carousel, the button is disabled; it cannot be used to start rotation.
+
+ Color Contrast of Text and Rotation Controls
+
+ In the view of this carousel where the controls and captions are displayed on top of the image, the background images can cause color contrast for the controls and text to become insufficient.
+ This view includes the following features to meet WCAG 2.1 color contrast requirements:
+
+
-
- To improve readability of the caption text and content, the transparency of the caption area is decreased when the mouse hovers over the carousel content.
- This improves the color contrast ratio of the white text, especially when light colored images are present in the background.
+ When the rotation control, next slide, and previous slide buttons are rendered on top of the carousel images, the buttons have forground and background colors that meet WCAG 2.1 color contrast requirements.
+ In addition, the focus styling uses SVG images that make the focus indicator highly visible when a control receives keyboard focus.
+ - The transparency of the caption area is decreased so the caption text meets the WCAG 2.1 color contrast requirements.
+ Screen Reader Announcement of Slide Changes
+
+ When automatic rotation is turned off, the carousel slide content is included in a live region.
+ This makes it easier for screen reader users to scan through the carousel slides.
+ When screen reader users activate the next or previous slide button , the new slide content is announced, giving users immediate feedback that helps them determine whether or not to interact with the content.
+ Very importantly, if automatic rotation is turned on, the live region is disabled.
+ If it were not, the page would be come unusable as announcements of the continuously changing content constantly interrupt anything else the user is reading.
+
@@ -273,8 +318,6 @@ Rotation Control Button
- Moves focus through interactive elements in the carousel.
- Rotation control, previous slide, and next slide buttons precede the slide content in the Tab sequence.
- - The rotation control button moves on screen when it receives keyboard focus and off screen when it is not focused.
- - Off-screen positioning allows it to be available to screen reader users when not focused.
@@ -359,7 +402,7 @@ Role, Property, State, and Tabindex Attributes
Provides a label that describes the content in the carousel region.
-
+
aria-live=off
@@ -375,7 +418,7 @@ Role, Property, State, and Tabindex Attributes
-
+
aria-live=polite
@@ -436,42 +479,15 @@ Role, Property, State, and Tabindex Attributes
-
-
-
- aria-disabled="true"
-
-
- button
-
-
-
- - Applied to the automatic rotation control button when rotation is stopped and conditions that prevent automatic rotation are present.
- - The button is disabled when a pointer is hovering over the carousel or any element in the carousel has keyboard focus.
- - The
aria-disabled
state is used instead of the HTML disabled
attribute to keep the button in the Tab sequence.
- - If hover or focus is removed from the carousel, the
aria-disabled
attribute is removed from the button.
-
-
-
-
-
- button
-
-
-
- a
-
- Identifies the element as a button
.
-
-
+
aria-label="LABEL_STRING"
- a
+ button
- Defines the accessible name for the next and previous slide buttons.
+ Defines the accessible name for the pause auto-rotation button and the next and previous slide buttons.
@@ -479,7 +495,7 @@ Role, Property, State, and Tabindex Attributes
aria-controls="IDREF"
- a
+ button
@@ -495,7 +511,7 @@ Role, Property, State, and Tabindex Attributes
Javascript and CSS Source Code
- - CSS: carousel.css
+ - CSS: carousel-1.css
- Javascript: carousel.js
- Javascript: carouselItem.js
- Javascript: carouselButtons.js
diff --git a/examples/carousel/carousel-1/css/carousel.css b/examples/carousel/carousel-1/css/carousel.css
deleted file mode 100644
index 11ce7cd722..0000000000
--- a/examples/carousel/carousel-1/css/carousel.css
+++ /dev/null
@@ -1,176 +0,0 @@
-
-/* .carousel */
-.carousel-item {
- display: none;
- max-height: 400px;
- max-width: 900px;
- position: relative;
- overflow: hidden;
- width: 100%;
-}
-
-.carousel .carousel-item.active {
- display: block;
-}
-
-/* More like bootstrap, less accessible */
-
-.carousel .carousel-inner {
- max-width: 900px;
- position: relative;
-}
-
-.carousel button.pause {
- display: block;
- font-size: 20px;
- width: auto;
- left: -300em;
- margin-bottom: 10px;
- height: auto;
- position: relative;
- top: 5px;
- right: -20px;
- border: thin solid outset;
-}
-
-.carousel button[aria-disabled=true] {
- color: #666;
-}
-
-.carousel button.pause:focus {
- display: block;
- position: relative;
- font-size: 20px;
- width: auto;
- left: 0;
- margin-bottom: 10px;
- height: auto;
- top: 5px;
- right: -20px;
-}
-
-.carousel .carousel-items {
- border: solid 2px transparent;
-}
-
-.carousel .carousel-items.focus {
- border-color: white;
- outline: solid 3px #005a9c;
-}
-
-.carousel .carousel-inner .carousel-image a img {
- height: 100%;
- width: 100%;
-}
-
-.carousel .carousel-inner .carousel-caption a {
- text-decoration: underline;
- border: none;
-}
-
-.carousel .carousel-inner .carousel-caption h3 a {
- color: #fff;
- font-weight: 600;
-}
-
-.carousel .carousel-inner .carousel-caption a:focus,
-.carousel .carousel-inner .carousel-caption a:hover {
- outline: solid 2px #fff;
- outline-offset: 1px;
-}
-
-.carousel .carousel-inner .carousel-caption p {
- font-size: 1em;
- line-height: 1.5;
- margin-bottom: 0;
-}
-
-.carousel .carousel-caption {
- position: absolute;
- right: 15%;
- bottom: 0;
- left: 15%;
- padding-top: 20px;
- padding-bottom: 20px;
- color: #fff;
- text-align: center;
- text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
-}
-
-.carousel .carousel-inner .carousel-caption {
- bottom: 0;
- left: 0;
- padding: 3% 3% 50px;
- right: 0;
- text-shadow: none;
-}
-
-.carousel:hover .carousel-inner .carousel-caption,
-.carousel .carousel-item.focus .carousel-caption {
- background-color: rgba(0, 0, 0, 0.4);
-}
-
-.carousel .carousel-inner,
-.carousel .carousel-item,
-.carousel .carousel-slide {
- max-height: 400px;
-}
-
-.carousel .carousel-control {
- position: absolute;
- top: 0;
- z-index: 10;
- font-size: 200%;
- font-weight: bold;
- color: #fff;
- text-align: center;
- text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
-}
-
-.carousel a.carousel-control svg {
- position: relative;
- display: inline-block;
- top: 45%;
-}
-
-.carousel a.carousel-control svg polygon {
- opacity: 0.7;
-}
-
-.carousel a.carousel-control:focus {
- border: 3px solid #fff;
- outline: 1px solid #005a9c;
-}
-
-.carousel a.carousel-control:focus svg polygon,
-.carousel a.carousel-control:hover svg polygon {
- opacity: 1;
-}
-
-.carousel a.carousel-control.previous {
- bottom: 0;
- width: 15%;
- background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%);
-}
-
-.carousel a.carousel-control.previous:focus,
-.carousel a.carousel-control.previous:hover {
- bottom: 0;
- width: 15%;
- background-image: linear-gradient(to right, rgba(0, 0, 0, 0.7) 0, rgba(0, 0, 0, 0.0001) 100%);
-}
-
-.carousel a.carousel-control.next {
- right: 0;
- bottom: 0;
- width: 15%;
- background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%);
-}
-
-.carousel a.carousel-control.next:focus,
-.carousel a.carousel-control.next:hover {
- right: 0;
- bottom: 0;
- width: 15%;
- background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.7) 100%);
-}
diff --git a/examples/carousel/carousel-1/js/pauseButton.js b/examples/carousel/carousel-1/js/pauseButton.js
deleted file mode 100644
index ebd875ee56..0000000000
--- a/examples/carousel/carousel-1/js/pauseButton.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
-* File: pasueButton.js
-*
-* Desc: Implements the pause button for the carousel widget
-*
-*/
-
-var PauseButton = function (domNode, carouselObj) {
- this.domNode = domNode;
-
- this.carousel = carouselObj;
-};
-
-var StartButton = function (domNode, carouselObj) {
- this.domNode = domNode;
-
- this.carousel = carouselObj;
-};
-
-PauseButton.prototype.init = function () {
- this.domNode.addEventListener('click', this.handleClick.bind(this));
- this.domNode.addEventListener('focus', this.handleFocus.bind(this));
- this.domNode.addEventListener('blur', this.handleBlur.bind(this));
-};
-
-/* EVENT HANDLERS */
-
-PauseButton.prototype.handleClick = function (event) {
- this.carousel.toggleRotation();
-};
-
-PauseButton.prototype.handleFocus = function (event) {
- this.domNode.classList.add('focus');
-};
-
-PauseButton.prototype.handleBlur = function (event) {
- this.domNode.classList.remove('focus');
-};
diff --git a/examples/carousel/css/carousel-1-more-accessible.css b/examples/carousel/css/carousel-1-more-accessible.css
new file mode 100644
index 0000000000..4342f03dd6
--- /dev/null
+++ b/examples/carousel/css/carousel-1-more-accessible.css
@@ -0,0 +1,185 @@
+/* .carousel */
+
+.carousel .carousel-inner {
+ display: static;
+}
+
+.carousel .carousel-item {
+ display: none;
+ max-width: 900px;
+ width: 100%;
+}
+
+.carousel .carousel-item.active {
+ display: block;
+}
+
+/* More like bootstrap, less accessible */
+
+/* Shared CSS for Pause, Next and Previous Slide Controls */
+
+.carousel .controls {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ position: relative;
+ height: 36px;
+ background-color: #eee;
+ border: 4px solid #eee;
+ border-radius: 5px 5px 0 0;
+}
+
+.carousel .controls button {
+ position: absolute;
+ top: 6px;
+ display: block;
+ background-color: transparent;
+ border: none;
+ outline: none;
+}
+
+.carousel .controls button.previous {
+ right: 58px;
+}
+
+.carousel .controls button.next {
+ right: 4px;
+}
+
+.carousel .controls button.rotation {
+ left: 4px;
+}
+
+.carousel .controls button svg rect.background {
+ stroke: black;
+ fill: black;
+ stroke-width: 1px;
+ opacity: 0.8;
+}
+
+.carousel .controls button svg rect.border {
+ fill: transparent;
+ stroke: transparent;
+ stroke-width: 2px;
+}
+
+/* Next and Previous Slide Controls */
+
+.carousel .controls button svg polygon {
+ stroke: white;
+ fill: white;
+ stroke-width: 2;
+ opacity: 1;
+}
+
+.carousel .controls button.rotation svg polygon.pause {
+ stroke-width: 4;
+ fill: transparent;
+ stroke: transparent;
+}
+
+.carousel .controls button.rotation svg polygon.play {
+ stroke-width: 1;
+ fill: transparent;
+ stroke: transparent;
+}
+
+.carousel .controls button.rotation.pause svg polygon.pause,
+.carousel .controls button.rotation.play svg polygon.play {
+ fill: white;
+ stroke: white;
+}
+
+/* Common focus styling for svg buttons */
+
+.carousel .controls button:focus rect.background,
+.carousel .controls button:hover rect.background,
+.carousel .controls button:focus rect.border,
+.carousel .controls button:hover rect.border {
+ fill: #005a9c;
+ stroke: #005a9c;
+ opacity: 1;
+}
+
+.carousel .controls button:focus rect.border {
+ stroke: white;
+}
+
+/* Caption Positioning */
+
+.carousel .carousel-items {
+ width: 100%;
+ background-color: #eee;
+ border: solid 4px #eee;
+ border-radius: 0 0 5px 5px;
+}
+
+.carousel .carousel-items.focus {
+ border-color: #005a9c;
+}
+
+.carousel .carousel-item .carousel-image {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+}
+
+.carousel .carousel-item .carousel-image a {
+ margin: 0;
+ padding: 0;
+}
+
+.carousel .carousel-item .carousel-image a img {
+ margin: 0;
+ padding: 0;
+ display: block;
+ overflow: hidden;
+ max-height: 100%;
+ max-width: 100%;
+}
+
+.carousel .carousel-item .carousel-caption {
+ margin: 0;
+ padding: 0.5em;
+ width: 100%;
+ height: 3em;
+ text-align: center;
+}
+
+.carousel .carousel-item .carousel-caption a {
+ display: inline-block;
+ background-color: rgba(0, 0, 0, 0);
+ padding-left: 0.25em;
+ padding-right: 0.25em;
+ padding-top: 0.125em;
+ padding-bottom: 0.125em;
+ border-radius: 5px;
+ border: 2px solid transparent;
+ margin: 0;
+ text-decoration: underline;
+}
+
+.carousel .carousel-item .carousel-caption h3 {
+ margin: 0;
+ padding: 0;
+ font-weight: bold;
+}
+
+.carousel .carousel-item .carousel-caption h3 a {
+ color: black;
+}
+
+.carousel .carousel-item .carousel-caption a:hover {
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
+.carousel .carousel-item .carousel-caption a:focus {
+ border-color: #005a9c;
+ background-color: rgba(0, 0, 0, 0.1);
+ outline: none;
+}
+
+.carousel .carousel-item .carousel-caption p {
+ margin: 0;
+ padding: 0;
+}
diff --git a/examples/carousel/css/carousel-1.css b/examples/carousel/css/carousel-1.css
new file mode 100644
index 0000000000..735f61e3db
--- /dev/null
+++ b/examples/carousel/css/carousel-1.css
@@ -0,0 +1,165 @@
+
+/* .carousel */
+
+.carousel .carousel-inner {
+ position: relative;
+}
+
+.carousel .carousel-item {
+ display: none;
+ max-height: 400px;
+ max-width: 900px;
+ position: relative;
+ overflow: hidden;
+ width: 100%;
+}
+
+.carousel .carousel-item.active {
+ display: block;
+}
+
+/* More like bootstrap, less accessible */
+
+.carousel .carousel-items {
+ border: solid 2px transparent;
+}
+
+.carousel .carousel-items.focus {
+ border-color: white;
+ outline: solid 3px #005a9c;
+}
+
+.carousel .carousel-item .carousel-image a img {
+ height: 100%;
+ width: 100%;
+}
+
+.carousel .carousel-item .carousel-caption a {
+ text-decoration: underline;
+}
+
+.carousel .carousel-item .carousel-caption a,
+.carousel .carousel-item .carousel-caption span.contrast {
+ display: inline-block;
+ background-color: rgba(0, 0, 0, 0.65);
+ padding-left: 0.25em;
+ padding-right: 0.25em;
+ border-radius: 5px;
+ border: 2px solid transparent;
+ margin: 0;
+}
+
+.carousel .carousel-item .carousel-caption h3 a {
+ color: #fff;
+ font-weight: 600;
+}
+
+.carousel .carousel-item .carousel-caption a:hover,
+.carousel .carousel-item .carousel-caption span.contrast:hover {
+ background-color: rgba(0, 0, 0, 1);
+ margin: 0;
+}
+
+.carousel .carousel-item .carousel-caption a:focus {
+ background-color: rgba(0, 0, 0, 1);
+ border-color: #fff;
+ margin: 0;
+}
+
+.carousel .carousel-item .carousel-caption p {
+ font-size: 1em;
+ line-height: 1.5;
+ margin-bottom: 0;
+}
+
+.carousel .carousel-item .carousel-caption {
+ position: absolute;
+ right: 15%;
+ bottom: 0;
+ left: 15%;
+ padding-top: 20px;
+ padding-bottom: 20px;
+ color: #fff;
+ text-align: center;
+}
+
+/* Shared CSS for Pause, Next and Previous Slide Controls */
+
+.carousel .controls button {
+ padding: 0;
+ position: absolute;
+ top: 5px;
+ z-index: 10;
+ background-color: transparent;
+ border: none;
+ outline: none;
+}
+
+.carousel .controls button svg rect.background {
+ stroke: black;
+ fill: black;
+ stroke-width: 1px;
+ opacity: 0.6;
+}
+
+.carousel .controls button svg rect.border {
+ fill: transparent;
+ stroke: transparent;
+ stroke-width: 2px;
+}
+
+/* Next and Previous Slide Controls */
+
+.carousel .controls button svg polygon {
+ stroke: white;
+ fill: white;
+ stroke-width: 2;
+ opacity: 1;
+}
+
+.carousel .controls button.previous {
+ right: 50px;
+}
+
+.carousel .controls button.next {
+ right: 6px;
+}
+
+/* Pause Control */
+
+.carousel .controls button.rotation {
+ left: 6px;
+}
+
+.carousel .controls button.rotation svg polygon.pause {
+ stroke-width: 4;
+ fill: transparent;
+ stroke: transparent;
+}
+
+.carousel .controls button.rotation svg polygon.play {
+ stroke-width: 1;
+ fill: transparent;
+ stroke: transparent;
+}
+
+.carousel .controls button.rotation.pause svg polygon.pause,
+.carousel .controls button.rotation.play svg polygon.play {
+ fill: white;
+ stroke: white;
+}
+
+/* Common focus styling for svg buttons */
+
+.carousel .controls button:focus rect.background,
+.carousel .controls button:hover rect.background,
+.carousel .controls button:focus rect.border,
+.carousel .controls button:hover rect.border {
+ fill: #005a9c;
+ stroke: #005a9c;
+ opacity: 1;
+}
+
+.carousel .controls button:focus rect.border {
+ stroke: white;
+}
diff --git a/examples/carousel/carousel-1/images/amsterdamslide__800x600.jpg b/examples/carousel/images/amsterdamslide__800x600.jpg
similarity index 100%
rename from examples/carousel/carousel-1/images/amsterdamslide__800x600.jpg
rename to examples/carousel/images/amsterdamslide__800x600.jpg
diff --git a/examples/carousel/carousel-1/images/britcomdavidslide__800x600.jpg b/examples/carousel/images/britcomdavidslide__800x600.jpg
similarity index 100%
rename from examples/carousel/carousel-1/images/britcomdavidslide__800x600.jpg
rename to examples/carousel/images/britcomdavidslide__800x600.jpg
diff --git a/examples/carousel/carousel-1/images/foyleswarslide__800x600.jpg b/examples/carousel/images/foyleswarslide__800x600.jpg
similarity index 100%
rename from examples/carousel/carousel-1/images/foyleswarslide__800x600.jpg
rename to examples/carousel/images/foyleswarslide__800x600.jpg
diff --git a/examples/carousel/carousel-1/images/lands-endslide__800x600.jpg b/examples/carousel/images/lands-endslide__800x600.jpg
similarity index 100%
rename from examples/carousel/carousel-1/images/lands-endslide__800x600.jpg
rename to examples/carousel/images/lands-endslide__800x600.jpg
diff --git a/examples/carousel/carousel-1/images/mag800-2__800x600.jpg b/examples/carousel/images/mag800-2__800x600.jpg
similarity index 100%
rename from examples/carousel/carousel-1/images/mag800-2__800x600.jpg
rename to examples/carousel/images/mag800-2__800x600.jpg
diff --git a/examples/carousel/carousel-1/images/trustslide-2__800x600.jpg b/examples/carousel/images/trustslide-2__800x600.jpg
similarity index 100%
rename from examples/carousel/carousel-1/images/trustslide-2__800x600.jpg
rename to examples/carousel/images/trustslide-2__800x600.jpg
diff --git a/examples/carousel/carousel-1/js/carousel.js b/examples/carousel/js/carousel.js
similarity index 68%
rename from examples/carousel/carousel-1/js/carousel.js
rename to examples/carousel/js/carousel.js
index 9e3071e921..96e79aeaef 100644
--- a/examples/carousel/carousel-1/js/carousel.js
+++ b/examples/carousel/js/carousel.js
@@ -22,8 +22,8 @@ var Carousel = function (domNode) {
this.currentItem = null;
this.pauseButton = null;
- this.startLabel = 'Start automatic slide show';
- this.stopLabel = 'Stop automatic slide show';
+ this.playLabel = 'Start automatic slide show';
+ this.pauseLabel = 'Stop automatic slide show';
this.rotate = true;
this.hasFocus = false;
@@ -34,12 +34,14 @@ var Carousel = function (domNode) {
Carousel.prototype.init = function () {
+ var elems, elem, button, items, item, imageLinks, i;
+
this.liveRegionNode = this.domNode.querySelector('.carousel-items');
- var items = this.domNode.querySelectorAll('.carousel-item');
+ items = this.domNode.querySelectorAll('.carousel-item');
- for (var i = 0; i < items.length; i++) {
- var item = new CarouselItem(items[i], this);
+ for (i = 0; i < items.length; i++) {
+ item = new CarouselItem(items[i], this);
item.init();
this.items.push(item);
@@ -50,7 +52,7 @@ Carousel.prototype.init = function () {
}
this.lastItem = item;
- var imageLinks = items[i].querySelectorAll('.carousel-image a');
+ imageLinks = items[i].querySelectorAll('.carousel-image a');
if (imageLinks && imageLinks[0]) {
imageLinks[0].addEventListener('focus', this.handleImageLinkFocus.bind(this));
@@ -59,27 +61,28 @@ Carousel.prototype.init = function () {
}
- // Next Slide and Previous Slide Buttons
+ // Pause, Next Slide and Previous Slide Buttons
- var elems = document.querySelectorAll('.carousel a.carousel-control');
+ elems = document.querySelectorAll('.carousel .controls button');
- for (var i = 0; i < elems.length; i++) {
- if (elems[i].tagName.toLowerCase() == 'a') {
- var button = new CarouselButton(elems[i], this);
+ for (i = 0; i < elems.length; i++) {
+ elem = elems[i];
- button.init();
+ if (elem.classList.contains('rotation')) {
+ button = new PauseButton(elem, this);
+ this.pauseButton = elem;
+ this.pauseButton.classList.add('pause');
+ this.pauseButton.setAttribute('aria-label', this.pauseLabel);
+ }
+ else {
+ button = new CarouselButton(elem, this);
}
- }
-
- this.currentItem = this.firstItem;
- this.pauseButton = this.domNode.parentNode.parentNode.querySelector('button.pause');
- if (this.pauseButton) {
- var button = new PauseButton(this.pauseButton, this);
button.init();
- this.pauseButton.innerHTML = this.stopLabel;
}
+ this.currentItem = this.firstItem;
+
this.domNode.addEventListener('mouseover', this.handleMouseOver.bind(this));
this.domNode.addEventListener('mouseout', this.handleMouseOut.bind(this));
@@ -151,42 +154,42 @@ Carousel.prototype.rotateSlides = function () {
setTimeout(this.rotateSlides.bind(this), this.timeInterval);
};
-Carousel.prototype.startRotation = function () {
+Carousel.prototype.updateRotation = function () {
+
if (!this.hasHover && !this.hasFocus && !this.isStopped) {
this.rotate = true;
this.liveRegionNode.setAttribute('aria-live', 'off');
- this.pauseButton.innerHTML = this.stopLabel;
}
- this.disablePauseButton();
-};
-
-Carousel.prototype.stopRotation = function () {
- this.rotate = false;
- this.liveRegionNode.setAttribute('aria-live', 'polite');
- this.pauseButton.innerHTML = this.startLabel;
- this.disablePauseButton();
-};
+ else {
+ this.rotate = false;
+ this.liveRegionNode.setAttribute('aria-live', 'polite');
+ }
-Carousel.prototype.disablePauseButton = function () {
- if (this.hasHover || this.hasFocus) {
- this.pauseButton.setAttribute('aria-disabled', 'true');
+ if (this.isStopped) {
+ this.pauseButton.setAttribute('aria-label', this.playLabel);
+ this.pauseButton.classList.remove('pause');
+ this.pauseButton.classList.add('play');
}
else {
- this.pauseButton.removeAttribute('aria-disabled');
+ this.pauseButton.setAttribute('aria-label', this.pauseLabel);
+ this.pauseButton.classList.remove('play');
+ this.pauseButton.classList.add('pause');
}
+
};
Carousel.prototype.toggleRotation = function () {
if (this.isStopped) {
- if (this.pauseButton.getAttribute('aria-disabled') !== 'true') {
+ if (!this.hasHover && !this.hasFocus) {
this.isStopped = false;
- this.startRotation();
}
}
else {
this.isStopped = true;
- this.stopRotation();
}
+
+ this.updateRotation();
+
};
Carousel.prototype.handleImageLinkFocus = function () {
@@ -197,19 +200,21 @@ Carousel.prototype.handleImageLinkBlur = function () {
this.liveRegionNode.classList.remove('focus');
};
-Carousel.prototype.handleMouseOver = function () {
- this.hasHover = true;
- this.stopRotation();
+Carousel.prototype.handleMouseOver = function (event) {
+ if (!this.pauseButton.contains(event.target)) {
+ this.hasHover = true;
+ }
+ this.updateRotation();
};
Carousel.prototype.handleMouseOut = function () {
this.hasHover = false;
- this.startRotation();
+ this.updateRotation();
};
/* Initialize Carousel Tablists */
-window.addEventListener('load', function (event) {
+window.addEventListener('load', function () {
var carousels = document.querySelectorAll('.carousel');
for (var i = 0; i < carousels.length; i++) {
diff --git a/examples/carousel/carousel-1/js/carouselButtons.js b/examples/carousel/js/carouselButtons.js
similarity index 73%
rename from examples/carousel/carousel-1/js/carouselButtons.js
rename to examples/carousel/js/carouselButtons.js
index 22bde956e9..82c6396775 100644
--- a/examples/carousel/carousel-1/js/carouselButtons.js
+++ b/examples/carousel/js/carouselButtons.js
@@ -33,7 +33,6 @@ var CarouselButton = function (domNode, carouselObj) {
};
CarouselButton.prototype.init = function () {
- this.domNode.addEventListener('keydown', this.handleKeydown.bind(this));
this.domNode.addEventListener('click', this.handleClick.bind(this));
this.domNode.addEventListener('focus', this.handleFocus.bind(this));
this.domNode.addEventListener('blur', this.handleBlur.bind(this));
@@ -51,26 +50,6 @@ CarouselButton.prototype.changeItem = function () {
/* EVENT HANDLERS */
-CarouselButton.prototype.handleKeydown = function (event) {
- var flag = false;
-
- switch (event.keyCode) {
- case this.keyCode.SPACE:
- case this.keyCode.RETURN:
- this.changeItem();
- this.domNode.focus();
- flag = true;
- break;
-
- default:
- break;
- }
-
- if (flag) {
- event.stopPropagation();
- event.preventDefault();
- }
-};
CarouselButton.prototype.handleClick = function (event) {
this.changeItem();
@@ -79,11 +58,11 @@ CarouselButton.prototype.handleClick = function (event) {
CarouselButton.prototype.handleFocus = function (event) {
this.carousel.hasFocus = true;
this.domNode.classList.add('focus');
- this.carousel.stopRotation();
+ this.carousel.updateRotation();
};
CarouselButton.prototype.handleBlur = function (event) {
this.carousel.hasFocus = false;
this.domNode.classList.remove('focus');
- this.carousel.startRotation();
+ this.carousel.updateRotation();
};
diff --git a/examples/carousel/carousel-1/js/carouselItem.js b/examples/carousel/js/carouselItem.js
similarity index 93%
rename from examples/carousel/carousel-1/js/carouselItem.js
rename to examples/carousel/js/carouselItem.js
index 7ed5417f63..dc1f1d1513 100644
--- a/examples/carousel/carousel-1/js/carouselItem.js
+++ b/examples/carousel/js/carouselItem.js
@@ -32,11 +32,11 @@ CarouselItem.prototype.show = function () {
CarouselItem.prototype.handleFocusIn = function (event) {
this.domNode.classList.add('focus');
this.carousel.hasFocus = true;
- this.carousel.stopRotation();
+ this.carousel.updateRotation();
};
CarouselItem.prototype.handleFocusOut = function (event) {
this.domNode.classList.remove('focus');
this.carousel.hasFocus = false;
- this.carousel.startRotation();
+ this.carousel.updateRotation();
};
diff --git a/examples/carousel/js/pauseButton.js b/examples/carousel/js/pauseButton.js
new file mode 100644
index 0000000000..833e274307
--- /dev/null
+++ b/examples/carousel/js/pauseButton.js
@@ -0,0 +1,22 @@
+/*
+* File: pasueButton.js
+*
+* Desc: Implements the pause button for the carousel widget
+*
+*/
+
+var PauseButton = function (domNode, carouselObj) {
+ this.domNode = domNode;
+
+ this.carousel = carouselObj;
+};
+
+PauseButton.prototype.init = function () {
+ this.domNode.addEventListener('click', this.handleClick.bind(this));
+};
+
+/* EVENT HANDLERS */
+
+PauseButton.prototype.handleClick = function () {
+ this.carousel.toggleRotation();
+};
diff --git a/index.html b/index.html
index 43d1fe56ac..bd370078ef 100644
--- a/index.html
+++ b/index.html
@@ -786,7 +786,7 @@
This document is governed by the
1 March 2019 W3C Process Document.
-
8 pm Sunday, March 8, on TV: Sneak peak at the final season.
+8 pm Sunday, March 8, on TV: Sneak peak at the final season.
Accessibility Features
+Controlling Automatic Slide Rotation
++ Users can stop and start slide rotation, which is an essential aspect of accessibility of the carousel for a variety of people with disabilities. + People with low vision or a cognitive disability that effects visual processing or reading benefit from being able to control slide rotation so they have sufficient time to explore slide content. + Similarly, since screen reader users cannot perceive automatic rotation, it can make reading the page confusing and disorienting. + For example, when slides are automatically rotating, a screen reader user may read an element on slide one, execute a screen reader command to read the next element, and, instead of hearing the next element on slide one, hear an element from slide 2 without any knowledge that the element just announced is from an entirely new context. +
+This example includes the following features for giving users control over slide rotation:
-
-
- Automatic rotation of slides in the carousel can be easily controlled by any user. +
- + Hovering the mouse over any carousel content pauses automatic rotation. + Automatic rotation resumes when the mouse moves away from the carousel unless another condition, such as keyboard focus, that prevents rotation has been triggered. + +
- + Moving keyboard focus to any of the carousel content, including the next and previous slide elements, pauses automatic rotation. + Automatic rotation resumes when keyboard focus moves out of the carousel content unless another condition, such as mouse hover, that prevents rotation has been triggered. + +
- The carousel also contains a rotation control button that can stop and start automatic rotation.
-
-
- - Hovering the mouse over any carousel content pauses automatic rotation. - Automatic rotation resumes when the mouse moves away from the carousel unless another condition, such as keyboard focus, that prevents rotation has been triggered. - -
- - Moving keyboard focus to any of the carousel content, including the next and previous slide elements, pauses automatic rotation. - Automatic rotation resumes when keyboard focus moves out of the carousel content unless another condition, such as mouse hover, that prevents rotation has been triggered. - -
- The carousel also contains a rotation control button that can stop and start automatic rotation.
-
-
-
- The rotation control button is the first element in the screen reader reading order. -
- The button is hidden off-screen when it does not have keyboard focus, and becomes visible when users move focus to it with Tab. -
-
- When not visible, the button is positioned off-screen, instead of being hidden with
display:none
, so it is always available to screen reader users. - This ensures the rotation control is accessible to people using a screen reader that does not move the mouse or keyboard focus when the user is moving its reading cursor. -
- - If the carousel is rotating, the button is labeled "Stop Automatic Slide Show," informing screen reader users that the slides are changing in addition to providing a way to stop the changes. -
- If the carousel is not rotating, the button is labeled "Start Automatic Slide Show." -
- If a user has activated the button to stop the show, the rotation will only restart if the button is activated. Moving keyboard focus or the mouse out of the carousel will not restart rotation. -
- If keyboard focus is inside the carousel, or if the mouse is hovering over the carousel, the button is disabled; it cannot be used to start rotation. -
+ - The rotation control button is the first element in the screen reader reading order. +
- The rotation control button is always visible so it is available to all users whether they are interacting via a mouse, keyboard, assistive technology, or touch. +
- If the carousel is rotating, its accessible name is
Stop Automatic Slide Show
, informing screen reader users that the slides are changing in addition to providing a way to stop the changes.
+ - If the carousel is not rotating, the accessible name of the button is
Start Automatic Slide Show
.
+ - If a user has activated the button to stop the show, the rotation will only restart if the button is activated. Moving keyboard focus or the mouse out of the carousel will not restart rotation. +
- If keyboard focus is inside the carousel, or if the mouse is hovering over the carousel, the button is disabled; it cannot be used to start rotation.
+
Color Contrast of Text and Rotation Controls
++ In the view of this carousel where the controls and captions are displayed on top of the image, the background images can cause color contrast for the controls and text to become insufficient. + This view includes the following features to meet WCAG 2.1 color contrast requirements: +
+- - To improve readability of the caption text and content, the transparency of the caption area is decreased when the mouse hovers over the carousel content. - This improves the color contrast ratio of the white text, especially when light colored images are present in the background. + When the rotation control, next slide, and previous slide buttons are rendered on top of the carousel images, the buttons have forground and background colors that meet WCAG 2.1 color contrast requirements. + In addition, the focus styling uses SVG images that make the focus indicator highly visible when a control receives keyboard focus. +
- The transparency of the caption area is decreased so the caption text meets the WCAG 2.1 color contrast requirements.
Screen Reader Announcement of Slide Changes
++ When automatic rotation is turned off, the carousel slide content is included in a live region. + This makes it easier for screen reader users to scan through the carousel slides. + When screen reader users activate the next or previous slide button , the new slide content is announced, giving users immediate feedback that helps them determine whether or not to interact with the content. + Very importantly, if automatic rotation is turned on, the live region is disabled. + If it were not, the page would be come unusable as announcements of the continuously changing content constantly interrupt anything else the user is reading. +
Rotation Control Button
- Moves focus through interactive elements in the carousel.
- Rotation control, previous slide, and next slide buttons precede the slide content in the Tab sequence. -
- The rotation control button moves on screen when it receives keyboard focus and off screen when it is not focused. -
- Off-screen positioning allows it to be available to screen reader users when not focused.
Role, Property, State, and Tabindex Attributes
aria-live=off
@@ -375,7 +418,7 @@ Role, Property, State, and Tabindex Attributes
aria-live=polite
@@ -436,42 +479,15 @@ Role, Property, State, and Tabindex Attributes
aria-disabled="true"
- button
- -
-
- Applied to the automatic rotation control button when rotation is stopped and conditions that prevent automatic rotation are present. -
- The button is disabled when a pointer is hovering over the carousel or any element in the carousel has keyboard focus. -
- The
aria-disabled
state is used instead of the HTMLdisabled
attribute to keep the button in the Tab sequence.
- - If hover or focus is removed from the carousel, the
aria-disabled
attribute is removed from the button.
-
button
- a
- button
.aria-label="LABEL_STRING"
a
+ button
Role, Property, State, and Tabindex Attributes
aria-controls="IDREF"
a
+ button
-
@@ -495,7 +511,7 @@
- CSS: carousel.css +
- CSS: carousel-1.css
- Javascript: carousel.js
- Javascript: carouselItem.js
- Javascript: carouselButtons.js diff --git a/examples/carousel/carousel-1/css/carousel.css b/examples/carousel/carousel-1/css/carousel.css deleted file mode 100644 index 11ce7cd722..0000000000 --- a/examples/carousel/carousel-1/css/carousel.css +++ /dev/null @@ -1,176 +0,0 @@ - -/* .carousel */ -.carousel-item { - display: none; - max-height: 400px; - max-width: 900px; - position: relative; - overflow: hidden; - width: 100%; -} - -.carousel .carousel-item.active { - display: block; -} - -/* More like bootstrap, less accessible */ - -.carousel .carousel-inner { - max-width: 900px; - position: relative; -} - -.carousel button.pause { - display: block; - font-size: 20px; - width: auto; - left: -300em; - margin-bottom: 10px; - height: auto; - position: relative; - top: 5px; - right: -20px; - border: thin solid outset; -} - -.carousel button[aria-disabled=true] { - color: #666; -} - -.carousel button.pause:focus { - display: block; - position: relative; - font-size: 20px; - width: auto; - left: 0; - margin-bottom: 10px; - height: auto; - top: 5px; - right: -20px; -} - -.carousel .carousel-items { - border: solid 2px transparent; -} - -.carousel .carousel-items.focus { - border-color: white; - outline: solid 3px #005a9c; -} - -.carousel .carousel-inner .carousel-image a img { - height: 100%; - width: 100%; -} - -.carousel .carousel-inner .carousel-caption a { - text-decoration: underline; - border: none; -} - -.carousel .carousel-inner .carousel-caption h3 a { - color: #fff; - font-weight: 600; -} - -.carousel .carousel-inner .carousel-caption a:focus, -.carousel .carousel-inner .carousel-caption a:hover { - outline: solid 2px #fff; - outline-offset: 1px; -} - -.carousel .carousel-inner .carousel-caption p { - font-size: 1em; - line-height: 1.5; - margin-bottom: 0; -} - -.carousel .carousel-caption { - position: absolute; - right: 15%; - bottom: 0; - left: 15%; - padding-top: 20px; - padding-bottom: 20px; - color: #fff; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); -} - -.carousel .carousel-inner .carousel-caption { - bottom: 0; - left: 0; - padding: 3% 3% 50px; - right: 0; - text-shadow: none; -} - -.carousel:hover .carousel-inner .carousel-caption, -.carousel .carousel-item.focus .carousel-caption { - background-color: rgba(0, 0, 0, 0.4); -} - -.carousel .carousel-inner, -.carousel .carousel-item, -.carousel .carousel-slide { - max-height: 400px; -} - -.carousel .carousel-control { - position: absolute; - top: 0; - z-index: 10; - font-size: 200%; - font-weight: bold; - color: #fff; - text-align: center; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); -} - -.carousel a.carousel-control svg { - position: relative; - display: inline-block; - top: 45%; -} - -.carousel a.carousel-control svg polygon { - opacity: 0.7; -} - -.carousel a.carousel-control:focus { - border: 3px solid #fff; - outline: 1px solid #005a9c; -} - -.carousel a.carousel-control:focus svg polygon, -.carousel a.carousel-control:hover svg polygon { - opacity: 1; -} - -.carousel a.carousel-control.previous { - bottom: 0; - width: 15%; - background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%); -} - -.carousel a.carousel-control.previous:focus, -.carousel a.carousel-control.previous:hover { - bottom: 0; - width: 15%; - background-image: linear-gradient(to right, rgba(0, 0, 0, 0.7) 0, rgba(0, 0, 0, 0.0001) 100%); -} - -.carousel a.carousel-control.next { - right: 0; - bottom: 0; - width: 15%; - background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%); -} - -.carousel a.carousel-control.next:focus, -.carousel a.carousel-control.next:hover { - right: 0; - bottom: 0; - width: 15%; - background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.7) 100%); -} diff --git a/examples/carousel/carousel-1/js/pauseButton.js b/examples/carousel/carousel-1/js/pauseButton.js deleted file mode 100644 index ebd875ee56..0000000000 --- a/examples/carousel/carousel-1/js/pauseButton.js +++ /dev/null @@ -1,38 +0,0 @@ -/* -* File: pasueButton.js -* -* Desc: Implements the pause button for the carousel widget -* -*/ - -var PauseButton = function (domNode, carouselObj) { - this.domNode = domNode; - - this.carousel = carouselObj; -}; - -var StartButton = function (domNode, carouselObj) { - this.domNode = domNode; - - this.carousel = carouselObj; -}; - -PauseButton.prototype.init = function () { - this.domNode.addEventListener('click', this.handleClick.bind(this)); - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); -}; - -/* EVENT HANDLERS */ - -PauseButton.prototype.handleClick = function (event) { - this.carousel.toggleRotation(); -}; - -PauseButton.prototype.handleFocus = function (event) { - this.domNode.classList.add('focus'); -}; - -PauseButton.prototype.handleBlur = function (event) { - this.domNode.classList.remove('focus'); -}; diff --git a/examples/carousel/css/carousel-1-more-accessible.css b/examples/carousel/css/carousel-1-more-accessible.css new file mode 100644 index 0000000000..4342f03dd6 --- /dev/null +++ b/examples/carousel/css/carousel-1-more-accessible.css @@ -0,0 +1,185 @@ +/* .carousel */ + +.carousel .carousel-inner { + display: static; +} + +.carousel .carousel-item { + display: none; + max-width: 900px; + width: 100%; +} + +.carousel .carousel-item.active { + display: block; +} + +/* More like bootstrap, less accessible */ + +/* Shared CSS for Pause, Next and Previous Slide Controls */ + +.carousel .controls { + margin: 0; + padding: 0; + width: 100%; + position: relative; + height: 36px; + background-color: #eee; + border: 4px solid #eee; + border-radius: 5px 5px 0 0; +} + +.carousel .controls button { + position: absolute; + top: 6px; + display: block; + background-color: transparent; + border: none; + outline: none; +} + +.carousel .controls button.previous { + right: 58px; +} + +.carousel .controls button.next { + right: 4px; +} + +.carousel .controls button.rotation { + left: 4px; +} + +.carousel .controls button svg rect.background { + stroke: black; + fill: black; + stroke-width: 1px; + opacity: 0.8; +} + +.carousel .controls button svg rect.border { + fill: transparent; + stroke: transparent; + stroke-width: 2px; +} + +/* Next and Previous Slide Controls */ + +.carousel .controls button svg polygon { + stroke: white; + fill: white; + stroke-width: 2; + opacity: 1; +} + +.carousel .controls button.rotation svg polygon.pause { + stroke-width: 4; + fill: transparent; + stroke: transparent; +} + +.carousel .controls button.rotation svg polygon.play { + stroke-width: 1; + fill: transparent; + stroke: transparent; +} + +.carousel .controls button.rotation.pause svg polygon.pause, +.carousel .controls button.rotation.play svg polygon.play { + fill: white; + stroke: white; +} + +/* Common focus styling for svg buttons */ + +.carousel .controls button:focus rect.background, +.carousel .controls button:hover rect.background, +.carousel .controls button:focus rect.border, +.carousel .controls button:hover rect.border { + fill: #005a9c; + stroke: #005a9c; + opacity: 1; +} + +.carousel .controls button:focus rect.border { + stroke: white; +} + +/* Caption Positioning */ + +.carousel .carousel-items { + width: 100%; + background-color: #eee; + border: solid 4px #eee; + border-radius: 0 0 5px 5px; +} + +.carousel .carousel-items.focus { + border-color: #005a9c; +} + +.carousel .carousel-item .carousel-image { + margin: 0; + padding: 0; + width: 100%; +} + +.carousel .carousel-item .carousel-image a { + margin: 0; + padding: 0; +} + +.carousel .carousel-item .carousel-image a img { + margin: 0; + padding: 0; + display: block; + overflow: hidden; + max-height: 100%; + max-width: 100%; +} + +.carousel .carousel-item .carousel-caption { + margin: 0; + padding: 0.5em; + width: 100%; + height: 3em; + text-align: center; +} + +.carousel .carousel-item .carousel-caption a { + display: inline-block; + background-color: rgba(0, 0, 0, 0); + padding-left: 0.25em; + padding-right: 0.25em; + padding-top: 0.125em; + padding-bottom: 0.125em; + border-radius: 5px; + border: 2px solid transparent; + margin: 0; + text-decoration: underline; +} + +.carousel .carousel-item .carousel-caption h3 { + margin: 0; + padding: 0; + font-weight: bold; +} + +.carousel .carousel-item .carousel-caption h3 a { + color: black; +} + +.carousel .carousel-item .carousel-caption a:hover { + background-color: rgba(0, 0, 0, 0.1); +} + +.carousel .carousel-item .carousel-caption a:focus { + border-color: #005a9c; + background-color: rgba(0, 0, 0, 0.1); + outline: none; +} + +.carousel .carousel-item .carousel-caption p { + margin: 0; + padding: 0; +} diff --git a/examples/carousel/css/carousel-1.css b/examples/carousel/css/carousel-1.css new file mode 100644 index 0000000000..735f61e3db --- /dev/null +++ b/examples/carousel/css/carousel-1.css @@ -0,0 +1,165 @@ + +/* .carousel */ + +.carousel .carousel-inner { + position: relative; +} + +.carousel .carousel-item { + display: none; + max-height: 400px; + max-width: 900px; + position: relative; + overflow: hidden; + width: 100%; +} + +.carousel .carousel-item.active { + display: block; +} + +/* More like bootstrap, less accessible */ + +.carousel .carousel-items { + border: solid 2px transparent; +} + +.carousel .carousel-items.focus { + border-color: white; + outline: solid 3px #005a9c; +} + +.carousel .carousel-item .carousel-image a img { + height: 100%; + width: 100%; +} + +.carousel .carousel-item .carousel-caption a { + text-decoration: underline; +} + +.carousel .carousel-item .carousel-caption a, +.carousel .carousel-item .carousel-caption span.contrast { + display: inline-block; + background-color: rgba(0, 0, 0, 0.65); + padding-left: 0.25em; + padding-right: 0.25em; + border-radius: 5px; + border: 2px solid transparent; + margin: 0; +} + +.carousel .carousel-item .carousel-caption h3 a { + color: #fff; + font-weight: 600; +} + +.carousel .carousel-item .carousel-caption a:hover, +.carousel .carousel-item .carousel-caption span.contrast:hover { + background-color: rgba(0, 0, 0, 1); + margin: 0; +} + +.carousel .carousel-item .carousel-caption a:focus { + background-color: rgba(0, 0, 0, 1); + border-color: #fff; + margin: 0; +} + +.carousel .carousel-item .carousel-caption p { + font-size: 1em; + line-height: 1.5; + margin-bottom: 0; +} + +.carousel .carousel-item .carousel-caption { + position: absolute; + right: 15%; + bottom: 0; + left: 15%; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; +} + +/* Shared CSS for Pause, Next and Previous Slide Controls */ + +.carousel .controls button { + padding: 0; + position: absolute; + top: 5px; + z-index: 10; + background-color: transparent; + border: none; + outline: none; +} + +.carousel .controls button svg rect.background { + stroke: black; + fill: black; + stroke-width: 1px; + opacity: 0.6; +} + +.carousel .controls button svg rect.border { + fill: transparent; + stroke: transparent; + stroke-width: 2px; +} + +/* Next and Previous Slide Controls */ + +.carousel .controls button svg polygon { + stroke: white; + fill: white; + stroke-width: 2; + opacity: 1; +} + +.carousel .controls button.previous { + right: 50px; +} + +.carousel .controls button.next { + right: 6px; +} + +/* Pause Control */ + +.carousel .controls button.rotation { + left: 6px; +} + +.carousel .controls button.rotation svg polygon.pause { + stroke-width: 4; + fill: transparent; + stroke: transparent; +} + +.carousel .controls button.rotation svg polygon.play { + stroke-width: 1; + fill: transparent; + stroke: transparent; +} + +.carousel .controls button.rotation.pause svg polygon.pause, +.carousel .controls button.rotation.play svg polygon.play { + fill: white; + stroke: white; +} + +/* Common focus styling for svg buttons */ + +.carousel .controls button:focus rect.background, +.carousel .controls button:hover rect.background, +.carousel .controls button:focus rect.border, +.carousel .controls button:hover rect.border { + fill: #005a9c; + stroke: #005a9c; + opacity: 1; +} + +.carousel .controls button:focus rect.border { + stroke: white; +} diff --git a/examples/carousel/carousel-1/images/amsterdamslide__800x600.jpg b/examples/carousel/images/amsterdamslide__800x600.jpg similarity index 100% rename from examples/carousel/carousel-1/images/amsterdamslide__800x600.jpg rename to examples/carousel/images/amsterdamslide__800x600.jpg diff --git a/examples/carousel/carousel-1/images/britcomdavidslide__800x600.jpg b/examples/carousel/images/britcomdavidslide__800x600.jpg similarity index 100% rename from examples/carousel/carousel-1/images/britcomdavidslide__800x600.jpg rename to examples/carousel/images/britcomdavidslide__800x600.jpg diff --git a/examples/carousel/carousel-1/images/foyleswarslide__800x600.jpg b/examples/carousel/images/foyleswarslide__800x600.jpg similarity index 100% rename from examples/carousel/carousel-1/images/foyleswarslide__800x600.jpg rename to examples/carousel/images/foyleswarslide__800x600.jpg diff --git a/examples/carousel/carousel-1/images/lands-endslide__800x600.jpg b/examples/carousel/images/lands-endslide__800x600.jpg similarity index 100% rename from examples/carousel/carousel-1/images/lands-endslide__800x600.jpg rename to examples/carousel/images/lands-endslide__800x600.jpg diff --git a/examples/carousel/carousel-1/images/mag800-2__800x600.jpg b/examples/carousel/images/mag800-2__800x600.jpg similarity index 100% rename from examples/carousel/carousel-1/images/mag800-2__800x600.jpg rename to examples/carousel/images/mag800-2__800x600.jpg diff --git a/examples/carousel/carousel-1/images/trustslide-2__800x600.jpg b/examples/carousel/images/trustslide-2__800x600.jpg similarity index 100% rename from examples/carousel/carousel-1/images/trustslide-2__800x600.jpg rename to examples/carousel/images/trustslide-2__800x600.jpg diff --git a/examples/carousel/carousel-1/js/carousel.js b/examples/carousel/js/carousel.js similarity index 68% rename from examples/carousel/carousel-1/js/carousel.js rename to examples/carousel/js/carousel.js index 9e3071e921..96e79aeaef 100644 --- a/examples/carousel/carousel-1/js/carousel.js +++ b/examples/carousel/js/carousel.js @@ -22,8 +22,8 @@ var Carousel = function (domNode) { this.currentItem = null; this.pauseButton = null; - this.startLabel = 'Start automatic slide show'; - this.stopLabel = 'Stop automatic slide show'; + this.playLabel = 'Start automatic slide show'; + this.pauseLabel = 'Stop automatic slide show'; this.rotate = true; this.hasFocus = false; @@ -34,12 +34,14 @@ var Carousel = function (domNode) { Carousel.prototype.init = function () { + var elems, elem, button, items, item, imageLinks, i; + this.liveRegionNode = this.domNode.querySelector('.carousel-items'); - var items = this.domNode.querySelectorAll('.carousel-item'); + items = this.domNode.querySelectorAll('.carousel-item'); - for (var i = 0; i < items.length; i++) { - var item = new CarouselItem(items[i], this); + for (i = 0; i < items.length; i++) { + item = new CarouselItem(items[i], this); item.init(); this.items.push(item); @@ -50,7 +52,7 @@ Carousel.prototype.init = function () { } this.lastItem = item; - var imageLinks = items[i].querySelectorAll('.carousel-image a'); + imageLinks = items[i].querySelectorAll('.carousel-image a'); if (imageLinks && imageLinks[0]) { imageLinks[0].addEventListener('focus', this.handleImageLinkFocus.bind(this)); @@ -59,27 +61,28 @@ Carousel.prototype.init = function () { } - // Next Slide and Previous Slide Buttons + // Pause, Next Slide and Previous Slide Buttons - var elems = document.querySelectorAll('.carousel a.carousel-control'); + elems = document.querySelectorAll('.carousel .controls button'); - for (var i = 0; i < elems.length; i++) { - if (elems[i].tagName.toLowerCase() == 'a') { - var button = new CarouselButton(elems[i], this); + for (i = 0; i < elems.length; i++) { + elem = elems[i]; - button.init(); + if (elem.classList.contains('rotation')) { + button = new PauseButton(elem, this); + this.pauseButton = elem; + this.pauseButton.classList.add('pause'); + this.pauseButton.setAttribute('aria-label', this.pauseLabel); + } + else { + button = new CarouselButton(elem, this); } - } - - this.currentItem = this.firstItem; - this.pauseButton = this.domNode.parentNode.parentNode.querySelector('button.pause'); - if (this.pauseButton) { - var button = new PauseButton(this.pauseButton, this); button.init(); - this.pauseButton.innerHTML = this.stopLabel; } + this.currentItem = this.firstItem; + this.domNode.addEventListener('mouseover', this.handleMouseOver.bind(this)); this.domNode.addEventListener('mouseout', this.handleMouseOut.bind(this)); @@ -151,42 +154,42 @@ Carousel.prototype.rotateSlides = function () { setTimeout(this.rotateSlides.bind(this), this.timeInterval); }; -Carousel.prototype.startRotation = function () { +Carousel.prototype.updateRotation = function () { + if (!this.hasHover && !this.hasFocus && !this.isStopped) { this.rotate = true; this.liveRegionNode.setAttribute('aria-live', 'off'); - this.pauseButton.innerHTML = this.stopLabel; } - this.disablePauseButton(); -}; - -Carousel.prototype.stopRotation = function () { - this.rotate = false; - this.liveRegionNode.setAttribute('aria-live', 'polite'); - this.pauseButton.innerHTML = this.startLabel; - this.disablePauseButton(); -}; + else { + this.rotate = false; + this.liveRegionNode.setAttribute('aria-live', 'polite'); + } -Carousel.prototype.disablePauseButton = function () { - if (this.hasHover || this.hasFocus) { - this.pauseButton.setAttribute('aria-disabled', 'true'); + if (this.isStopped) { + this.pauseButton.setAttribute('aria-label', this.playLabel); + this.pauseButton.classList.remove('pause'); + this.pauseButton.classList.add('play'); } else { - this.pauseButton.removeAttribute('aria-disabled'); + this.pauseButton.setAttribute('aria-label', this.pauseLabel); + this.pauseButton.classList.remove('play'); + this.pauseButton.classList.add('pause'); } + }; Carousel.prototype.toggleRotation = function () { if (this.isStopped) { - if (this.pauseButton.getAttribute('aria-disabled') !== 'true') { + if (!this.hasHover && !this.hasFocus) { this.isStopped = false; - this.startRotation(); } } else { this.isStopped = true; - this.stopRotation(); } + + this.updateRotation(); + }; Carousel.prototype.handleImageLinkFocus = function () { @@ -197,19 +200,21 @@ Carousel.prototype.handleImageLinkBlur = function () { this.liveRegionNode.classList.remove('focus'); }; -Carousel.prototype.handleMouseOver = function () { - this.hasHover = true; - this.stopRotation(); +Carousel.prototype.handleMouseOver = function (event) { + if (!this.pauseButton.contains(event.target)) { + this.hasHover = true; + } + this.updateRotation(); }; Carousel.prototype.handleMouseOut = function () { this.hasHover = false; - this.startRotation(); + this.updateRotation(); }; /* Initialize Carousel Tablists */ -window.addEventListener('load', function (event) { +window.addEventListener('load', function () { var carousels = document.querySelectorAll('.carousel'); for (var i = 0; i < carousels.length; i++) { diff --git a/examples/carousel/carousel-1/js/carouselButtons.js b/examples/carousel/js/carouselButtons.js similarity index 73% rename from examples/carousel/carousel-1/js/carouselButtons.js rename to examples/carousel/js/carouselButtons.js index 22bde956e9..82c6396775 100644 --- a/examples/carousel/carousel-1/js/carouselButtons.js +++ b/examples/carousel/js/carouselButtons.js @@ -33,7 +33,6 @@ var CarouselButton = function (domNode, carouselObj) { }; CarouselButton.prototype.init = function () { - this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); this.domNode.addEventListener('click', this.handleClick.bind(this)); this.domNode.addEventListener('focus', this.handleFocus.bind(this)); this.domNode.addEventListener('blur', this.handleBlur.bind(this)); @@ -51,26 +50,6 @@ CarouselButton.prototype.changeItem = function () { /* EVENT HANDLERS */ -CarouselButton.prototype.handleKeydown = function (event) { - var flag = false; - - switch (event.keyCode) { - case this.keyCode.SPACE: - case this.keyCode.RETURN: - this.changeItem(); - this.domNode.focus(); - flag = true; - break; - - default: - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; CarouselButton.prototype.handleClick = function (event) { this.changeItem(); @@ -79,11 +58,11 @@ CarouselButton.prototype.handleClick = function (event) { CarouselButton.prototype.handleFocus = function (event) { this.carousel.hasFocus = true; this.domNode.classList.add('focus'); - this.carousel.stopRotation(); + this.carousel.updateRotation(); }; CarouselButton.prototype.handleBlur = function (event) { this.carousel.hasFocus = false; this.domNode.classList.remove('focus'); - this.carousel.startRotation(); + this.carousel.updateRotation(); }; diff --git a/examples/carousel/carousel-1/js/carouselItem.js b/examples/carousel/js/carouselItem.js similarity index 93% rename from examples/carousel/carousel-1/js/carouselItem.js rename to examples/carousel/js/carouselItem.js index 7ed5417f63..dc1f1d1513 100644 --- a/examples/carousel/carousel-1/js/carouselItem.js +++ b/examples/carousel/js/carouselItem.js @@ -32,11 +32,11 @@ CarouselItem.prototype.show = function () { CarouselItem.prototype.handleFocusIn = function (event) { this.domNode.classList.add('focus'); this.carousel.hasFocus = true; - this.carousel.stopRotation(); + this.carousel.updateRotation(); }; CarouselItem.prototype.handleFocusOut = function (event) { this.domNode.classList.remove('focus'); this.carousel.hasFocus = false; - this.carousel.startRotation(); + this.carousel.updateRotation(); }; diff --git a/examples/carousel/js/pauseButton.js b/examples/carousel/js/pauseButton.js new file mode 100644 index 0000000000..833e274307 --- /dev/null +++ b/examples/carousel/js/pauseButton.js @@ -0,0 +1,22 @@ +/* +* File: pasueButton.js +* +* Desc: Implements the pause button for the carousel widget +* +*/ + +var PauseButton = function (domNode, carouselObj) { + this.domNode = domNode; + + this.carousel = carouselObj; +}; + +PauseButton.prototype.init = function () { + this.domNode.addEventListener('click', this.handleClick.bind(this)); +}; + +/* EVENT HANDLERS */ + +PauseButton.prototype.handleClick = function () { + this.carousel.toggleRotation(); +}; diff --git a/index.html b/index.html index 43d1fe56ac..bd370078ef 100644 --- a/index.html +++ b/index.html @@ -786,7 +786,7 @@
Role, Property, State, and Tabindex Attributes
Javascript and CSS Source Code
-
-
This document is governed by the 1 March 2019 W3C Process Document. -