我一直在尝试通过键盘访问和屏幕阅读器访问来使我目前的可扩展手风琴符合W3C Web内容辅助功能指南的AA级。
我对JavaScript / jQuery并不是很熟悉所以到目前为止我一直在进行大量的猜测和检查。
我完成了以下工作:
但显然我错过了以下内容:
以下是我一直在使用的CodePen:https://codepen.io/kwhytock/pen/Ozzopr 我包含了所有jQuery UI代码,但是以Accordion为中心的代码从第2516行开始。
$(function() {
$("#accordion:nth-child(1n)").accordion({
collapsible: true
});
$("#accordion:nth-child(1n)").accordion({
active: false
});
});
var widgetsAccordion = $.widget("ui.accordion", {
version: "1.12.1",
options: {
active: 0,
animate: {},
classes: {
"ui-accordion-header": "ui-corner-top",
"ui-accordion-header-collapsed": "ui-corner-all",
"ui-accordion-content": "ui-corner-bottom"
},
collapsible: false,
event: "click",
header: ".accordionTitle",
heightStyle: "auto",
// Callbacks
activate: null,
beforeActivate: null
},
hideProps: {
borderTopWidth: "hide",
borderBottomWidth: "hide",
paddingTop: "hide",
paddingBottom: "hide",
height: "hide"
},
showProps: {
borderTopWidth: "show",
borderBottomWidth: "show",
paddingTop: "show",
paddingBottom: "show",
height: "show"
},
_create: function() {
var options = this.options;
this.prevShow = this.prevHide = $();
this._addClass("ui-accordion", "ui-widget ui-helper-reset");
this.element.attr("role", "tablist");
// Don't allow collapsible: false and active: false / null
if (!options.collapsible && (options.active === false || options.active == null)) {
options.active = 0;
}
this._processPanels();
// handle negative values
if (options.active < 0) {
options.active += this.headers.length;
}
this._refresh();
},
_getCreateEventData: function() {
return {
header: this.active,
panel: !this.active.length ? $() : this.active.next()
};
},
_createIcons: function() {
var icon, children,
icons = this.options.icons;
if (icons) {
icon = $("<span>");
this._addClass(icon, "ui-accordion-header-icon", "ui-icon " + icons.header);
icon.prependTo(this.headers);
children = this.active.children(".ui-accordion-header-icon");
this._removeClass(children, icons.header)
._addClass(children, null, icons.activeHeader)
._addClass(this.headers, "ui-accordion-icons");
}
},
_destroyIcons: function() {
this._removeClass(this.headers, "ui-accordion-icons");
this.headers.children(".ui-accordion-header-icon").remove();
},
_destroy: function() {
var contents;
// Clean up main element
this.element.removeAttr("role");
// Clean up headers
this.headers
.removeAttr("role aria-expanded aria-selected aria-controls tabIndex")
.removeUniqueId();
this._destroyIcons();
// Clean up content panels
contents = this.headers.next()
.css("display", "")
.removeAttr("role aria-hidden aria-labelledby")
.removeUniqueId();
if (this.options.heightStyle !== "content") {
contents.css("height", "");
}
},
_setOption: function(key, value) {
if (key === "active") {
// _activate() will handle invalid values and update this.options
this._activate(value);
return;
}
if (key === "event") {
if (this.options.event) {
this._off(this.headers, this.options.event);
}
this._setupEvents(value);
}
this._super(key, value);
// Setting collapsible: false while collapsed; open first panel
if (key === "collapsible" && !value && this.options.active === false) {
this._activate(0);
}
if (key === "icons") {
this._destroyIcons();
if (value) {
this._createIcons();
}
}
},
_setOptionDisabled: function(value) {
this._super(value);
this.element.attr("aria-disabled", value);
// Support: IE8 Only
// #5332 / #6059 - opacity doesn't cascade to positioned elements in IE
// so we need to add the disabled class to the headers and panels
this._toggleClass(null, "ui-state-disabled", !!value);
this._toggleClass(this.headers.add(this.headers.next()), null, "ui-state-disabled", !!value);
},
_keydown: function(event) {
if (event.altKey || event.ctrlKey) {
return;
}
var keyCode = $.ui.keyCode,
length = this.headers.length,
currentIndex = this.headers.index(event.target),
toFocus = true;
switch (event.keyCode) {
case keyCode.RIGHT:
case keyCode.TAB:
if (event.shiftKey && event.keyCode == 9) {
//shift was down when tab was pressed
}
toFocus = this.headers[(currentIndex - 1) % length];
case keyCode.DOWN:
toFocus = this.headers[(currentIndex + 1)];
break;
case keyCode.LEFT:
case keyCode.UP:
toFocus = this.headers[(currentIndex - 1 + length) % length];
break;
case keyCode.SPACE:
case keyCode.ENTER:
this._eventHandler(event);
break;
case keyCode.HOME:
toFocus = this.headers[0];
break;
case keyCode.END:
toFocus = this.headers[length - 1];
break;
}
if (toFocus) {
$(event.target).attr("tabIndex", -1);
$(toFocus).attr("tabIndex", 0);
$(toFocus).trigger("focus");
event.preventDefault();
}
},
_panelKeyDown: function(event) {
if (event.keyCode === $.ui.keyCode.UP && event.ctrlKey) {
$(event.currentTarget).prev().trigger("focus");
}
},
refresh: function() {
var options = this.options;
this._processPanels();
// Was collapsed or no panel
if ((options.active === false && options.collapsible === true) ||
!this.headers.length) {
options.active = false;
this.active = $();
// active false only when collapsible is true
} else if (options.active === false) {
this._activate(0);
// was active, but active panel is gone
} else if (this.active.length && !$.contains(this.element[0], this.active[0])) {
// all remaining panel are disabled
if (this.headers.length === this.headers.find(".ui-state-disabled").length) {
options.active = false;
this.active = $();
// activate previous panel
} else {
this._activate(Math.max(0, options.active - 1));
}
// was active, active panel still exists
} else {
// make sure active index is correct
options.active = this.headers.index(this.active);
}
this._destroyIcons();
this._refresh();
},
_processPanels: function() {
var prevHeaders = this.headers,
prevPanels = this.panels;
this.headers = this.element.find(this.options.header);
this._addClass(this.headers, "ui-accordion-header ui-accordion-header-collapsed",
"ui-state-default");
this.panels = this.headers.next().filter(":not(.ui-accordion-content-active)").hide();
this._addClass(this.panels, "ui-accordion-content", "ui-helper-reset ui-widget-content");
// Avoid memory leaks (#10056)
if (prevPanels) {
this._off(prevHeaders.not(this.headers));
this._off(prevPanels.not(this.panels));
}
},
_refresh: function() {
var maxHeight,
options = this.options,
heightStyle = options.heightStyle,
parent = this.element.parent();
this.active = this._findActive(options.active);
this._addClass(this.active, "ui-accordion-header-active", "ui-state-active")
._removeClass(this.active, "ui-accordion-header-collapsed");
this._addClass(this.active.next(), "ui-accordion-content-active");
this.active.next().show();
this.headers
.attr("role", "heading")
.attr("type", "button")
.each(function() {
var header = $(this),
headerId = header.uniqueId().attr("id"),
panel = header.next(),
panelId = panel.uniqueId().attr("id");
header.attr("aria-controls", panelId);
panel.attr("aria-labelledby", headerId);
})
.next()
.attr("role", "region");
this.headers
.not(this.active)
.attr({
"aria-selected": "false",
"aria-expanded": "false",
tabIndex: -1
})
.next()
.attr({
"aria-hidden": "true"
})
.hide();
// Make sure at least one header is in the tab order
if (!this.active.length) {
this.headers.eq(0).attr("tabIndex", 0);
} else {
this.active.attr({
"aria-selected": "true",
"aria-expanded": "true",
tabIndex: 0
})
.next()
.attr({
"aria-hidden": "false"
});
}
this._createIcons();
this._setupEvents(options.event);
if (heightStyle === "fill") {
maxHeight = parent.height();
this.element.siblings(":visible").each(function() {
var elem = $(this),
position = elem.css("position");
if (position === "absolute" || position === "fixed") {
return;
}
maxHeight -= elem.outerHeight(true);
});
this.headers.each(function() {
maxHeight -= $(this).outerHeight(true);
});
this.headers.next()
.each(function() {
$(this).height(Math.max(0, maxHeight -
$(this).innerHeight() + $(this).height()));
})
.css("overflow", "auto");
} else if (heightStyle === "auto") {
maxHeight = 0;
this.headers.next()
.each(function() {
var isVisible = $(this).is(":visible");
if (!isVisible) {
$(this).show();
}
maxHeight = Math.max(maxHeight, $(this).css("height", "").height());
if (!isVisible) {
$(this).hide();
}
})
.height(maxHeight);
}
},
_activate: function(index) {
var active = this._findActive(index)[0];
// Trying to activate the already active panel
if (active === this.active[0]) {
return;
}
// Trying to collapse, simulate a click on the currently active header
active = active || this.active[0];
this._eventHandler({
target: active,
currentTarget: active,
preventDefault: $.noop
});
},
_findActive: function(selector) {
return typeof selector === "number" ? this.headers.eq(selector) : $();
},
_setupEvents: function(event) {
var events = {
keydown: "_keydown"
};
if (event) {
$.each(event.split(" "), function(index, eventName) {
events[eventName] = "_eventHandler";
});
}
this._off(this.headers.add(this.headers.next()));
this._on(this.headers, events);
this._on(this.headers.next(), {
keydown: "_panelKeyDown"
});
this._hoverable(this.headers);
this._focusable(this.headers);
},
_eventHandler: function(event) {
var activeChildren, clickedChildren,
options = this.options,
active = this.active,
clicked = $(event.currentTarget),
clickedIsActive = clicked[0] === active[0],
collapsing = clickedIsActive && options.collapsible,
toShow = collapsing ? $() : clicked.next(),
toHide = active.next(),
eventData = {
oldHeader: active,
oldPanel: toHide,
newHeader: collapsing ? $() : clicked,
newPanel: toShow
};
event.preventDefault();
if (
// click on active header, but not collapsible
(clickedIsActive && !options.collapsible) ||
// allow canceling activation
(this._trigger("beforeActivate", event, eventData) === false)) {
return;
}
options.active = collapsing ? false : this.headers.index(clicked);
// When the call to ._toggle() comes after the class changes
// it causes a very odd bug in IE 8 (see #6720)
this.active = clickedIsActive ? $() : clicked;
this._toggle(eventData);
// Switch classes
// corner classes on the previously active header stay after the animation
this._removeClass(active, "ui-accordion-header-active", "ui-state-active");
if (options.icons) {
activeChildren = active.children(".ui-accordion-header-icon");
this._removeClass(activeChildren, null, options.icons.activeHeader)
._addClass(activeChildren, null, options.icons.header);
}
if (!clickedIsActive) {
this._removeClass(clicked, "ui-accordion-header-collapsed")
._addClass(clicked, "ui-accordion-header-active", "ui-state-active");
if (options.icons) {
clickedChildren = clicked.children(".ui-accordion-header-icon");
this._removeClass(clickedChildren, null, options.icons.header)
._addClass(clickedChildren, null, options.icons.activeHeader);
}
this._addClass(clicked.next(), "ui-accordion-content-active");
}
},
_toggle: function(data) {
var toShow = data.newPanel,
toHide = this.prevShow.length ? this.prevShow : data.oldPanel;
// Handle activating a panel during the animation for another activation
this.prevShow.add(this.prevHide).stop(true, true);
this.prevShow = toShow;
this.prevHide = toHide;
if (this.options.animate) {
this._animate(toShow, toHide, data);
} else {
toHide.hide();
toShow.show();
this._toggleComplete(data);
}
toHide.attr({
"aria-hidden": "true"
});
toHide.prev().attr({
"aria-selected": "false",
"aria-expanded": "false"
});
// if we're switching panels, remove the old header from the tab order
// if we're opening from collapsed state, remove the previous header from the tab order
// if we're collapsing, then keep the collapsing header in the tab order
if (toShow.length && toHide.length) {
toHide.prev().attr({
"tabIndex": -1,
"aria-expanded": "false"
});
} else if (toShow.length) {
this.headers.filter(function() {
return parseInt($(this).attr("tabIndex"), 10) === 0;
})
.attr("tabIndex", -1);
}
toShow
.attr("aria-hidden", "false")
.prev()
.attr({
"aria-selected": "true",
"aria-expanded": "true",
tabIndex: 0
});
},
_animate: function(toShow, toHide, data) {
var total, easing, duration,
that = this,
adjust = 0,
boxSizing = toShow.css("box-sizing"),
down = toShow.length &&
(!toHide.length || (toShow.index() < toHide.index())),
animate = this.options.animate || {},
options = down && animate.down || animate,
complete = function() {
that._toggleComplete(data);
};
if (typeof options === "number") {
duration = options;
}
if (typeof options === "string") {
easing = options;
}
// fall back from options to animation in case of partial down settings
easing = easing || options.easing || animate.easing;
duration = duration || options.duration || animate.duration;
if (!toHide.length) {
return toShow.animate(this.showProps, duration, easing, complete);
}
if (!toShow.length) {
return toHide.animate(this.hideProps, duration, easing, complete);
}
total = toShow.show().outerHeight();
toHide.animate(this.hideProps, {
duration: duration,
easing: easing,
step: function(now, fx) {
fx.now = Math.round(now);
}
});
toShow
.hide()
.animate(this.showProps, {
duration: duration,
easing: easing,
complete: complete,
step: function(now, fx) {
fx.now = Math.round(now);
if (fx.prop !== "height") {
if (boxSizing === "content-box") {
adjust += fx.now;
}
} else if (that.options.heightStyle !== "content") {
fx.now = Math.round(total - toHide.outerHeight() - adjust);
adjust = 0;
}
}
});
},
_toggleComplete: function(data) {
var toHide = data.oldPanel,
prev = toHide.prev();
this._removeClass(toHide, "ui-accordion-content-active");
this._removeClass(prev, "ui-accordion-header-active")
._addClass(prev, "ui-accordion-header-collapsed");
// Work around for rendering bug in IE (#5421)
if (toHide.length) {
toHide.parent()[0].className = toHide.parent()[0].className;
}
this._trigger("activate", null, data);
}
});
var safeActiveElement = $.ui.safeActiveElement = function(document) {
var activeElement;
// Support: IE 9 only
// IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe>
try {
activeElement = document.activeElement;
} catch (error) {
activeElement = document.body;
}
// Support: IE 9 - 11 only
// IE may return null instead of an element
// Interestingly, this only seems to occur when NOT in an iframe
if (!activeElement) {
activeElement = document.body;
}
// Support: IE 11 only
// IE11 returns a seemingly empty object in some cases when accessing
// document.activeElement from an <iframe>
if (!activeElement.nodeName) {
activeElement = document.body;
}
return activeElement;
};
.accordionTitle {
border: 1px solid #ccc;
margin: 5px 0 0 0;
font-weight: 200 !important;
font-size: 1.15em;
background-color: #F8F8F8;
padding: 1em 0.5em;
text-decoration: none;
color: #000;
-webkit-transition: background-color 0.5s ease-in-out;
transition: background-color 0.5s ease-in-out;
}
.accordionTitle:before {
content: "";
font-size: 1.5em;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid;
float: left;
margin: 0.475em;
margin-right: 0.55em;
-webkit-transition: -webkit-transform 0.3s ease-in-out;
transition: -webkit-transform 0.3s ease-in-out;
transition: transform 0.3s ease-in-out;
transition: transform 0.3s ease-in-out, -webkit-transform 0.3s ease-in-out;
-webkit-transform: rotate(-90deg);
transform: rotate(-90deg);
}
.accordionTitle[aria-selected="true"]:before {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
.accordionTitle:focus,
.accordionTitle:hover {
background-color: #dadada;
}
.ui-accordion-content {
height: auto !important;
overflow: hidden;
padding: 1.5em 1.5em;
border: 1px solid #ccc;
}
[aria-pressed=true],
[aria-expanded=true] {
background-color: #f9f9f9;
}
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="http://sh101ftp.net/imgload/wordpress/jquery-ui.js"></script>
<script src="http://sh101ftp.net/imgload/wordpress/NewCustomCodeJS.js"></script>
<h2 id="question1" class="question"><span class="dropcap dropcap3" style="color: #127eb6;">1</span> <span style="color: #404040;">What might help you make physical activity an ongoing thing?</span></h2>
<div id="accordion" role="presentation">
<h3 class="accordionTitle"><strong>A.</strong> Option A</h3>
<div>
<p>This plan is practical, social, and could work well for both of you. Some disabilities an</span>d other pre-existing conditions have implications for working out. Your friend knows her own body and can seek medical clearance if needed. This is her call.</p>
<p><u><a href="http://www.prochange.com/college-health" target="_blank" rel="noopener noreferrer">liveWell program (Pro-Change Behavior Systems, Inc.)</a></u></p>
</div>
<h3 class="accordionTitle"><strong>B.</strong> Option B</h3>
<div>
<p>Self-consciousness can be a barrier to working out, yes. Candy hasn’t said that’s a problem for her, though. Many people with disabilities are marginalized and excluded. We all do better when we’re socially integrated into our communities. For example, people with robust social networks (supportive friends and family) experience lower rates of chronic disease and longer lives, and more job opportunities, according to a 2011 report from the National Research Council.</p>
<p><u><a href="http://november-project.com/" target="_blank" rel="noopener noreferrer">November Project</a></u></p>
<p><u><a href="https://www.meetup.com/" target="_blank" rel="noopener noreferrer">Meetup</a></u></p>
</div>
<h3 class="accordionTitle"><strong>C.</strong> Option C</h3>
<div>
<p>Disability advocates call this “inspiration porn.” It’s condescending. Why should you be amazed that Candy wants to do something with her life?</p>
</div>
</div>
答案 0 :(得分:1)
您正在做太多工作。我说这是基于看到的代码,例如:
<div id="accordion" role="presentation">
默认情况下,<div>
没有角色,因此设置role="presentation"
是多余的,只会使代码code肿。
此外,由于遍历您的Codepen示例似乎非常混乱(您不能向后翻转),因此您对tabindex
的动态使用已关闭。通常,在使用本机HTML元素(例如<button>
)时,您不必弄乱tabindex
。
一旦您开始抛出ARIA属性和tabindex
,它就会变得非常混乱。我建议您构建一个简单的示例,以便您可以看到它如何正常工作。从Accordions的WAI-ARIA创作惯例1.1部分开始。它有一个working example。
基本上,手风琴由以下组成:
首先尝试这些简单的步骤:
每个手风琴标题的标题都包含在<button>
或带有role="button"
的元素中。
每个手风琴标题按钮都包装在<hX>
元素中,其元素具有适合页面信息体系结构的级别。 button元素是标题元素内的唯一元素。
如果与手风琴标题关联的手风琴面板可见,则标题按钮元素的aria-expanded
设置为true。如果面板不可见,则将aria-expanded
设置为false。面板本身应适当设置aria-hidden
或用CSS("display:none"
)隐藏
手风琴标题按钮元素应将aria-controls
设置为包含手风琴面板内容的元素的ID。
手风琴面板有role="region"
和aria-labelledby
,其值表示控制面板显示的按钮。
所以你会有类似的东西:
<div> <!-- accordion container -->
<h3>
<button id="first" aria-expanded="false" aria-controls="panel1">first accordion title</button>
</h3>
<div id="panel1" role="region" style="display:none;" aria-labelledby= "first">
<!-- contents of your panel -->
</div>
<h3>
<button id="second" aria-expanded="false" aria-controls="panel2">second title</button>
</h3>
<div id="panel2" role="region" style="display:none;" aria-labelledby= "second">
<!-- contents of your panel -->
</div>
</div>
选择按钮后,应切换按钮的aria-expanded
属性和面板的display:none
CSS样式。
这将允许对所有手风琴标题(按钮)进行本机制表,在您的情况下是问题A,B和C。您不必弄混tabindex
,因为按钮可以通过默认。您所要做的就是切换按钮的aria-expanded
属性并隐藏/取消隐藏面板内容。十分简单。它可以与键盘或屏幕阅读器搭配使用。