
本文介绍如何在页面加载时,根据 URL 中的 fragment(如 #faq-0)自动展开对应 FAQ 答案项,并同步更新其 ARIA 状态与过渡样式,确保可访问性与视觉反馈一致。
本文介绍如何在页面加载时,根据 url 中的 fragment(如 `#faq-0`)自动展开对应 faq 答案项,并同步更新其 aria 状态与过渡样式,确保可访问性与视觉反馈一致。
在构建可访问、语义化且支持深度链接的 FAQ 组件时,一个常见需求是:当用户通过带锚点的 URL(例如 https://example.com/faq#faq-2)进入页面时,对应的问题答案应自动展开,而非要求用户手动点击。然而,仅调用 $(hash).show() 往往无效——原因在于该方法仅重置 display 为 block,却未处理以下关键细节:
- ✅ 需显式设置 transition 以匹配交互展开的动画效果;
- ✅ 需添加 data-slide-toggle="true" 标识,便于后续逻辑识别状态;
- ❌ 更重要的是:必须同步更新对应问题
- 的 aria-expanded 属性和 aria-controls 关联关系
,否则屏幕阅读器无法正确播报展开状态,违反 WCAG 无障碍标准。
正确的初始化逻辑(Drupal Behaviors 兼容)
以下代码应在 Drupal 的 behavior.attach 钩子中执行(或 DOM 加载完成后运行),确保在内容渲染完毕后生效:
(function ($, Drupal) {
'use strict';
Drupal.behaviors.faqHashInit = {
attach: function (context, settings) {
// 确保仅在首次加载时执行(避免重复触发)
if (this.processed) return;
this.processed = true;
const hash = window.location.hash;
if (!hash || !$(hash).length) return;
const $targetAnswer = $(hash);
const controlId = $targetAnswer.attr('id');
const $trigger = $(`[aria-controls="${controlId}"]`);
// 1. 展开答案:设置 display + transition + data 属性
$targetAnswer
.css({
'display': 'block',
'transition': 'all 300ms ease-in'
})
.data('slide-toggle', true);
// 2. 同步更新问题项的 ARIA 状态(关键!)
if ($trigger.length) {
$trigger
.attr('aria-expanded', 'true')
.attr('tabindex', '0'); // 确保键盘可聚焦
}
// 3. (可选)滚动到问题区域,提升用户体验
const offsetTop = $trigger.length ? $trigger.offset().top - 80 : $targetAnswer.offset().top - 80;
$('html, body').animate({ scrollTop: offsetTop }, 300);
}
};
})(jQuery, Drupal);注意事项与最佳实践
- 避免 $(hash).show():.show() 会覆盖内联 style,但可能忽略 transition,且不设置 data-* 属性,导致行为不一致。
- ARIA 同步不可省略:aria-expanded="true" 必须由 JS 主动写入问题
- ,否则辅助技术将误判为“已折叠”。
- 防重复执行:使用 this.processed 标志防止 Drupal 多次 attach 行为造成重复操作。
- 兼容性兜底:若目标元素不存在(如 ID 拼写错误或动态生成延迟),应添加存在性校验(如 if ($(hash).length))。
- CSS 优先级建议:推荐将 .list--faq li[id] { display: none; } 写入 CSS,而非依赖内联 style,便于统一维护;JS 仅负责动态切换状态。
总结
URL 锚点驱动的 FAQ 自动展开,本质是状态同步工程:不仅要控制 display,更要维护 DOM 结构、ARIA 属性、CSS 动画及用户焦点体验。上述方案已在 Drupal 9+ 环境中验证有效,兼顾可访问性(a11y)、性能与可维护性。将逻辑封装为独立 Behavior,即可复用于任意符合该 HTML 结构的 FAQ 区块。










