From 7d2188cb938ce7b9cff79f837ac03644891eb2b7 Mon Sep 17 00:00:00 2001
From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com>
Date: Fri, 7 Dec 2018 21:40:34 +0100
Subject: [PATCH 1/8] feat: export polyfill and destroy methods
The polyfill now exports a method 'destroy' as part of it's public API
(previously it was returned, only for testing) so you can stop it.
To restart later, an additional method 'polyfill' is exposed.
---
index.js | 69 ++++++++++++++++++++++++++++++--------------------------
test.js | 37 +++++++++++-------------------
2 files changed, 50 insertions(+), 56 deletions(-)
diff --git a/index.js b/index.js
index 7e59a05..abf7ee1 100755
--- a/index.js
+++ b/index.js
@@ -4,31 +4,28 @@
* (c) 2018 Jonas Kuske
* Released under the MIT License.
*/
-(function (indexFn) {
- if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
- // Test env: export function so it can run multiple times
- exports.polyfill = indexFn;
+
+(function (global, factory) {
+ var SmoothscrollAnchorPolyfill = factory();
+ if (typeof exports === 'object' && typeof module !== 'undefined') {
+ module.exports = SmoothscrollAnchorPolyfill;
} else {
- // Else run immediately, no time to waste!
- indexFn();
+ global.SmoothscrollAnchorPolyfill = SmoothscrollAnchorPolyfill;
}
-})(function () {
- /* */
+ // In test environment: abort, else run polyfill immediately
+ if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') return;
+ else SmoothscrollAnchorPolyfill.polyfill();
+})(this, function () {
- // Noop to be returned if polyfill returns early. Will be assigned later
- var destroy = function () { };
+ /* */
// Abort if outside browser (window is undefined in Node etc.)
var isBrowser = typeof window !== 'undefined';
- if (!isBrowser) return destroy;
+ if (!isBrowser) return;
var w = window, d = document, docEl = d.documentElement;
- var forcePolyfill = w.__forceSmoothscrollAnchorPolyfill__ === true;
- // Abort if smoothscroll is natively supported and force flag is not set
- if (!forcePolyfill && 'scrollBehavior' in docEl.style) return destroy;
-
// Check if browser supports focus without automatic scrolling (preventScroll)
var supportsPreventScroll = false;
try {
@@ -75,7 +72,7 @@
* If a valid CSS property value for scroll-behavior is passed, returns
* whether it specifies smooth scroll behavior or not, else returns null
* @param {any} value The value to check
- * @returns {boolean|undefined}
+ * @returns {?boolean} The specified scroll behavior or null
*/
function getScrollBehavior(value) {
var status = null;
@@ -99,10 +96,8 @@
*/
function isAnchorToLocalElement(el) {
return (
- // Is an anchor
- el.tagName && el.tagName.toLowerCase() === 'a' &&
- // Targets an element
- el.href.indexOf('#') > -1 &&
+ // Is an anchor with a fragment in the url
+ el.tagName && el.tagName.toLowerCase() === 'a' && /#/.test(el.href) &&
// Target is on current page
el.hostname === location.hostname && el.pathname === location.pathname
);
@@ -256,16 +251,26 @@
lastTwoScrollPos[1] = getScrollTop();
}
- // Register all event handlers
- d.addEventListener('click', handleClick, false);
- d.addEventListener('scroll', trackScrollPositions);
- w.addEventListener("hashchange", handleHashChange);
-
- // Assign destroy function that unregisters event listeners. Used for testing
- destroy = function () {
- d.removeEventListener('click', handleClick, false);
- d.removeEventListener('scroll', trackScrollPositions);
- w.removeEventListener('hashchange', handleHashChange);
- }
- return destroy;
+ return {
+ /**
+ * Starts the polyfill by attaching the neccessary EventListeners
+ */
+ polyfill: function () {
+ // Abort if smoothscroll is natively supported and force flag is not set
+ var forcePolyfill = w.__forceSmoothscrollAnchorPolyfill__ === true;
+ if (!forcePolyfill && 'scrollBehavior' in docEl.style) return;
+
+ d.addEventListener('click', handleClick, false);
+ d.addEventListener('scroll', trackScrollPositions);
+ w.addEventListener('hashchange', handleHashChange);
+ },
+ /**
+ * Stops the polyfill by removing all EventListeners
+ */
+ destroy: function () {
+ d.removeEventListener('click', handleClick, false);
+ d.removeEventListener('scroll', trackScrollPositions);
+ w.removeEventListener('hashchange', handleHashChange);
+ }
+ };
});
diff --git a/test.js b/test.js
index b83e55d..0a6cb7b 100755
--- a/test.js
+++ b/test.js
@@ -1,4 +1,4 @@
-const { polyfill } = require('./index')
+const { polyfill, destroy } = require('./index')
const insertElement = (type, attrs) => {
const el = document.createElement(type)
@@ -22,7 +22,7 @@ afterEach(() => {
document.documentElement.removeAttribute('style')
document.body.innerHTML = ''
- document.head.innerHTML = ''
+ destroy()
})
describe('General', () => {
@@ -33,11 +33,10 @@ describe('General', () => {
document.documentElement.style.scrollBehavior = 'smooth'
const spy = jest.spyOn(window, 'scroll')
- const destroy = polyfill()
+ polyfill()
anchor.click()
expect(spy).not.toHaveBeenCalled()
- destroy()
})
@@ -50,11 +49,10 @@ describe('General', () => {
window.__forceSmoothscrollAnchorPolyfill__ = true
const spy = jest.spyOn(window, 'scroll')
- const destroy = polyfill()
+ polyfill()
anchor.click()
expect(spy).toHaveBeenCalled()
- destroy()
})
})
@@ -66,12 +64,11 @@ describe('Scroll targeting', () => {
const anchorTwo = insertElement('a', { href: '#' })
const spy = jest.spyOn(window, 'scroll')
- const destroy = polyfill()
+ polyfill()
anchor.click()
anchorTwo.click()
expect(spy).toHaveBeenCalledTimes(2)
- destroy()
})
it('Scrolls to targeted element if anchor targets its ID', () => {
@@ -82,12 +79,11 @@ describe('Scroll targeting', () => {
const windowSpy = jest.spyOn(window, 'scroll')
const spy = jest.spyOn(target, 'scrollIntoView')
- const destroy = polyfill()
+ polyfill()
anchor.click()
expect(windowSpy).not.toHaveBeenCalled()
expect(spy).toHaveBeenCalled()
- destroy()
})
it('Scrolls to element instead of top if hash "#top" targets an ID', () => {
@@ -98,12 +94,11 @@ describe('Scroll targeting', () => {
const windowSpy = jest.spyOn(window, 'scroll')
const spy = jest.spyOn(target, 'scrollIntoView')
- const destroy = polyfill()
+ polyfill()
anchor.click()
expect(spy).toHaveBeenCalled()
expect(windowSpy).not.toHaveBeenCalled()
- destroy()
})
})
@@ -115,11 +110,10 @@ describe('Enabling & Toggling the polyfill', () => {
const anchor = insertElement('a', { href: '#' })
const spy = jest.spyOn(window, 'scroll')
- const destroy = polyfill()
+ polyfill()
anchor.click()
expect(spy).toHaveBeenCalled()
- destroy()
})
it('Can be enabled through the style attribute on ', () => {
@@ -128,18 +122,17 @@ describe('Enabling & Toggling the polyfill', () => {
const anchor = insertElement('a', { href: '#' })
const spy = jest.spyOn(window, 'scroll')
- const destroy = polyfill()
+ polyfill()
anchor.click()
expect(spy).toHaveBeenCalled()
- destroy()
})
it('Can be enabled by documentElement.style.scrollBehavior', () => {
const anchor = insertElement('a', { href: '#' })
const spy = jest.spyOn(window, 'scroll')
- const destroy = polyfill()
+ polyfill()
// Only set scrollBehavior after the polyfill ran, so it doesn't bail
// due to checking for native support ('scrollBehavior' in docEl.style)
@@ -147,7 +140,6 @@ describe('Enabling & Toggling the polyfill', () => {
anchor.click()
expect(spy).toHaveBeenCalled()
- destroy()
})
it('Can be disabled through method with higher specificity', () => {
@@ -157,7 +149,7 @@ describe('Enabling & Toggling the polyfill', () => {
const anchor = insertElement('a', { href: '#' })
const spy = jest.spyOn(window, 'scroll')
- const destroy = polyfill()
+ polyfill()
anchor.click()
expect(spy).toHaveBeenCalledTimes(1)
@@ -169,7 +161,6 @@ describe('Enabling & Toggling the polyfill', () => {
document.documentElement.style.scrollBehavior = 'smooth'
anchor.click()
expect(spy).toHaveBeenCalledTimes(2)
- destroy()
})
})
@@ -180,21 +171,20 @@ describe('Enabling & Toggling the polyfill', () => {
const anchor = insertElement('a', { href: '#top' })
const spy = jest.spyOn(window, 'scroll')
- const destroy = polyfill()
+ polyfill()
anchor.click()
expect(spy).toHaveBeenCalledTimes(1)
document.documentElement.style.scrollBehavior = 'auto'
anchor.click()
expect(spy).toHaveBeenCalledTimes(1)
- destroy()
})
it('Responds to computedStyle of font-family', () => {
const anchor = insertElement('a', { href: '#top' })
const spy = jest.spyOn(window, 'scroll')
- const destroy = polyfill()
+ polyfill()
anchor.click()
expect(spy).toHaveBeenCalledTimes(0)
@@ -204,7 +194,6 @@ describe('Enabling & Toggling the polyfill', () => {
document.documentElement.style.font = '100 1rem "scroll-behavior:inherit"'
anchor.click()
expect(spy).toHaveBeenCalledTimes(1)
- destroy()
})
})
})
\ No newline at end of file
From 58be9645760f10460a06a539e34c9ff7febe16b6 Mon Sep 17 00:00:00 2001
From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com>
Date: Fri, 7 Dec 2018 21:49:25 +0100
Subject: [PATCH 2/8] feat: accept force flag as parameter when invoking
polyfill method
---
index.js | 523 ++++++++++++++++++++++++++++---------------------------
test.js | 16 +-
2 files changed, 279 insertions(+), 260 deletions(-)
diff --git a/index.js b/index.js
index abf7ee1..7f3c96e 100755
--- a/index.js
+++ b/index.js
@@ -5,272 +5,277 @@
* Released under the MIT License.
*/
-(function (global, factory) {
- var SmoothscrollAnchorPolyfill = factory();
- if (typeof exports === 'object' && typeof module !== 'undefined') {
- module.exports = SmoothscrollAnchorPolyfill;
- } else {
- global.SmoothscrollAnchorPolyfill = SmoothscrollAnchorPolyfill;
- }
-
- // In test environment: abort, else run polyfill immediately
- if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') return;
- else SmoothscrollAnchorPolyfill.polyfill();
-})(this, function () {
-
- /* */
-
- // Abort if outside browser (window is undefined in Node etc.)
- var isBrowser = typeof window !== 'undefined';
- if (!isBrowser) return;
-
- var w = window, d = document, docEl = d.documentElement;
-
- // Check if browser supports focus without automatic scrolling (preventScroll)
- var supportsPreventScroll = false;
- try {
- var el = d.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) { }
-
- // Regex to extract the value following the scroll-behavior property name
- var extractValue = /scroll-behavior:[\s]*([^;"`'\s]+)/;
-
- /**
- * Returns true if scroll-behavior: smooth is set and not overwritten
- * by a higher-specifity declaration, else returns false
- */
- function shouldSmoothscroll() {
- // Values to check for set scroll-behavior in order of priority/specificity
- var valuesToCheck = [
- // Priority 1: behavior specified as inline property
- // Allows toggling smoothscroll from JS (docEl.style.scrollBehavior = ...)
- docEl.style.scrollBehavior,
- // Priority 2: behavior specified inline in style attribute
- (extractValue.exec(docEl.getAttribute('style')) || [])[1],
- // Priority 3: behavior specified in fontFamily
- // Behaves like regular CSS, e.g. allows using media queries
- (extractValue.exec(getComputedStyle(docEl).fontFamily) || [])[1]
- ];
- // Loop over values in specified order, return once a valid value is found
- for (var i = 0; i < valuesToCheck.length; i++) {
- var specifiedBehavior = getScrollBehavior(valuesToCheck[i]);
- if (specifiedBehavior !== null) return specifiedBehavior;
+(function(global, factory) {
+ var SmoothscrollAnchorPolyfill = factory();
+ if (typeof exports === 'object' && typeof module !== 'undefined') {
+ module.exports = SmoothscrollAnchorPolyfill;
+ } else {
+ global.SmoothscrollAnchorPolyfill = SmoothscrollAnchorPolyfill;
}
- // No value found? Return false, no set value = no smoothscroll :(
- return false;
- }
-
- /**
- * If a valid CSS property value for scroll-behavior is passed, returns
- * whether it specifies smooth scroll behavior or not, else returns null
- * @param {any} value The value to check
- * @returns {?boolean} The specified scroll behavior or null
- */
- function getScrollBehavior(value) {
- var status = null;
- if (/^smooth$/.test(value)) status = true;
- if (/^(initial|inherit|auto|unset)$/.test(value)) status = false;
- return status;
- }
-
- /**
- * Get the target element of an event
- * @param {event} evt
- */
- function getEventTarget(evt) {
- evt = evt || w.event;
- return evt.target || evt.srcElement;
- }
-
- /**
- * Check if an element is an anchor pointing to a target on the current page
- * @param {HTMLElement} el
- */
- function isAnchorToLocalElement(el) {
- return (
- // Is an anchor with a fragment in the url
- el.tagName && el.tagName.toLowerCase() === 'a' && /#/.test(el.href) &&
- // Target is on current page
- el.hostname === location.hostname && el.pathname === location.pathname
- );
- }
-
- /**
- * 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({ preventScroll: true });
- if (d.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({ preventScroll: true });
+
+ // In test environment: abort, else run polyfill immediately
+ if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') return;
+ else SmoothscrollAnchorPolyfill.polyfill();
+})(this, function() {
+
+ /* */
+
+ // Abort if outside browser (window is undefined in Node etc.)
+ var isBrowser = typeof window !== 'undefined';
+ if (!isBrowser) return;
+
+ var w = window, d = document, docEl = d.documentElement;
+
+ // Check if browser supports focus without automatic scrolling (preventScroll)
+ var supportsPreventScroll = false;
+ try {
+ var el = d.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) { }
+
+ // Regex to extract the value following the scroll-behavior property name
+ var extractValue = /scroll-behavior:[\s]*([^;"`'\s]+)/;
+
+ /**
+ * Returns true if scroll-behavior: smooth is set and not overwritten
+ * by a higher-specifity declaration, else returns false
+ */
+ function shouldSmoothscroll() {
+ // Values to check for set scroll-behavior in order of priority/specificity
+ var valuesToCheck = [
+ // Priority 1: behavior specified as inline property
+ // Allows toggling smoothscroll from JS (docEl.style.scrollBehavior = ...)
+ docEl.style.scrollBehavior,
+ // Priority 2: behavior specified inline in style attribute
+ (extractValue.exec(docEl.getAttribute('style')) || [])[1],
+ // Priority 3: behavior specified in fontFamily
+ // Behaves like regular CSS, e.g. allows using media queries
+ (extractValue.exec(getComputedStyle(docEl).fontFamily) || [])[1]
+ ];
+ // Loop over values in specified order, return once a valid value is found
+ for (var i = 0; i < valuesToCheck.length; i++) {
+ var specifiedBehavior = getScrollBehavior(valuesToCheck[i]);
+ if (specifiedBehavior !== null) return specifiedBehavior;
+ }
+ // No value found? Return false, no set value = no smoothscroll :(
+ return false;
+ }
+
+ /**
+ * If a valid CSS property value for scroll-behavior is passed, returns
+ * whether it specifies smooth scroll behavior or not, else returns null
+ * @param {any} value The value to check
+ * @returns {?boolean} The specified scroll behavior or null
+ */
+ function getScrollBehavior(value) {
+ var status = null;
+ if (/^smooth$/.test(value)) status = true;
+ if (/^(initial|inherit|auto|unset)$/.test(value)) status = false;
+ return status;
+ }
+
+ /**
+ * Get the target element of an event
+ * @param {event} evt
+ */
+ function getEventTarget(evt) {
+ evt = evt || w.event;
+ return evt.target || evt.srcElement;
+ }
+
+ /**
+ * Check if an element is an anchor pointing to a target on the current page
+ * @param {HTMLElement} el
+ */
+ function isAnchorToLocalElement(el) {
+ return (
+ // Is an anchor with a fragment in the url
+ el.tagName && el.tagName.toLowerCase() === 'a' && /#/.test(el.href) &&
+ // Target is on current page
+ el.hostname === location.hostname && el.pathname === location.pathname
+ );
+ }
+
+ /**
+ * 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({ preventScroll: true });
+ if (d.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({ preventScroll: true });
+ }
+ }
+
+ /**
+ * Returns the element whose id matches the hash or
+ * document.body if the hash is "#top" or "" (empty string)
+ * @param {string} hash
+ */
+ function getScrollTarget(hash) {
+ if (typeof hash !== 'string') return null;
+
+ // 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 ? d.getElementById(hash.slice(1)) : d.body;
+ if (hash === '#top' && !target) target = d.body;
+ return target;
}
- }
-
- /**
- * Returns the element whose id matches the hash or
- * document.body if the hash is "#top" or "" (empty string)
- * @param {string} hash
- */
- function getScrollTarget(hash) {
- if (typeof hash !== 'string') return null;
-
- // 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 ? d.getElementById(hash.slice(1)) : d.body;
- if (hash === '#top' && !target) target = d.body;
- return target;
- }
-
- /**
- * 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
- * @param {Function} validate The validation function
- * @returns {HTMLElement|boolean}
- */
- function findInParents(element, validate) {
- if (validate(element)) return element;
- if (element.parentNode) return findInParents(element.parentNode, validate);
- return false;
- }
-
- // Stores the setTimeout id of pending focus changes, allows aborting them
- var pendingFocusChange;
-
- /**
- * Scrolls to a given element or to the top if the given element
- * is document.body, then focuses the element
- * @param {HTMLElement} target
- */
- function triggerSmoothscroll(target) {
- // Clear potential pending focus change triggered by a previous scroll
- if (!supportsPreventScroll) clearTimeout(pendingFocusChange);
-
- // Use JS 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 === d.body;
- if (scrollTop) w.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(target);
- else pendingFocusChange = setTimeout(focusElement.bind(null, target), 450);
- }
-
- /**
- * 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) {
- // Abort if shift/ctrl-click or not primary click (button !== 0)
- if (evt.metaKey || evt.ctrlKey || evt.shiftKey || evt.button !== 0) return;
- // scroll-behavior not set to smooth? Bail out, let browser handle it
- if (!shouldSmoothscroll()) return;
-
- // Check the DOM from the click target upwards if a local anchor was clicked
- var anchor = findInParents(getEventTarget(evt), isAnchorToLocalElement);
- if (!anchor) return;
-
- // Find the element targeted by the hash
- var hash = anchor.hash;
- var target = getScrollTarget(hash);
-
- if (target) {
- // Prevent default browser behavior to avoid a jump to the anchor target
- evt.preventDefault();
-
- // Trigger the smooth scroll
- triggerSmoothscroll(target);
-
- // Append the hash to the URL
- if (history.pushState) history.pushState(null, d.title, (hash || '#'));
+
+ /**
+ * 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
+ * @param {Function} validate The validation function
+ * @returns {HTMLElement|boolean}
+ */
+ function findInParents(element, validate) {
+ if (validate(element)) return element;
+ if (element.parentNode) return findInParents(element.parentNode, validate);
+ return false;
}
- }
-
- // To enable smooth scrolling on hashchange, we need to immediately restore
- // the scroll pos after a hashchange changed it, so we track it constantly.
- // Some browsers don't trigger a scroll event before the hashchange,
- // so to undo, the position from last scroll is the one we need to go back to.
- // In others (e.g. IE) the scroll listener is triggered again before the
- // hashchange occurs and the last reported position is already the new one
- // updated by the hashchange – we need the second last to undo there.
- // Because of this we don't track just the last, but the last two positions.
- var lastTwoScrollPos = [];
-
- /**
- * Tries to undo the automatic, instant scroll caused by a hashchange
- * and instead scrolls smoothly to the new hash target
- */
- function handleHashChange() {
- // scroll-behavior not set to smooth or body nor parsed yet? Abort
- if (!shouldSmoothscroll() || !d.body) return;
-
- var target = getScrollTarget(location.hash);
- if (!target) return;
-
- // If the position last reported by the scroll listener is the same as the
- // current one caused by a hashchange, go back to second last – else last
- var currentPos = getScrollTop();
- var top = lastTwoScrollPos[lastTwoScrollPos[1] === currentPos ? 0 : 1];
-
- // Undo the scroll caused by the hashchange...
- w.scroll({ top: top, behavior: 'instant' });
- // ...and instead smoothscroll to the target
- triggerSmoothscroll(target);
- }
-
- /**
- * Returns the scroll offset towards the top
- */
- function getScrollTop() {
- return docEl.scrollTop || d.body.scrollTop;
- }
-
- /**
- * Update the last two scroll positions
- */
- function trackScrollPositions() {
- if (!d.body) return; // Body not parsed yet? Abort
- lastTwoScrollPos[0] = lastTwoScrollPos[1];
- lastTwoScrollPos[1] = getScrollTop();
- }
-
- return {
+ // Stores the setTimeout id of pending focus changes, allows aborting them
+ var pendingFocusChange;
+
/**
- * Starts the polyfill by attaching the neccessary EventListeners
+ * Scrolls to a given element or to the top if the given element
+ * is document.body, then focuses the element
+ * @param {HTMLElement} target
*/
- polyfill: function () {
- // Abort if smoothscroll is natively supported and force flag is not set
- var forcePolyfill = w.__forceSmoothscrollAnchorPolyfill__ === true;
- if (!forcePolyfill && 'scrollBehavior' in docEl.style) return;
-
- d.addEventListener('click', handleClick, false);
- d.addEventListener('scroll', trackScrollPositions);
- w.addEventListener('hashchange', handleHashChange);
- },
+ function triggerSmoothscroll(target) {
+ // Clear potential pending focus change triggered by a previous scroll
+ if (!supportsPreventScroll) clearTimeout(pendingFocusChange);
+
+ // Use JS 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 === d.body;
+ if (scrollTop) w.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(target);
+ else pendingFocusChange = setTimeout(focusElement.bind(null, target), 450);
+ }
+
/**
- * Stops the polyfill by removing all EventListeners
+ * 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
*/
- destroy: function () {
- d.removeEventListener('click', handleClick, false);
- d.removeEventListener('scroll', trackScrollPositions);
- w.removeEventListener('hashchange', handleHashChange);
+ 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;
+ // scroll-behavior not set to smooth? Bail out, let browser handle it
+ if (!shouldSmoothscroll()) return;
+
+ // Check the DOM from the click target upwards if a local anchor was clicked
+ var anchor = findInParents(getEventTarget(evt), isAnchorToLocalElement);
+ if (!anchor) return;
+
+ // Find the element targeted by the hash
+ var hash = anchor.hash;
+ var target = getScrollTarget(hash);
+
+ if (target) {
+ // Prevent default browser behavior to avoid a jump to the anchor target
+ evt.preventDefault();
+
+ // Trigger the smooth scroll
+ triggerSmoothscroll(target);
+
+ // Append the hash to the URL
+ if (history.pushState) history.pushState(null, d.title, (hash || '#'));
+ }
+
}
- };
+
+ // To enable smooth scrolling on hashchange, we need to immediately restore
+ // the scroll pos after a hashchange changed it, so we track it constantly.
+ // Some browsers don't trigger a scroll event before the hashchange,
+ // so to undo, the position from last scroll is the one we need to go back to.
+ // In others (e.g. IE) the scroll listener is triggered again before the
+ // hashchange occurs and the last reported position is already the new one
+ // updated by the hashchange – we need the second last to undo there.
+ // Because of this we don't track just the last, but the last two positions.
+ var lastTwoScrollPos = [];
+
+ /**
+ * Tries to undo the automatic, instant scroll caused by a hashchange
+ * and instead scrolls smoothly to the new hash target
+ */
+ function handleHashChange() {
+ // scroll-behavior not set to smooth or body nor parsed yet? Abort
+ if (!shouldSmoothscroll() || !d.body) return;
+
+ var target = getScrollTarget(location.hash);
+ if (!target) return;
+
+ // If the position last reported by the scroll listener is the same as the
+ // current one caused by a hashchange, go back to second last – else last
+ var currentPos = getScrollTop();
+ var top = lastTwoScrollPos[lastTwoScrollPos[1] === currentPos ? 0 : 1];
+
+ // Undo the scroll caused by the hashchange...
+ w.scroll({ top: top, behavior: 'instant' });
+ // ...and instead smoothscroll to the target
+ triggerSmoothscroll(target);
+ }
+
+ /**
+ * Returns the scroll offset towards the top
+ */
+ function getScrollTop() {
+ return docEl.scrollTop || d.body.scrollTop;
+ }
+
+ /**
+ * Update the last two scroll positions
+ */
+ function trackScrollPositions() {
+ if (!d.body) return; // Body not parsed yet? Abort
+ lastTwoScrollPos[0] = lastTwoScrollPos[1];
+ lastTwoScrollPos[1] = getScrollTop();
+ }
+
+ return {
+ /**
+ * Starts the polyfill by attaching the neccessary EventListeners
+ *
+ * Aborts, if ('scrollBehavior' in documentElement.style) and the force flag
+ * isn't set on the options parameter Object or globally on window
+ * @param {{force: boolean}} opts Enable polyfill despite native support
+ */
+ polyfill: function(opts) {
+ opts = opts || {};
+ // Abort if smoothscroll is natively supported and force flag is not set
+ var forcePolyfill = opts.force || w.__forceSmoothscrollAnchorPolyfill__;
+ if (!forcePolyfill && 'scrollBehavior' in docEl.style) return;
+
+ d.addEventListener('click', handleClick, false);
+ d.addEventListener('scroll', trackScrollPositions);
+ w.addEventListener('hashchange', handleHashChange);
+ },
+ /**
+ * Stops the polyfill by removing all EventListeners
+ */
+ destroy: function() {
+ d.removeEventListener('click', handleClick, false);
+ d.removeEventListener('scroll', trackScrollPositions);
+ w.removeEventListener('hashchange', handleHashChange);
+ }
+ };
});
diff --git a/test.js b/test.js
index 0a6cb7b..2b19f90 100755
--- a/test.js
+++ b/test.js
@@ -40,7 +40,7 @@ describe('General', () => {
})
- it('Runs even in browsers with native support if force flag is set', () => {
+ it('Runs even with native support if force flag is set on window', () => {
const anchor = insertElement('a', { href: '#' })
// Mock native support
@@ -51,6 +51,20 @@ describe('General', () => {
const spy = jest.spyOn(window, 'scroll')
polyfill()
+ anchor.click()
+ expect(spy).toHaveBeenCalled()
+ })
+
+ it('Runs even with native support if force flag is passed as arg', () => {
+ const anchor = insertElement('a', { href: '#' })
+
+ // Mock native support
+ document.documentElement.style.scrollBehavior = 'smooth'
+
+ const spy = jest.spyOn(window, 'scroll')
+ // Pass force flag in options object
+ polyfill({ force: true })
+
anchor.click()
expect(spy).toHaveBeenCalled()
})
From 0f4248bd4cc6b540547efa3a6a8f31f2c5fef76e Mon Sep 17 00:00:00 2001
From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com>
Date: Sat, 8 Dec 2018 02:09:06 +0100
Subject: [PATCH 3/8] docs: rewrite docs for v1.0.0, move to docs/
fix #9, fix #10
---
.gitattributes | 2 -
CHANGELOG.md | 16 +-
favicon.ico => docs/favicon.ico | Bin
index.css => docs/index.css | 46 +++++-
index.html => docs/index.html | 267 ++++++++++++++++++++++++--------
5 files changed, 257 insertions(+), 74 deletions(-)
delete mode 100755 .gitattributes
rename favicon.ico => docs/favicon.ico (100%)
rename index.css => docs/index.css (82%)
rename index.html => docs/index.html (58%)
diff --git a/.gitattributes b/.gitattributes
deleted file mode 100755
index b383ed1..0000000
--- a/.gitattributes
+++ /dev/null
@@ -1,2 +0,0 @@
-index.html linguist-documentation
-index.css linguist-documentation
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e7e7e44..9a3889a 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,13 +4,23 @@ 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).
+## [1.0.0] - 2018-12-07
+### Added
+ - The methods 'destroy' and 'polyfill' are now exported (CommonJS) or exposed on window.SmoothscrollAnchorPolyfill. The polyfill still runs automatically on load so embedding it is enough, but now you can destroy it if you want (EventListeners are removed) and start it again, later.
+ - In addition to `window.__forceSmoothscrollAnchorPolyfill__`, you can now pass `{ force: true }` when invoking `polyfill()` to force-enable the package even in browsers with native support
+### Changed
+ - Updated the documentation website to reflect the new API
+ - Moved the documentation in a separate docs/ folder to clean up the repo
+ - Small fixes for formatting and typos in the README
+
## [1.0.0-beta] - 2018-12-05
### Changed
- The README.md file has been updated to match the API of v1.0.0
- - Fixed 'window is not defined' error in Node environments, important for usage with SSR
- BREAKING: Polyfill now only handles smooth scroll if scroll-behavior is set to 'smooth' via <html style="">, documentElement.style.scrollBehavior or a custom font-family (more information will be added to the documentation)
### Added
- Tests for smooth scrolling when clicking anchors have been implemented
+### Fixed
+ - Fixed 'window is not defined' error in Node environments, important for usage with SSR
## [0.12.0] - 2018-11-15
### Added
@@ -20,12 +30,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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
diff --git a/favicon.ico b/docs/favicon.ico
similarity index 100%
rename from favicon.ico
rename to docs/favicon.ico
diff --git a/index.css b/docs/index.css
similarity index 82%
rename from index.css
rename to docs/index.css
index 248409d..12834ca 100755
--- a/index.css
+++ b/docs/index.css
@@ -1,9 +1,3 @@
-html {
- scroll-behavior: smooth;
- /* Additionally specified in custom font-family so polyfill can parse it */
- font-family: 'scroll-behavior:smooth';
-}
-
body {
margin: 0;
font-family: 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif;
@@ -66,6 +60,42 @@ section, .fullscreen {
padding-top: 11rem;
}
+.smooth-scroll {
+ scroll-behavior: smooth;
+ /* Additionally specified in custom font-family so polyfill can parse it */
+ font-family: 'scroll-behavior:smooth';
+}
+
+.fullscreen h1 {
+ margin-top: 7rem;
+}
+
+.scroll-btn {
+ margin-top: 7rem;
+}
+
+@media screen and (min-height: 660px) {
+ .fullscreen h1 {
+ margin-top: 12rem;
+ }
+ .scroll-btn {
+ margin-top: 12rem;
+ }
+}
+
+@media screen and (min-height: 900px) {
+ .fullscreen h1 {
+ margin-top: 24rem;
+ }
+ .scroll-btn {
+ margin-top: 24rem;
+ }
+}
+
+html:not(.smooth-scroll) .scroll-btn {
+ font-weight: lighter;
+}
+
.max-width {
max-width: 100%;
}
@@ -192,4 +222,8 @@ h1[id]:hover, h2[id]:hover, h3[id]:hover, h4[id]:hover, h1[data-to]:hover, h2[da
h1[id]:hover::before, h2[id]:hover::before, h3[id]:hover::before, h4[id]:hover::before, h1[data-to]:hover::before, h2[data-to]:hover::before, h3[data-to]:hover::before, h4[data-to]:hover::before {
opacity: 0.5;
+}
+
+.allow-overflow {
+ overflow: auto;
}
\ No newline at end of file
diff --git a/index.html b/docs/index.html
similarity index 58%
rename from index.html
rename to docs/index.html
index b884fb9..5a23173 100755
--- a/index.html
+++ b/docs/index.html
@@ -1,5 +1,5 @@
-
+
@@ -13,10 +13,10 @@
// Element.classList polyfill
; (function () { function a(e) { this.element = e } var b = function (e) { return e.replace(/^\s+|\s+$/g, '') }, c = function (e) { return new RegExp('(^|\\s+)' + e + '(\\s+|$)') }, d = function (e, f, g) { for (var h = 0; h < e.length; h++)f.call(g, e[h]) }; a.prototype = { add: function add() { d(arguments, function (e) { this.contains(e) || (this.element.className = b(this.element.className + ' ' + e)) }, this) }, remove: function remove() { d(arguments, function (e) { this.element.className = b(this.element.className.replace(c(e), ' ')) }, this) }, toggle: function toggle(e) { return this.contains(e) ? (this.remove(e), !1) : (this.add(e), !0) }, contains: function contains(e) { return c(e).test(this.element.className) }, item: function item(e) { return this.element.className.split(/\s+/)[e] || null }, replace: function replace(e, f) { this.remove(e), this.add(f) } }, 'classList' in Element.prototype || Object.defineProperty(Element.prototype, 'classList', { get: function get() { return new a(this) } }), window.DOMTokenList && !DOMTokenList.prototype.replace && (DOMTokenList.prototype.replace = a.prototype.replace) })();
-
-
+
+
-
+
@@ -49,18 +49,21 @@
smoothscroll-anchor-polyfill
⚓ Apply smooth scroll to anchor links to
replicate CSS scroll-behavior
The Scroll Behavior specification allows for native smooth
+
The Scroll
+ Behavior specification allows for
+ native smooth
scrolling
in browsers
– both by using JavaScript scroll APIs like window.scrollTo
and
- element.scrollIntoView or by simply setting the
+ Element.scrollIntoView or by simply setting the
property
scroll-behavior to smooth in CSS, which
will then make any scrolling smooth by default. This includes
@@ -87,57 +90,99 @@
Start
Usage
-
Requirements
-
Since this script uses the JavaScript scroll APIs and relies on
+
⚠ Since this script uses the JavaScript scroll APIs and relies on
their smooth scroll functionality to operate, you'll need a
polyfill for the Scroll Behavior spec in order for this script to
make a difference. smoothscroll-polyfill
is used as example throughout this site, but you may just as well use
another polyfill – or write your own implementation.
-
⚠ Furthermore, this script assumes that scroll-behavior
- is set globally to smooth in your CSS. If you don't do that,
- scroll behavior will differ between browsers with and without
- native support for Scroll Behavior. Set it as follows:
Because browsers don't parse CSS properties they don't recognize.
+ For this reason, reading the scroll-behavior property
+ from your regular stylesheets is unfortunately not possible (without
+ a performance hit). Instead, specify scroll-behavior
+ using one of these options:
+
Option 1: Using the inline style attribute
+
Simply define scroll-behaviour as an inline style on
+ the html element:
This way, the polyfill can read the
+ property using getAttribute('style') even if the browser
+ doesn't parse it.
+
+
Option 2: Using font-family as
+ workaround
+
Alternatively, you can specify the property as the name of a custom
+ font family:
+
<style>
+ html {
+ /* Normal CSS properties for browsers with native support */
+ scroll-behavior: smooth;
+
+ /* Additionally defined as the name of a font, so the polyfill can read it */
+ font-family: "scroll-behavior: smooth", sans-serif;
+ }
+<style>
+ Your actual fonts will still work the way they should – plus, you can
+ simply declare actual fonts on body { } and use font
+ styles on html { } exclusively for the means of this
+ polyfill, which is prefered. Unlike inline styles, this allows you to
+ use normal CSS features like media queries or classes. The following
+ only enables smooth scroll on desktop devices, for example:
+
💡 Redeclaring your scroll-behavior properties as font
+ names can be automated using a PostCSS
+ plugin, so you can write regular CSS and don't have to
+ bother with font-families. It just works™
-
Basic usage
+
2. Installing the polyfill
+
Option 1: Using <script>
Simply drop in <script> tags linking to the
- polyfills and and you're good to go.
+ polyfills and you're good to go.
-
<!-- A polyfill to enable smoothscroll for the JavaScript APIs -->
-<script src="https://unpkg.com/smoothscroll-polyfill"></script>
+
<!-- Any polyfill to enable smoothscroll for the JavaScript APIs -->
+<script src="https://unpkg.com/smoothscroll-polyfill/dist/smoothscroll.min.js"></script>
<!-- This package, to apply the smoothscroll to anchor links -->
<script src="https://unpkg.com/smoothscroll-anchor-polyfill"></script>
-
With npm
-
Alternatively, if you're using npm, you can
- install using
- npm install smoothscroll-anchor-polyfill and then use
- the polyfill by
- requiring/importing it in your JS.
-
-
// Import any polyfill to enable smoothscroll for JS APIs
+
Option 2: With npm
+
Alternatively, if you're using npm, you can
+ install using npm install smoothscroll-anchor-polyfill
+ and then use the polyfill by requiring/importing it in your JS.
+
+
// Import any polyfill to enable smoothscroll for JS APIs
import smoothscrollPolyfill from 'smoothscroll-polyfill';
+
// Import this package to apply the smoothscroll to anchor links
-import 'smoothscroll-anchor-polyfill';
+import smoothscrollAnchorPolyfill from 'smoothscroll-anchor-polyfill';
-// Enable the main smoothscroll polyfill (might not be neccessary if you use an alternative one)
+// (Unlike this package, smoothscroll-polyfill needs to be actively invoked: )
smoothscrollPolyfill.polyfill();
-
Advanced (with Code Splitting)
-
If you're using a build system with support for code splitting like
- Parcel or Webpack,
- you can use dynamic imports to load the polyfills – this way,
- browsers won't even download the polyfill code if they already have
- support for the Scroll Behavior spec natively:
-
-
// Only continue if polyfills are actually needed
+
Advanced installation (with Code Splitting)
+
If you're using a build system with support for code splitting
+ like
+ Parcel or Webpack,
+ you can use dynamic imports to load the polyfills – this way,
+ browsers won't even download the polyfill code if they already have
+ support for the Scroll Behavior spec natively:
+
+
// Only continue if polyfills are actually needed
if (!('scrollBehavior') in document.documentElement.style) {
// Wait until the Polyfills are loaded
@@ -147,7 +192,7 @@
Advanced (with Code Splitting)
])
// then use the modules however you want
.then(([smoothscrollPolyfill, smoothscrollAnchorPolyfill]) => {
- // e.g. enable the main smoothscroll polyfill (might not be neccessary if you use an alternative one)
+ // (Unlike this package, smoothscroll-polyfill needs to be actively invoked: )
smoothscroll.polyfill();
});
}
@@ -156,15 +201,31 @@
Advanced (with Code Splitting)
Docs
-
There's not much more to it than loading this polyfill – it will
+
For 90% of use cases, there should not be much more to it than
+ loading this polyfill – it will
execute immediately no matter if loaded through a script tag or in a
CommonJS environment. If the Scroll Behavior spec is supported
natively, the code won't do anything.
-
⚠ Remember to also set the scroll-behavior property
- globally (on html)
- in your CSS, otherwise your site will behave differently in browsers
- with and without support for the Scroll Behavior spec! ⚠
-
Use the Polyfill even if there is native
+
Changing the scroll behavior
+
The prefered way to dynamically adjust the scroll behavior is the font-family workaround. This way you can
+ simply toggle a CSS class on <html> depending on the
+ behavior you want. Valid property values are smooth for
+ enabling smooth scroll and auto, initial,
+ inherit or unset for enabling instant,
+ jumping scroll.
+
+
You can also assign these values directly to document.documentElement.style.scrollBehavior,
+ it will have precedence over both the inline style attribute and
+ the
+ property set using the font-family workaround.
+
+
+ ⚠ Assigning to .scrollBehavior is not recommened
+ however as this property is used for feature detection. Assigning to
+ it before a polyfill was loaded will break this one and most other
+ polyfills related to smooth scrolling. ⚠
+
Using the polyfill even if there is native
support
@@ -176,21 +237,104 @@
Use the Polyfill even if there is native
native smooth scroll. Not recommended.
-
Polyfill anchors dynamically inserted later
+
Methods: destroy and polyfill
+
+
This package exports two methods, destroy and polyfill.
+ If loaded through a script tag, these methods are exposed on
+ window.smoothscrollAnchorPolyfill.
+
+
+
+ destroy():
+
+
+
The polyfill runs automatically when it's loaded, setting up
+ the EventListeners it needs. This method disables the
+ polyfill and removes all EventListeners.
+
+
+
+
+ polyfill({ force: boolean }):
+
+
+
If you used destroy() to disable the polyfill,
+ you can re-enable it with this method. It takes an (optional)
+ Object as argument, the property force behaves
+ the same way as the global force flag
+ on window.
+
+
+
⚠ Note that both the global force flag
+ and the
+ check for native
+ support ('scrollBehavior' in document.documentElement.style)
+ will be re-evaluated when polyfill() runs. If you
+ assigned to .scrollBehavior in the
+ meantime, this check will evaluate to true and the
+ polyfill won't enable itself.
+ Use the force flag or run delete document.documentElement.style.scrollBehavior;
+ if you encounter this problem.
+
Limitations
+
scroll-behavior is not
+ detected in regular
+ stylesheets
+
As already explained in the Usage section,
+ scroll-behavior
+ can not be set in regular CSS, as accessing the property there
+ from JavaScript is not possible without a performance hit. This
+ is caused by browsers not parsing a CSS property if it isn't
+ recognized as valid. If you need the flexiblity of CSS, consider
+ the font-family
+ workaround.
+
scroll-behavior is only supported
+ as global setting
+
In browsers with native support, you can define scroll-behavior
+ at multiple points in your document, e.g. auto on
+ the document itself, but smooth on a slideshow
+ container that has separate scrolling. This polyfill does not
+ allow for that, either all anchors on the page scroll smoothly by
+ setting scroll-behavior
+ at document level, or none.
+
Scrolling triggered by hashchange
+ is not smooth in
+ Chrome
+
This actually doesn't have anything to do with this polyfill –
+ it's
+ a limitation of Blink's native implementation (so it affects
+ other Blink-based browsers like Opera, too). While 'normal'
+ scrolling is smooth, if you click a couple of links and then
+ navigate back and forth using the browser's forwards/backwards
+ buttons (which triggers a hashchange
+ everytime), it jumps from anchor to anchor instead of scrolling
+ smoothly. If this is important to you, you can fix it by
+ detecting the Blink engine and force-enabling this polyfill. Load
+ browsengine.js,
+ then do (before the polyfill runs):
+
if (window.webpage.engine.blink) {
+ window.__forceSmoothscrollAnchorPolyfill__ = true;
+}
+
FAQ
+
Will this break Server Side Rendering?
+
No.
+
Polyfill anchors dynamically inserted
+ later
The polyfill uses Event
Delegation to detect clicks,
so even if an anchor is added to the page after the polyfill was
loaded, everything should work.
-
Disable the polyfill
-
There currently is no way to disable the polyfill once it was
- loaded. I considered adding this functionality but couldn't think of
- a good use case – if you have one, let me know!
-
💡 Note that even if global scroll-behavior is set and
- this polyfill is loaded, you can still perform instant, jumping
- navigation by using one of the JavaScript scroll APIs and specifying
- { behavior: 'instant' } in the options object.
-
Will this break Server Side Rendering?
-
No.
+
Does this support prefers-reduced-motion?
+
prefers-reduced-motion is a relatively new CSS media
+ query that hints at whether a client prefers less motion, which can
+ be important for people with certain illnesses. But Safari is the
+ only browser that has implemented it yet, and it doesn't support the
+ Scroll Behavior spec, so there is no reference for the interplay of
+ prefers-reduced-motion and Scroll Behavior yet. For this
+ reason, it is not implemented (yet) in this polyfill.
+ However, it is relatively safe to assume that prefers-reduced-motion
+ will disable scroll-behavior: smooth so this can
+ absolutely be discussed – please file an issue on GitHub if it
+ affects your project.
@@ -333,7 +477,8 @@
Legal
Note: the legalities discussed here concern this website
itself, not the software package "smoothscroll-anchor-polyfill",
and are required for compliance with German & European law. "smoothscroll-anchor-polyfill"
- itself is licensed under MIT license, for more information
+ itself is licensed under a plain MIT license, for more
+ information
check out the respective GitHub
repository.
The Scroll
Behavior specification allows for
native smooth
@@ -88,21 +88,21 @@
Start
-
-
Usage
+
+
Usage
⚠ Since this script uses the JavaScript scroll APIs and relies on
their smooth scroll functionality to operate, you'll need a
polyfill for the Scroll Behavior spec in order for this script to
make a difference. smoothscroll-polyfill
is used as example throughout this site, but you may just as well use
another polyfill – or write your own implementation.
-
1. Setting scroll-behavior in CSS
+
1. Setting scroll-behavior in CSS
Because browsers don't parse CSS properties they don't recognize.
For this reason, reading the scroll-behavior property
from your regular stylesheets is unfortunately not possible (without
a performance hit). Instead, specify scroll-behavior
using one of these options:
-
Option 1: Using the inline style attribute
+
Option 1: Using the inline style attribute
Simply define scroll-behaviour as an inline style on
the html element:
property using getAttribute('style') even if the browser
doesn't parse it.
-
Option 2: Using font-family as
- workaround
+
Option 2: Using font-family as
+ workaround
Alternatively, you can specify the property as the name of a custom
font family:
<style>
@@ -149,8 +149,8 @@
Option 2: Using font-family as
plugin, so you can write regular CSS and don't have to
bother with font-families. It just works™
-
2. Installing the polyfill
-
Option 1: Using <script>
+
2. Installing the polyfill
+
Option 1: Using <script>
Simply drop in <script> tags linking to the
polyfills and you're good to go.
@@ -160,7 +160,7 @@
Option 1: Using <script>
<!-- This package, to apply the smoothscroll to anchor links -->
<script src="https://unpkg.com/smoothscroll-anchor-polyfill"></script>
-
Option 2: With npm
+
Option 2: With npm
Alternatively, if you're using npm, you can
install using npm install smoothscroll-anchor-polyfill
and then use the polyfill by requiring/importing it in your JS.
@@ -174,7 +174,8 @@
Option 2: With npm
// (Unlike this package, smoothscroll-polyfill needs to be actively invoked: )
smoothscrollPolyfill.polyfill();
-
Advanced installation (with Code Splitting)
+
Advanced installation (with Code
+ Splitting)
If you're using a build system with support for code splitting
like
Parcel or Webpack,
@@ -199,14 +200,14 @@
Advanced installation (with Code Splitting)
-
-
Docs
+
+
Docs
For 90% of use cases, there should not be much more to it than
loading this polyfill – it will
execute immediately no matter if loaded through a script tag or in a
CommonJS environment. If the Scroll Behavior spec is supported
natively, the code won't do anything.
-
Changing the scroll behavior
+
Changing the scroll behavior
The prefered way to dynamically adjust the scroll behavior is the font-family workaround. This way you can
simply toggle a CSS class on <html> depending on the
@@ -225,8 +226,8 @@
Changing the scroll behavior
however as this property is used for feature detection. Assigning to
it before a polyfill was loaded will break this one and most other
polyfills related to smooth scrolling. ⚠
-
Using the polyfill even if there is native
- support
+
Using the polyfill even if there is native
+ support
Using the polyfill even if there is native
native smooth scroll. Not recommended.
-
Methods: destroy and polyfill
+
Methods: destroy and polyfill
This package exports two methods, destroy and polyfill.
If loaded through a script tag, these methods are exposed on
@@ -275,10 +276,11 @@
Methods: destroy and polyfill
polyfill won't enable itself.
Use the force flag or run delete document.documentElement.style.scrollBehavior;
if you encounter this problem.
-
Limitations
-
scroll-behavior is not
- detected in regular
- stylesheets
+
Limitations
+
scroll-behavior is
+ not
+ detected in regular
+ stylesheets
As already explained in the Usage section,
scroll-behavior
can not be set in regular CSS, as accessing the property there
@@ -287,8 +289,9 @@
scroll-behavior is not
recognized as valid. If you need the flexiblity of CSS, consider
the font-family
workaround.
-
scroll-behavior is only supported
- as global setting
+
scroll-behavior is only
+ supported
+ as global setting
In browsers with native support, you can define scroll-behavior
at multiple points in your document, e.g. auto on
the document itself, but smooth on a slideshow
@@ -296,12 +299,10 @@
scroll-behavior is only supported
allow for that, either all anchors on the page scroll smoothly by
setting scroll-behavior
at document level, or none.
-
Scrolling triggered by hashchange
- is not smooth in
- Chrome
+
Scrolling triggered by hashchange
+ is not smooth in Chrome
This actually doesn't have anything to do with this polyfill –
- it's
- a limitation of Blink's native implementation (so it affects
+ it's a limitation of Blink's native implementation (so it affects
other Blink-based browsers like Opera, too). While 'normal'
scrolling is smooth, if you click a couple of links and then
navigate back and forth using the browser's forwards/backwards
@@ -314,16 +315,16 @@
Scrolling triggered by hashchange
if (window.webpage.engine.blink) {
window.__forceSmoothscrollAnchorPolyfill__ = true;
}
-
FAQ
-
Will this break Server Side Rendering?
+
FAQ
+
Will this break Server Side Rendering?
No.
-
Polyfill anchors dynamically inserted
- later
+
Polyfill anchors dynamically inserted
+ later
The polyfill uses Event
Delegation to detect clicks,
so even if an anchor is added to the page after the polyfill was
loaded, everything should work.
-
Does this support prefers-reduced-motion?
+
Does this support prefers-reduced-motion?
prefers-reduced-motion is a relatively new CSS media
query that hints at whether a client prefers less motion, which can
be important for people with certain illnesses. But Safari is the
@@ -338,11 +339,11 @@
Does this support prefers-reduced-motion?
-
-
+
+
-
Rechtliches
+
Rechtliches
Hinweis: die folgenden rechtlichen Hinweise betreffen
diese Website als solche entsprechend deutschem und europäischem
Recht, nicht das vorgestellte Software-Paket "smoothscroll-anchor-polyfill".
@@ -473,7 +474,7 @@
Datenschutz
veranlassen.
-
Legal
+
Legal
Note: the legalities discussed here concern this website
itself, not the software package "smoothscroll-anchor-polyfill",
and are required for compliance with German & European law. "smoothscroll-anchor-polyfill"
@@ -563,8 +564,8 @@
Privacy
From 14199adf65a85faa8fdce4258c2fb9f6d7316683 Mon Sep 17 00:00:00 2001
From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com>
Date: Sat, 8 Dec 2018 02:55:01 +0100
Subject: [PATCH 5/8] docs: update references to docs page in README
---
README.md | 10 +++++-----
package.json | 2 +-
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index e5f68b3..1ed6ed1 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-
+
@@ -20,14 +20,14 @@
| --------- | --------- | --------- | --------- | --------- | --------- |
| IE9+, Edge| native| native*| last 2 versions| last 2 versions| native*
-> \* hashchange navigation triggered by forwards/backwards buttons isn't smooth despite native support. [Learn more](https://jonaskuske.github.io/smoothscroll-anchor-polyfill#hashchange)
+> \* hashchange navigation triggered by forwards/backwards buttons isn't smooth despite native support. [Learn more](https://jonaskuske.github.io/smoothscroll-anchor-polyfill#hashchange-blink)
## Usage
### 1. Set `scroll-behavior: smooth` in CSS
-> Has to be set global (on `html`), [check the docs for limitations](https://jonaskuske.github.io/smoothscroll-anchor-polyfill#limitations)
+> Has to be set global (on `html`), [check the docs for limitations](https://jonaskuske.github.io/smoothscroll-anchor-polyfill#global-only)
Because CSS properties unknown to a browser can't efficiently be parsed from JavaScript, using normal stylesheets is not enough unfortunately. To specify the property in a way the polyfill can read it, you have two options:
#### 1a. Using inline styles
@@ -57,7 +57,7 @@ Alternatively, you can specify the property as the name of a custom font family.
### 2. Install the polyfill
Because this polyfill only wires up anchor links to use the browser's native `window.scroll()` and `element.scrollIntoView()` methods, you'll need to load a polyfill providing smooth scroll to these methods in addition to the steps outlined below.
-> [smoothscroll-polyfill](http://iamdustan.com/smoothscroll/) works, but you can just as well use another one or write your own implementation. [Learn More](https://jonaskuske.github.io/smoothscroll-anchor-polyfill#requirements)
+> [smoothscroll-polyfill](http://iamdustan.com/smoothscroll/) works, but you can just as well use another one or write your own implementation. [Learn More](https://jonaskuske.github.io/smoothscroll-anchor-polyfill#usage)
#### 2a. From a CDN
```html
@@ -79,7 +79,7 @@ import 'smoothscroll-anchor-polyfill';
> ⚠ The documentation is not up-to-date as of now, it will be updated when this packages leaves the beta phase.
-The full documentation with advanced installation instructions, known limitations, features like enabling and disabling the smooth scroll and more can be found at
+The full documentation with advanced installation instructions, limitations, features like enabling and disabling the smooth scrolling and more can be found at
[**jonaskuske.github.io/smoothscroll-anchor-polyfill**](https://jonaskuske.github.io/smoothscroll-anchor-polyfill).
The documentation site itself is built as a smooth scrolling one-page design, utilizing this polyfill.
diff --git a/package.json b/package.json
index f7b29bc..149a15a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "smoothscroll-anchor-polyfill",
- "version": "1.0.0-beta",
+ "version": "1.1.0",
"description": "Apply smooth scroll to anchor links to replicate CSS scroll-behavior",
"main": "dist/index.js",
"browserslist": [
From 21c6712c30b70753c06715200c82d47a3ecb35ca Mon Sep 17 00:00:00 2001
From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com>
Date: Sat, 8 Dec 2018 02:59:57 +0100
Subject: [PATCH 6/8] docs: update release date in CHANGELOG
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a3889a..cffd0b9 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,7 @@ 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).
-## [1.0.0] - 2018-12-07
+## [1.0.0] - 2018-12-08
### Added
- The methods 'destroy' and 'polyfill' are now exported (CommonJS) or exposed on window.SmoothscrollAnchorPolyfill. The polyfill still runs automatically on load so embedding it is enough, but now you can destroy it if you want (EventListeners are removed) and start it again, later.
- In addition to `window.__forceSmoothscrollAnchorPolyfill__`, you can now pass `{ force: true }` when invoking `polyfill()` to force-enable the package even in browsers with native support
From cf24d31aea4115d7bb71c84ac2483506c055e59b Mon Sep 17 00:00:00 2001
From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com>
Date: Sat, 8 Dec 2018 03:06:08 +0100
Subject: [PATCH 7/8] docs: remove warning about outdated docs in README
---
README.md | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 1ed6ed1..6289e2d 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
+
@@ -77,8 +78,6 @@ import 'smoothscroll-anchor-polyfill';
## Full Documentation & Demo
-> ⚠ The documentation is not up-to-date as of now, it will be updated when this packages leaves the beta phase.
-
The full documentation with advanced installation instructions, limitations, features like enabling and disabling the smooth scrolling and more can be found at
[**jonaskuske.github.io/smoothscroll-anchor-polyfill**](https://jonaskuske.github.io/smoothscroll-anchor-polyfill).
The documentation site itself is built as a smooth scrolling one-page design, utilizing this polyfill.
From ddc1a69a5127c6af69c3869f74ee3a081ff1eed5 Mon Sep 17 00:00:00 2001
From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com>
Date: Sat, 8 Dec 2018 03:07:41 +0100
Subject: [PATCH 8/8] chore: fix version number prior to release
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 149a15a..a1b4100 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "smoothscroll-anchor-polyfill",
- "version": "1.1.0",
+ "version": "1.0.0",
"description": "Apply smooth scroll to anchor links to replicate CSS scroll-behavior",
"main": "dist/index.js",
"browserslist": [