From 528c346dc82773afe39c467ceda56fcdb92dd811 Mon Sep 17 00:00:00 2001 From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com> Date: Wed, 14 Nov 2018 23:49:43 +0100 Subject: [PATCH 01/10] style: rename arguments & comments to prevent line wrapping --- index.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 3fed828..f3a111f 100755 --- a/index.js +++ b/index.js @@ -19,19 +19,16 @@ /** * Check if an element is an anchor pointing to a target on the current page - * @param {HTMLElement} element + * @param {HTMLElement} el */ - function isAnchorToLocalElement(element) { - var localHostname = location.hostname; - var localPathname = location.pathname; - + function isAnchorToLocalElement(el) { return ( // Is an anchor - element.tagName && element.tagName.toLowerCase() === 'a' && + el.tagName && el.tagName.toLowerCase() === 'a' && // Targets an element - element.href.indexOf('#') > 0 && + el.href.indexOf('#') > -1 && // Target is on current page - element.hostname === localHostname && element.pathname === localPathname + el.hostname === location.hostname && el.pathname === location.pathname ); } @@ -48,7 +45,9 @@ } /** - * Check if the clicked target is an anchor pointing to a local element, if so prevent the default behavior and handle the scrolling using the native JavaScript scroll APIs so smoothscroll-Polyfills apply + * Check if the clicked target is an anchor pointing to a local element, + * if so prevent the default behavior and handle the scrolling using the + * native JavaScript scroll APIs so smoothscroll polyfills apply * @param {event} evt */ function handleClick(evt) { From 36d1ed504e25f7964c1b99f3ec5671ddfcfebc11 Mon Sep 17 00:00:00 2001 From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com> Date: Wed, 14 Nov 2018 23:55:28 +0100 Subject: [PATCH 02/10] feat: focus elements upon navigation fix #4 elements with IDs are focused upon navigation, body is focused when scrolling to top --- index.js | 24 ++++++++++++++++++++++-- package.json | 2 +- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index f3a111f..9f9e90f 100755 --- a/index.js +++ b/index.js @@ -32,6 +32,21 @@ ); } + /** + * Focuses an element, if it's not focused after the first try, + * allow focusing by adjusting tabIndex and retry + * @param {HTMLElement} el + */ + function focusElement(el) { + el.focus(); + if (document.activeElement !== el) { + el.setAttribute('tabIndex', '-1'); + // TODO: Only remove outline if it comes from the UA, not the user CSS + el.style.outline = 'none'; + el.focus(); + } + } + /** * Walks up the DOM starting from a given element until an element satisfies the validate function * @param {HTMLElement} element The element from where to start validating @@ -64,8 +79,13 @@ if (isScrollTop || target) { evt.preventDefault(); - if (isScrollTop) window.scrollTo({ top: 0, left: 0, behavior: 'smooth' }); - else target.scrollIntoView({ behavior: 'smooth', block: 'start' }); + if (isScrollTop) { + window.scrollTo({ top: 0, left: 0, behavior: 'smooth' }); + focusElement(document.body); + } else { + target.scrollIntoView({ behavior: 'smooth', block: 'start' }); + focusElement(target); + } } } diff --git a/package.json b/package.json index 2bacdf3..cfe45f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "smoothscroll-anchor-polyfill", - "version": "0.9.4", + "version": "0.10.4", "description": "Apply smooth scroll to anchor links to replicate CSS scroll-behavior", "main": "dist/index.js", "browserslist": [ From 5c6fd3bce8b2b15c4258542abc810fb4315c237b Mon Sep 17 00:00:00 2001 From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com> Date: Thu, 15 Nov 2018 00:02:07 +0100 Subject: [PATCH 03/10] fix: keep native behavior for shift-click and other meta keys --- index.js | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 9f9e90f..3b3fe0a 100755 --- a/index.js +++ b/index.js @@ -66,6 +66,9 @@ * @param {event} evt */ function handleClick(evt) { + // Abort if shift/ctrl-click or not primary click (button !== 0) + if (evt.metaKey || evt.ctrlKey || evt.shiftKey || evt.button !== 0) return; + var clickTarget = getEventTarget(evt); var anchor = findInParents(clickTarget, isAnchorToLocalElement); if (!anchor) return; diff --git a/package.json b/package.json index cfe45f4..7f8981d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "smoothscroll-anchor-polyfill", - "version": "0.10.4", + "version": "0.10.5", "description": "Apply smooth scroll to anchor links to replicate CSS scroll-behavior", "main": "dist/index.js", "browserslist": [ From 66715479a5a4ddc0a369b255bd5158b12cde0eb7 Mon Sep 17 00:00:00 2001 From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com> Date: Thu, 15 Nov 2018 02:14:49 +0100 Subject: [PATCH 04/10] fix: prevent flickers during smoothscroll Use preventScroll where supported, otherwise focus() after a timeout so its auto scrolling doesn't interrupt the smooth scroll --- index.js | 36 ++++++++++++++++++++++++++++++++---- package.json | 2 +- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 3b3fe0a..f1ef392 100755 --- a/index.js +++ b/index.js @@ -8,6 +8,20 @@ return; } + // Check if browser supports focus without automatic scrolling (preventScroll) + var supportsPreventScroll = false; + try { + var el = document.createElement('a'); + // Define getter for preventScroll to find out if the browser accesses it + var preppedFocusOption = Object.defineProperty({}, 'preventScroll', { + get: function () { + supportsPreventScroll = true; + } + }); + // Trigger focus – if browser uses preventScroll the var will be set to true + el.focus(preppedFocusOption); + } catch (e) { } + /** * Get the target element of an event * @param {event} evt @@ -38,12 +52,12 @@ * @param {HTMLElement} el */ function focusElement(el) { - el.focus(); + el.focus({ preventScroll: true }); if (document.activeElement !== el) { el.setAttribute('tabIndex', '-1'); // TODO: Only remove outline if it comes from the UA, not the user CSS el.style.outline = 'none'; - el.focus(); + el.focus({ preventScroll: true }); } } @@ -59,6 +73,9 @@ return false; } + // Stores the setTimeout id of pending focus changes, allows aborting them + var pendingFocusChange; + /** * Check if the clicked target is an anchor pointing to a local element, * if so prevent the default behavior and handle the scrolling using the @@ -70,6 +87,7 @@ if (evt.metaKey || evt.ctrlKey || evt.shiftKey || evt.button !== 0) return; var clickTarget = getEventTarget(evt); + // Check the DOM from the click target upwards if a local anchor was clicked var anchor = findInParents(clickTarget, isAnchorToLocalElement); if (!anchor) return; @@ -80,15 +98,25 @@ var target = !isScrollTop && document.getElementById(targetId); if (isScrollTop || target) { + // Prevent default browser behavior to avoid a jump to the anchor target evt.preventDefault(); + // Clear potential pending focus change triggered by a previous scroll + if (!supportsPreventScroll) window.clearTimeout(pendingFocusChange); if (isScrollTop) { window.scrollTo({ top: 0, left: 0, behavior: 'smooth' }); - focusElement(document.body); } else { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); - focusElement(target); } + + // If the browser supports preventScroll: immediately focus the target + // Otherwise schedule the focus so the smoothscroll isn't interrupted + if (supportsPreventScroll) focusElement(isScrollTop ? document.body : target); + else pendingFocusChange = setTimeout( + focusElement, + 450, + isScrollTop ? document.body : target + ); } } diff --git a/package.json b/package.json index 7f8981d..2fd526b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "smoothscroll-anchor-polyfill", - "version": "0.10.5", + "version": "0.10.6", "description": "Apply smooth scroll to anchor links to replicate CSS scroll-behavior", "main": "dist/index.js", "browserslist": [ From caed04970bbb27dfebffdb25b8825e36a9731910 Mon Sep 17 00:00:00 2001 From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com> Date: Thu, 15 Nov 2018 02:16:12 +0100 Subject: [PATCH 05/10] feat: support HTML5 special fragment "#top" --- index.js | 32 +++++++++++++++----------------- package.json | 2 +- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/index.js b/index.js index f1ef392..44163f9 100755 --- a/index.js +++ b/index.js @@ -91,32 +91,30 @@ var anchor = findInParents(clickTarget, isAnchorToLocalElement); if (!anchor) return; - // If href ends with '#', no id: just scroll to the top - var isScrollTop = anchor.href.match(/#$/); - // Try to retrieve the targeted element - var targetId = !isScrollTop && anchor.hash.slice(1); - var target = !isScrollTop && document.getElementById(targetId); + var hash = anchor.hash; - if (isScrollTop || target) { + // Retrieve target if an id is specified in the hash, otherwise use body. + // If hash is "#top" and no target with id "top" was found, also use body + // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-href + var target = hash ? document.getElementById(hash.slice(1)) : document.body; + if (hash === '#top' && !target) target = document.body; + + if (target) { // Prevent default browser behavior to avoid a jump to the anchor target evt.preventDefault(); // Clear potential pending focus change triggered by a previous scroll if (!supportsPreventScroll) window.clearTimeout(pendingFocusChange); - if (isScrollTop) { - window.scrollTo({ top: 0, left: 0, behavior: 'smooth' }); - } else { - target.scrollIntoView({ behavior: 'smooth', block: 'start' }); - } + // Use scroll APIs to scroll to top (if target is body) or to the element + // This allows polyfills for these APIs to do their smooth scrolling magic + var scrollTop = target === document.body; + if (scrollTop) window.scroll({ top: 0, left: 0, behavior: 'smooth' }); + else target.scrollIntoView({ behavior: 'smooth', block: 'start' }); // If the browser supports preventScroll: immediately focus the target // Otherwise schedule the focus so the smoothscroll isn't interrupted - if (supportsPreventScroll) focusElement(isScrollTop ? document.body : target); - else pendingFocusChange = setTimeout( - focusElement, - 450, - isScrollTop ? document.body : target - ); + if (supportsPreventScroll) focusElement(target); + else pendingFocusChange = setTimeout(focusElement, 450, target); } } diff --git a/package.json b/package.json index 2fd526b..ceae6f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "smoothscroll-anchor-polyfill", - "version": "0.10.6", + "version": "0.11.0", "description": "Apply smooth scroll to anchor links to replicate CSS scroll-behavior", "main": "dist/index.js", "browserslist": [ From b909ca18343e94a80882cb28b6a4f5d2a58ab4b9 Mon Sep 17 00:00:00 2001 From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com> Date: Thu, 15 Nov 2018 04:20:53 +0100 Subject: [PATCH 06/10] style: update HTML formatting --- index.html | 54 +++++++++++------------------------------------------- 1 file changed, 11 insertions(+), 43 deletions(-) diff --git a/index.html b/index.html index e45023e..74652fe 100755 --- a/index.html +++ b/index.html @@ -16,8 +16,8 @@ - @@ -18,24 +25,7 @@ @@ -428,21 +418,9 @@

Privacy

From ab54cb1a426909c24ea3c89711ba9e23b8897257 Mon Sep 17 00:00:00 2001 From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com> Date: Thu, 15 Nov 2018 07:10:42 +0100 Subject: [PATCH 10/10] docs: add changelog --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100755 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100755 index 0000000..a7cab86 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.12.0] - 2018-11-15 +### Added +- The special fragment `#top` is now supported for scrolling to the top, but only if no element with id `top` is found +- After navigating to an anchor, the respective hash is now appended to the URL using `history.pushState()` to match default browser behavior. +- When a hashchange event occurs, the polyfill tries to cancel the instant jumping scroll to the new hash, handling it with the smooth scroll instead. +- When navigating to an anchor, the anchor is now focused. +- In browsers supporting the optional `preventScroll` argument, the anchor is focused immediately and the focus scroll is prevented by passing this argument. +- If the browser doesn't support `preventScroll` (e.g. Internet Explorer), the focus is scheduled to happen 450ms after the smooth scroll started so it does not interfere with the smooth scrolling (which caused flickering). + + +### Changed +- The flag to enforce the polyfill (even if the browser has native support) is now called (`window.__forceSmoothscrollAnchorPolyfill__`). The docs have been updated to reflect this. + + +### Fixed +- The polyfill now properly handles Shift/Meta keys and allows for opening links in new windows by shift-clicking instead of preventing it with `event.preventDefault()` +- The docs website now works in Internet Explorer 9, polyfills for `Element.classList`, `requestAnimationFrame` + an alternative for flexbox layouts have been added