{"id":253531,"date":"2017-04-14T06:22:58","date_gmt":"2017-04-14T13:22:58","guid":{"rendered":"http:\/\/css-tricks.com\/?p=253531"},"modified":"2017-04-14T06:22:58","modified_gmt":"2017-04-14T13:22:58","slug":"smooth-scrolling-accessibility","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/smooth-scrolling-accessibility\/","title":{"rendered":"Smooth Scrolling and Accessibility"},"content":{"rendered":"

Smooth scrolling<\/em> (the animated change of position within the viewport from the originating link to the destination anchor) can be a nice interaction detail added to a site, giving a polished feel to the experience. If you don’t believe me, look at how many people have responded to the Smooth Scrolling snippet<\/a> here on CSS-Tricks.<\/p>\n

<\/p>\n

\"smooth
Smooth scrolling vs abrupt jumps<\/figcaption><\/figure>\n

Regardless of how you implement the feature, there are a few accessibility issues that should be addressed: focus management and animation. <\/p>\n

Focus Management<\/h3>\n

It is important to ensure that all content<\/strong> can be accessed with the keyboard alone because some users 100% rely<\/strong> on the keyboard for navigation. So, when a keyboard user navigates through the content and hits a link that uses smooth scrolling, they should be able to use it to navigate to the target anchor element. <\/p>\n

In other words, when you follow a link, the keyboard focus<\/a> should follow it, too and be able to access the next element after the target. Here is an example of links to page anchors where focus is maintained because there is no JavaScript used:<\/p>\n

\"Example
Default browser behavior with links to page anchors and focus is properly maintained.<\/figcaption><\/figure>\n

Try it for yourself: use the tab key to navigate using this demo<\/a>. Please note that Safari\/WebKit has an outstanding bug<\/a> regarding keyboard focus.<\/p>\n

Original jQuery Example<\/h3>\n

Let’s look at the jQuery example from the original post<\/a>:<\/p>\n

$(function() {\r\n  $('a[href*=\"#\"]:not([href=\"#\"])').click(function() {\r\n    if (location.pathname.replace(\/^\\\/\/,'') == this.pathname.replace(\/^\\\/\/,'') && location.hostname == this.hostname) {\r\n      var target = $(this.hash);\r\n      target = target.length ? target : $('[name=' + this.hash.slice(1) +']');\r\n      if (target.length) {\r\n        $('html, body').animate({\r\n          scrollTop: target.offset().top\r\n        }, 1000);\r\n        return false;\r\n      }\r\n    }\r\n  });\r\n});<\/code><\/pre>\n

This is implemented on a page from the W3C:<\/p>\n

\"Smooth
the Skip Nav should :focus<\/code> on the content, but this does not change focus<\/figcaption><\/figure>\n

Here, we see the “Skip to Content” link<\/a> is not setting focus on the content that was navigated to. So, if we use this example, we make the navigation worse for folks using the keyboard because the user expects to be navigating to the content that is targeted, but they’re not, because focus is not updated to reflect the change.<\/p>\n

Try it for yourself using the tab key to navigate with this demo<\/a>.<\/p>\n

What Went Wrong?<\/h3>\n

Why doesn’t this work? We’re using JavaScript to take over the normal browser linking behavior (note the URL never updates with the \/#target) which means we need set the focus with JavaScript. In jQuery that would be $(target).focus();<\/code>. <\/p>\n

In order for this to work on non-focusable target elements (section, div, span, h1-6, ect), we have to set tabindex=\"-1\"<\/code> on them in order to be able to $(target).focus();<\/code>. We can either add the tabindex=\"-1\"<\/code> directly on non-focusable target elements in the html markup or add it using JavaScript as seen here.<\/p>\n

$(function() {\r\n  $('a[href*=\"#\"]:not([href=\"#\"])').click(function() {\r\n    if (location.pathname.replace(\/^\\\/\/,'') == this.pathname.replace(\/^\\\/\/,'') && location.hostname == this.hostname) {\r\n      var target = $(this.hash);\r\n      target = target.length ? target : $('[name=' + this.hash.slice(1) +']');\r\n      if (target.length) {\r\n        $('html, body').animate({\r\n          scrollTop: target.offset().top\r\n        }, 1000);\r\n        target.focus(); \/\/ Setting focus\r\n        if (target.is(\":focus\")){ \/\/ Checking if the target was focused\r\n          return false;\r\n        } else {\r\n          target.attr('tabindex','-1'); \/\/ Adding tabindex for elements not focusable\r\n          target.focus(); \/\/ Setting focus\r\n        };\r\n        return false;\r\n      }\r\n    }\r\n  });\r\n});<\/code><\/pre>\n

Try it for yourself using the tab key to navigate with this demo<\/a>. Don’t forget your :focus styling!<\/a><\/p>\n

\"Focus<\/figure>\n

A Better Way?<\/h3>\n

It might be better for users in general if we handle this feature without hijacking the normal browser navigation behavior. For example, if you follow a link, you can go back with the browser back button. Also, you can bookmark (or copy and paste) the current URL and the browser will go to that specific destination from the last link you clicked.<\/p>\n

\/\/ URL updates and the element focus is maintained\r\n\/\/ originally found via in Update 3 on http:\/\/www.learningjquery.com\/2007\/10\/improved-animated-scrolling-script-for-same-page-links\r\n\r\n\/\/ filter handling for a \/dir\/ OR \/indexordefault.page\r\nfunction filterPath(string) {\r\n  return string\r\n    .replace(\/^\\\/\/, '')\r\n    .replace(\/(index|default).[a-zA-Z]{3,4}$\/, '')\r\n    .replace(\/\\\/$\/, '');\r\n}\r\n\r\nvar locationPath = filterPath(location.pathname);\r\n$('a[href*=\"#\"]').each(function () {\r\n  var thisPath = filterPath(this.pathname) || locationPath;\r\n  var hash = this.hash;\r\n  if ($(\"#\" + hash.replace(\/#\/, '')).length) {\r\n    if (locationPath == thisPath && (location.hostname == this.hostname || !this.hostname) && this.hash.replace(\/#\/, '')) {\r\n      var $target = $(hash), target = this.hash;\r\n      if (target) {\r\n        $(this).click(function (event) {\r\n          event.preventDefault();\r\n          $('html, body').animate({scrollTop: $target.offset().top}, 1000, function () {\r\n            location.hash = target; \r\n            $target.focus();\r\n            if ($target.is(\":focus\")){ \/\/checking if the target was focused\r\n              return false;\r\n            }else{\r\n              $target.attr('tabindex','-1'); \/\/Adding tabindex for elements not focusable\r\n              $target.focus(); \/\/Setting focus\r\n            };\r\n          });       \r\n        });\r\n      }\r\n    }\r\n  }\r\n});<\/code><\/pre>\n
\"Focus
Example showing the URL updates with every anchor that is clicked<\/figcaption><\/figure>\n

Here, the URL updates with every anchor that is clicked. Try it for yourself using the tab key to navigate using this demo<\/a>.<\/p>\n

Native Example<\/h3>\n

Let’s look at the native browser example from the CSS-Tricks post<\/a>. (There is also a polyfill<\/a>.)<\/p>\n

document.querySelector('#target-of-thing-clicked-on').scrollIntoView({ \r\n  behavior: 'smooth' \r\n});<\/code><\/pre>\n

Unfortunately, with this method, we run into the same issue as the jQuery method where the page scrolls within the viewport, but does not update the keyboard focus. So, if we want to go this route, we would still have to set .focus()<\/code> and ensure non-focusable target elements receive tabindex=\"-1\"<\/code>.<\/p>\n

Another consideration here is the lack of a callback function for when the scrolling stops. That may or may not be a problem. You’d move the focus simultaneously with the scrolling rather than at the end, which may or may not be a little weird. Anyway, there will be work to do!<\/p>\n

Motion and Accessibility<\/h3>\n

Some people can literally get sick<\/a> from the fast movement on the screen. I’d recommend a slow speed of the motion because if the user is going to jump across a lot of content<\/a>, it can cause a dizzying effect if it’s too fast. <\/p>\n

Also, it’s not a bad idea to offer users a way to turn off animations. Fortunately, Safari 10.1 introduced the Reduced Motion Media Query<\/a> which provides developers a method to include animation in a way that can be disabled at the browser level.<\/p>\n

\/* JavaScript MediaQueryList Interface *\/\r\nvar motionQuery = window.matchMedia('(prefers-reduced-motion)');\r\nif (motionQuery.matches) {\r\n  \/* reduce motion *\/\r\n}\r\nmotionQuery.addListener( handleReduceMotionChanged );<\/code><\/pre>\n

Unfortunately, no other browsers have implemented this feature yet. So, until support is spread wider than one browser, we can provide the user an option via the interface to enable\/disable animation that could cause users issues.<\/p>\n

<label>\r\n  <input type=\"checkbox\" id=\"animation\" name=\"animation\" checked=\"checked\">\r\n  Enable Animation\r\n<\/label> <\/code><\/pre>\n
$(this).click(function(event) {\r\n  if ($('#animation').prop('checked')) {\r\n    event.preventDefault();\r\n    $('html, body').animate({scrollTop: $target.offset().top}, 1000, function() {\r\n      location.hash = target;\r\n      $target.focus();\r\n      if ($target.is(\":focus\")) {\r\n        return !1;\r\n      } else {\r\n        $target.attr('tabindex', '-1');\r\n        $target.focus()\r\n      }\r\n    })\r\n  }\r\n});<\/code><\/pre>\n

Try it for yourself with this demo<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"

Smooth scrolling (the animated change of position within the viewport from the originating link to the destination anchor) can be a nice interaction detail added to a site, giving a polished feel to the experience. If you don’t believe me, look at how many people have responded to the Smooth Scrolling snippet here on CSS-Tricks.<\/p>\n","protected":false},"author":243824,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"c2c_always_allow_admin_comments":false,"footnotes":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":[]},"categories":[4],"tags":[466,754,1215],"jetpack_publicize_connections":[],"acf":[],"jetpack_featured_media_url":"","jetpack-related-posts":[{"id":294680,"url":"https:\/\/css-tricks.com\/need-to-scroll-to-the-top-of-the-page\/","url_meta":{"origin":253531,"position":0},"title":"Need to scroll to the top of the page?","date":"September 2, 2019","format":false,"excerpt":"Perhaps the easiest way to offer that to the user is a link that targets an ID on the element. So like... Jump to top of page But we've got a few options here. If you want it to smooth scroll up to the top, you can do that in\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2019\/08\/smooth-stones.png?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":254403,"url":"https:\/\/css-tricks.com\/focus-styles-non-interactive-elements\/","url_meta":{"origin":253531,"position":1},"title":"Focus Styles on Non-Interactive Elements?","date":"May 2, 2017","format":false,"excerpt":"Last month, Heather Migliorisi looked at the accessibility of Smooth Scrolling. In order to do smooth scrolling, you: Check if the clicked link is #jump link Stop the browser default behavior of jumping immediately to that element on the page Animate the scrolling to the element the #jump link pointed\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":252840,"url":"https:\/\/css-tricks.com\/scrolling-web-primer\/","url_meta":{"origin":253531,"position":2},"title":"Scrolling on the Web: A Primer","date":"March 16, 2017","format":false,"excerpt":"Scrolling is complicated. Nolan Lawson: User scrolls with two fingers on a touch pad User scrolls with one finger on a touch screen User scrolls with a mouse wheel on a physical mouse User clicks the sidebar and drags it up and down User presses up, down, PageUp, PageDown, or\u2026","rel":"","context":"In "Link"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":283203,"url":"https:\/\/css-tricks.com\/downsides-of-smooth-scrolling\/","url_meta":{"origin":253531,"position":3},"title":"Downsides of Smooth Scrolling","date":"March 11, 2019","format":false,"excerpt":"Smooth scrolling has gotten a lot easier. If you want it all the time on your page, and you are happy letting the browser deal with the duration for you, it's a single line of CSS: html { scroll-behavior: smooth; } I tried this on version 17 of this site,\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2018\/07\/smooth-scroll-nav.gif?fit=800%2C400&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":333276,"url":"https:\/\/css-tricks.com\/cancelable-smooth-scrolling\/","url_meta":{"origin":253531,"position":4},"title":"“Cancelable” Smooth Scrolling","date":"February 1, 2021","format":false,"excerpt":"Here's the situation: Your site offers a \"scroll back to top\" button, and you've implemented smooth scrolling. As the page scrolls back to the top, users see something that catches their eye and they want to stop the scrolling, so they do a smidge of a scroll on the mouse\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2020\/09\/scroll-to-top.png?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":332473,"url":"https:\/\/css-tricks.com\/fixing-smooth-scrolling-with-find-on-page\/","url_meta":{"origin":253531,"position":5},"title":"Fixing Smooth Scrolling with Find-on-Page","date":"January 12, 2021","format":false,"excerpt":"Back when we released the v17 design (we're on v18 now) of this site. I added html { scroll-behavior: smooth; } to the CSS. Right away, I got comments like this (just one example): ... when you control+f or command+f and search on CSS-Tricks, it\u2019ll scroll very slowly instead of\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2021\/01\/smooth-scroll.jpg?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]}],"featured_media_src_url":null,"_links":{"self":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/253531"}],"collection":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/users\/243824"}],"replies":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/comments?post=253531"}],"version-history":[{"count":19,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/253531\/revisions"}],"predecessor-version":[{"id":253748,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/253531\/revisions\/253748"}],"wp:attachment":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media?parent=253531"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/categories?post=253531"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/tags?post=253531"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}