平滑滚动(从起始链接到目标锚点的视口中位置的动画变化)可以作为网站添加的一个不错的交互细节,为用户体验带来精致的感觉。如果您不相信我,看看有多少人对 CSS-Tricks 上的 平滑滚动代码片段 做出了回应。

无论您如何实现此功能,都应解决一些无障碍性问题:焦点管理和动画。
焦点管理
务必确保所有内容都只能通过键盘访问,因为某些用户完全依赖键盘进行导航。因此,当键盘用户浏览内容并点击使用平滑滚动的链接时,他们应该能够使用它导航到目标锚元素。
换句话说,当您点击链接时,键盘焦点也应该跟随它,并能够访问目标后的下一个元素。这是一个链接到页面锚点的示例,其中焦点保持不变,因为没有使用 JavaScript

亲自尝试一下:使用 Tab 键通过 此演示 进行导航。请注意,Safari/WebKit 存在一个关于键盘焦点的未解决的错误。
原始 jQuery 示例
让我们看看来自 原始帖子 的 jQuery 示例
$(function() {
$('a[href*="#"]:not([href="#"])').click(function() {
if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {
var target = $(this.hash);
target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
if (target.length) {
$('html, body').animate({
scrollTop: target.offset().top
}, 1000);
return false;
}
}
});
});
这是在 W3C 的一个页面上实现的

:focus
,但焦点没有改变在这里,我们看到 “跳至内容”链接 没有将焦点设置到导航到的内容上。因此,如果我们使用此示例,我们会使键盘用户的导航体验变差,因为用户期望导航到目标内容,但他们没有,因为焦点没有更新以反映更改。
亲自尝试一下,使用 Tab 键通过 此演示 进行导航。
哪里出错了?
为什么这不起作用?我们正在使用 JavaScript 接管正常的浏览器链接行为(请注意,URL 不会随着 /#target 更新),这意味着我们需要使用 JavaScript 设置焦点。在 jQuery 中,这将是$(target).focus();
。
为了使这在不可聚焦的目标元素(section、div、span、h1-6 等)上正常工作,我们必须在它们上面设置tabindex="-1"
以便能够使用$(target).focus();
。我们可以在 html 标记中直接在不可聚焦的目标元素上添加tabindex="-1"
,也可以像这里看到的那样使用 JavaScript 添加它。
$(function() {
$('a[href*="#"]:not([href="#"])').click(function() {
if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {
var target = $(this.hash);
target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
if (target.length) {
$('html, body').animate({
scrollTop: target.offset().top
}, 1000);
target.focus(); // Setting focus
if (target.is(":focus")){ // Checking if the target was focused
return false;
} else {
target.attr('tabindex','-1'); // Adding tabindex for elements not focusable
target.focus(); // Setting focus
};
return false;
}
}
});
});
亲自尝试一下,使用 Tab 键通过 此演示 进行导航。不要忘记你的 :focus 样式!

更好的方法?
如果我们不劫持正常的浏览器导航行为来处理此功能,可能对用户更有利。例如,如果您点击链接,您可以使用浏览器后退按钮返回。此外,您可以将当前 URL 添加到书签(或复制粘贴),浏览器将从您上次点击的链接转到该特定目标。
// URL updates and the element focus is maintained
// originally found via in Update 3 on http://www.learningjquery.com/2007/10/improved-animated-scrolling-script-for-same-page-links
// filter handling for a /dir/ OR /indexordefault.page
function filterPath(string) {
return string
.replace(/^\//, '')
.replace(/(index|default).[a-zA-Z]{3,4}$/, '')
.replace(/\/$/, '');
}
var locationPath = filterPath(location.pathname);
$('a[href*="#"]').each(function () {
var thisPath = filterPath(this.pathname) || locationPath;
var hash = this.hash;
if ($("#" + hash.replace(/#/, '')).length) {
if (locationPath == thisPath && (location.hostname == this.hostname || !this.hostname) && this.hash.replace(/#/, '')) {
var $target = $(hash), target = this.hash;
if (target) {
$(this).click(function (event) {
event.preventDefault();
$('html, body').animate({scrollTop: $target.offset().top}, 1000, function () {
location.hash = target;
$target.focus();
if ($target.is(":focus")){ //checking if the target was focused
return false;
}else{
$target.attr('tabindex','-1'); //Adding tabindex for elements not focusable
$target.focus(); //Setting focus
};
});
});
}
}
}
});

在这里,URL 在每次点击锚点时都会更新。亲自尝试一下,使用 Tab 键通过 此演示 进行导航。
原生示例
让我们看看来自 CSS-Tricks 帖子 的原生浏览器示例。(还有一个 polyfill。)
document.querySelector('#target-of-thing-clicked-on').scrollIntoView({
behavior: 'smooth'
});
不幸的是,使用这种方法,我们遇到了与 jQuery 方法相同的问题,即页面在视口中滚动,但不会更新键盘焦点。因此,如果我们想走这条路,我们仍然必须设置.focus()
并确保不可聚焦的目标元素接收tabindex="-1"
。
这里另一个需要考虑的问题是缺少滚动停止时的回调函数。这可能是也可能不是问题。您将与滚动同时移动焦点,而不是在末尾移动,这可能有点奇怪。无论如何,都需要做一些工作!
运动与无障碍性
有些人会因为屏幕上的快速移动而 感到恶心。我建议使用较慢的运动速度,因为如果 用户要跳过大量内容,如果速度过快,可能会导致眩晕感。
此外,为用户提供一种关闭动画的方法也不失为一个好主意。幸运的是,Safari 10.1 引入了 减少运动媒体查询,它为开发人员提供了一种以可通过浏览器级别禁用的方式包含动画的方法。
/* JavaScript MediaQueryList Interface */
var motionQuery = window.matchMedia('(prefers-reduced-motion)');
if (motionQuery.matches) {
/* reduce motion */
}
motionQuery.addListener( handleReduceMotionChanged );
不幸的是,其他浏览器尚未实现此功能。因此,在支持范围扩大到不止一个浏览器之前,我们可以通过界面为用户提供一个选项来启用/禁用可能导致用户问题的动画。
<label>
<input type="checkbox" id="animation" name="animation" checked="checked">
Enable Animation
</label>
$(this).click(function(event) {
if ($('#animation').prop('checked')) {
event.preventDefault();
$('html, body').animate({scrollTop: $target.offset().top}, 1000, function() {
location.hash = target;
$target.focus();
if ($target.is(":focus")) {
return !1;
} else {
$target.attr('tabindex', '-1');
$target.focus()
}
})
}
});
亲自尝试一下 此演示。
这是一个非常有趣的话题。推动运动和动画的目的是增强您在原本二维的 UI 中的空间方位感,这非常有趣——而且显然很受欢迎。您提到
但这不仅仅是一些人——而是很多人。轻度前庭障碍在男性中非常普遍,虽然它不会让人呕吐,但肯定会对用户体验产生负面影响,会令人感到突兀,并且其他方面也不理想。
关键在于控制:如果您正在四处切换标签,并且您切换到视口下方,那么网站会平滑滚动,根据我的经验,这种感觉就像延迟和不受控制的运动。因此,首先,我就像:呃,为什么视口还没有改变;其次:呕 [呕吐声]。你明白我的意思。
视口应该以焦点的速度改变,您可以相当快地切换标签。
另一方面,如果您使用滚轮或滑动或与滚动条交互来有意地滚动——那么动画化该滚动是相当无害的。但是,我认为,键盘导航 != 滚动意图。这些是不同的和不同的输入,具有不同和不同的约定。
@Michael Schofield – 是的!我们永远不想仅仅因为访问网站而让人感到恶心。希望更多网站会为用户提供关闭动画的选项,或者更多浏览器会介入并提供像 Safari 这样的解决方案(这是我所期待的)。
这里另一个需要考虑的问题是缺少滚动停止时的回调函数。这可能是也可能不是问题。您将与滚动同时移动焦点,而不是在末尾移动,这可能有点奇怪。无论如何,都需要做一些工作!
我可以确认这是一个问题。将焦点() 设置到目标会劫持平滑滚动,并且它会突然跳转到目标。
我想解决方法是使用与滚动持续时间相同的
setTimeout()
,并在其中更改焦点。不过感觉不太优雅。或者转向 animationend 事件路由
即将到来的最简单的方法绝对是 CSS 中的
scroll-behavior
:root {
scroll-behavior: smooth;
}
太棒了!
scroll-behavior
将消除对 JS(用于滚动操作)的需求,并消除在移动到可聚焦元素时对焦点管理的需求。我真的很期待浏览器实现此功能。但是,无论如何,我们仍然需要使用
tabindex="-1"
使不可聚焦元素(section、div、span、h1-6 等)可聚焦。此外,我们仍然需要提供一种方法来为那些发现运动令人头晕目眩的人禁用滚动。
即使这样,这看起来仍然是一个很棒的 CSS 功能!感谢分享!
值得注意的是,还有一个相应的 CSS scroll-behavior 属性,可以将其设置为
smooth
。我认为,这使得同时拥有平滑滚动和键盘可访问性成为可能无需 JavaScript(当然是在支持的浏览器中)。您可以使用以下代码通过脚本检查元素是否可聚焦
您可以执行以下操作
注意:使用
location.hash
,而不是this.hash
。简化运动媒体查询。这是我之前不知道的,并且很高兴了解到。谢谢,Chris。
为了实现键盘友好的平滑滚动作为渐进增强并且无需 jQuery,您可以使用 Fetch Injection 执行以下操作
const el = document.querySelector('details summary')
el.onclick = (evt) => {
fetchInject([
'https://cdn.jsdelivr.net.cn/smooth-scroll/10.2.1/smooth-scroll.min.js'
])
el.onclick = null
}
有关 Fetch Injection 的更多信息,请参阅 Hack Cabin。
不错的文章,我喜欢为用户提供禁用动画的可能性。
在最后一个演示中,后退按钮在 FF 52.0.2 中无法正常工作:URL 在哈希之前的最后一个路径组件中具有“null”。
实际上,我误读了 URL:这不是“null”,而是“nujibey”。我的错。不过,后退按钮仍然无法按预期工作 :)
我是不是错过了什么?当我们可以在滚动动画结束后触发事件时,为什么要使用 :focus?
如果我们触发 hashchange,那么我们就会自动重置 tabindex。以下是它的工作原理
http://codepen.io/ppscvalentin/pen/JNNBzQ
如果目标不可聚焦,则应添加负 tabindex(或 0),然后将焦点设置在其上。
实际上,您不需要。当您更改 window.location 时,tabindex 会设置为正确的值。查看笔