From c2b3bde812cc384b0d163b5d127b9130644e87d6 Mon Sep 17 00:00:00 2001 From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com> Date: Tue, 26 Mar 2019 02:20:13 +0100 Subject: [PATCH 1/6] chore: update date to 2019 in license etc. --- .eslintignore | 1 + LICENSE | 2 +- README.md | 2 +- docs/index.html | 116 ++++++++++++++++++++++++++++++------------------ 4 files changed, 76 insertions(+), 45 deletions(-) create mode 100755 .eslintignore diff --git a/.eslintignore b/.eslintignore new file mode 100755 index 0000000..f59ec20 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/LICENSE b/LICENSE index 2140916..c755c19 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Jonas Kuske +Copyright (c) 2019 Jonas Kuske Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 434400e..ef5380a 100644 --- a/README.md +++ b/README.md @@ -105,4 +105,4 @@ ___   -© 2018, Jonas Kuske +© 2019, Jonas Kuske diff --git a/docs/index.html b/docs/index.html index 9c8df30..346508a 100755 --- a/docs/index.html +++ b/docs/index.html @@ -49,7 +49,9 @@

smoothscroll-anchor-polyfill

⚓ Apply smooth scroll to anchor links to replicate CSS scroll-behavior

-
GitHub
+
GitHub +
@@ -61,12 +63,15 @@

Start

The Scroll Behavior specification allows for native smooth scrolling in browsers – both by using JavaScript scroll - APIs like window.scrollTo and Element.scrollIntoView + APIs like window.scrollTo and + Element.scrollIntoView or by simply setting the property scroll-behavior to - smooth in CSS, which will then make any scrolling smooth + smooth in CSS, which will then make any scrolling + smooth by default. This includes scrolls that are triggered by an anchor link pointing to an element on the page by targeting it's - id in the hash, like <a href="#target">.
+ id in the hash, like + <a href="#target">.
By using this CSS property, you can build a one-page design with smooth scroll between the different sections without having to write a single line of JS – just like this page is doing! @@ -92,18 +97,21 @@

Usage

polyfill for the Scroll Behavior spec in order for this script to make a difference. It just wires up <a> tags and hashchange events to use window.scroll() - and Element.scrollIntoView(). smoothscroll-polyfill + and Element.scrollIntoView(). smoothscroll-polyfill is used as example throughout this site, but you may just as well use another polyfill – or write your own implementation. If you are using jQuery on your site, you could use $.animate() to provide smooth scroll for these scroll APIs, for example.

-

1. Setting scroll-behavior in CSS

+

1. Setting scroll-behavior in + CSS

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-behavior as an inline style on the html element:

<html style="scroll-behavior: smooth;">
@@ -147,7 +155,8 @@ 

Option 2: Using font-family as } <style>

💡 Redeclaring your scroll-behavior properties as font - names can be automated using a PostCSS + 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™

@@ -176,13 +185,15 @@

Option 2: With npm

// (Unlike this package, smoothscroll-polyfill needs to be actively invoked: ) smoothscrollPolyfill.polyfill(); -

👉🏻 The polyfill is also provided in ES module format as index.mjs +

👉🏻 The polyfill is also provided in ES module format as + index.mjs and index.min.mjs.

Advanced installation (with Code Splitting)

If you're using a build system with support for code splitting like - Parcel or Webpack, + 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:

@@ -211,20 +222,23 @@

Docs

execute immediately no matter if loaded through a script tag or in a module 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 behavior you want. The documentation site is using this method: click the "Toggle smooth scroll" button and - notice how the class smooth-scroll is toggled on <html>.

+ notice how the class smooth-scroll is toggled on + <html>.

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, +

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 @@ -236,7 +250,8 @@

Using the polyfill even if there is native support

- window.__forceSmoothscrollAnchorPolyfill__: + window.__forceSmoothscrollAnchorPolyfill__:

If this is set to true, anchor navigation @@ -244,11 +259,14 @@

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. +

This package exports two methods, destroy and + polyfill. If loaded through a script tag, these methods are exposed on - window.smoothscrollAnchorPolyfill.

+ window.smoothscrollAnchorPolyfill. +

Both methods return the polyfill instance so you can chain them (e.g. .destroy().polyfill() to restart the script).

@@ -275,11 +293,13 @@

Methods: destroy and polyfill

⚠ Note that both the global force flag and - the check for native support ('scrollBehavior' in document.documentElement.style) + 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; + itself. Use the force flag or run delete document.documentElement.style.scrollBehavior; if you encounter this problem.

Limitations

scroll-behavior is @@ -294,7 +314,8 @@

scroll-behavior is workaround.

scroll-behavior is only supported as global setting

-

In browsers with native support, you can define scroll-behavior +

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 @@ -303,10 +324,13 @@

scroll-behavior is only at document level, or none.

scroll-behavior doesn't affect scrolling triggered from JavaScript

-

This polyfill only affects scrolling triggered by clicks on <a> +

This polyfill only affects scrolling triggered by clicks on + <a> tags and through hashchange events. You'll still have to - pass { behavior: 'smooth' } when using APIs like window.scroll() - unless your polyfill for these APIs has it's own CSS property check.

+ pass { behavior: 'smooth' } when using APIs like + window.scroll() + unless your polyfill for these APIs has it's own CSS property check. +

Inconsistencies in native implementations

While Scroll Behavior has native support in a couple of browsers @@ -320,7 +344,8 @@

Inconsistencies in native 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, + 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;
@@ -338,7 +363,8 @@ 

Will this work if anchors are inserted 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. Firefox currently is @@ -348,7 +374,8 @@

Does this support prefers-reduced-motionnot disabled automatically if this media query matches.
So no, this polyfill does not automatically disable itself if the - client prefers less motion, but yes, it supports prefers-reduced-motion + client prefers less motion, but yes, it supports + prefers-reduced-motion the same way Firefox does – via a media query:
@media (prefers-reduced-motion: reduce) {
   html {
@@ -361,13 +388,15 @@ 

Does this support prefers-reduced-motion
- +

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". + Recht, nicht das vorgestellte Software-Paket + "smoothscroll-anchor-polyfill". "smoothscroll-anchor-polyfill" selbst ist lizensiert nach der MIT-Lizenz, mehr Informationen können dem GitHub @@ -476,8 +505,8 @@

Datenschutz

  • Verwendetes Betriebssystem
  • Verwendete IP-Adresse
  • -

    Diese Seite verwendet den Dienst Google Fonts, um verschiedene +

    Diese Seite verwendet den Dienst Google Fonts, um verschiedene Schriftarten einzubinden. Beim Abrufen der Schriftdateien, was bei Seitenaufruf automatisch geschieht, können die Daten, die oben aufgelistet @@ -497,11 +526,14 @@

    Datenschutz

    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, not the software package + "smoothscroll-anchor-polyfill", + and are required for compliance with German & European law. + "smoothscroll-anchor-polyfill" itself is licensed under a plain MIT license, for more information - check out the respective GitHub + check out the respective GitHub repository.

    Imprint

    Information in accordance with section §5 TMG

    @@ -535,13 +567,11 @@

    Disclaimer

    10 of the Telemedia Act(TMG).

    Accountability for links

    -

    Responsibility for the content of external links (to web pages of - third - parties) lies solely with the operators of the linked pages. No - violations were - evident to us at the time of linking. Should any legal infringement - become - known to us, we will remove the respective link immediately.

    +

    Responsibility for the content of external links (to web pages of third parties) lies + solely with the operators of the linked pages. No violations were evident to us at the + time of linking. Should any legal infringement become known to us, we will remove the + respective link immediately. +

    Copyright

    Our web pages and their contents are subject to German copyright @@ -562,8 +592,8 @@

    Privacy

  • Used operating system
  • Used IP address
  • -

    This website is using service Google Fonts to load font +

    This website is using service Google Fonts to load font files. While loading the font files – which happens automatically when visiting this site – the pieces of data listed above might be gathered and stored by service provider Google as well.

    @@ -579,7 +609,7 @@

    Privacy

    -

    © 2018, Jonas Kuske

    +

    © 2019, Jonas Kuske

    From 39cdfb51e913d7ff29276c86f246285e0629afb9 Mon Sep 17 00:00:00 2001 From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com> Date: Tue, 26 Mar 2019 02:23:05 +0100 Subject: [PATCH 2/6] feat: UMD module, CSS var, outlines removed see changelog --- CHANGELOG.md | 7 + docs/index.css | 5 +- index.js | 356 +++++++++++++++++++++++-------------------- package.json | 4 +- scripts/esm.js | 6 +- test/browser.test.js | 29 ++-- test/warning.js | 1 + 7 files changed, 222 insertions(+), 186 deletions(-) create mode 100755 test/warning.js diff --git a/CHANGELOG.md b/CHANGELOG.md index dd55c3c..20f9bc9 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ 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.3.0-beta.0] - 2019-03-26 +### Added + - The CSS custom property `--scroll-behavior` can now be used to adjust scroll behavior. The `font-family` workaround still works for supporting legacy browsers that don't implement CSS variables + - Now usable as UMD module with `define()` syntax +### Fixed + - After focusing a scroll target, the outline is only forcefully removed if it wasn't set by the user + ## [1.2.0] - 2018-12-14 ### Added - ES Module versions are now provided as `index.mjs` and `index.min.mjs`! diff --git a/docs/index.css b/docs/index.css index 6475cea..8d13568 100755 --- a/docs/index.css +++ b/docs/index.css @@ -1,12 +1,15 @@ .smooth-scroll { scroll-behavior: smooth; - /* Additionally specified in custom font-family so polyfill can parse it */ + /* Specify in custom property so polyfill can parse it */ + --scroll-behavior: smooth; + /* Additionally specified in custom font-family for IE and other old browsers */ font-family: 'scroll-behavior:smooth'; } @media (prefers-reduced-motion: reduce) { .smooth-scroll { scroll-behavior: auto; + --scroll-behavior: auto; font-family: "scroll-behavior: auto"; } } diff --git a/index.js b/index.js index d23996c..e9bc820 100755 --- a/index.js +++ b/index.js @@ -3,30 +3,27 @@ /** * @license * smoothscroll-anchor-polyfill __VERSION__ - * (c) 2018 Jonas Kuske + * (c) 2019 Jonas Kuske * Released under the MIT License. */ -(function(global, Factory) { - var SmoothscrollAnchorPolyfill = new Factory(); - var isESModule = typeof global !== 'undefined' && global.__ESM_MOCK__; - if (!isESModule && typeof exports === 'object' && typeof module !== 'undefined') { - module.exports = SmoothscrollAnchorPolyfill; - } else { - global.SmoothscrollAnchorPolyfill = SmoothscrollAnchorPolyfill; - } - SmoothscrollAnchorPolyfill.polyfill(); -})(this, /** @constructor */ function SmoothscrollAnchorPolyfill() { - +(function(root, factory) { + var isESModule = root && root.__sap_ES_MODULE__ + // @ts-ignore + if (!isESModule && typeof define === 'function' && define.amd) define([], factory); + else if (!isESModule && typeof module === 'object' && module.exports) module.exports = factory(); + else root.SmoothscrollAnchorPolyfill = factory(); +})(this, function() { + function SmoothscrollAnchorPolyfill() { var instance = this, isBrowser = typeof window !== 'undefined'; if (isBrowser) { - /** - * Add flag to Window interface, workaround for type check - * @typedef {{__forceSmoothscrollAnchorPolyfill__: [boolean]}} GlobalFlag - * @typedef {Window & GlobalFlag} WindowWithFlag - * @type {WindowWithFlag} */ - var w = (window), d = document, docEl = d.documentElement; + /** + * Add flag to Window interface, workaround for type check + * @typedef {{__forceSmoothscrollAnchorPolyfill__: [boolean]}} GlobalFlag + * @typedef {Window & GlobalFlag} WindowWithFlag + * @type {WindowWithFlag} */ + var w = (window), d = document, docEl = d.documentElement, dummy = d.createElement('a'); } /** @@ -34,39 +31,49 @@ * * Aborts, if ('scrollBehavior' in documentElement.style) and the force flag * isn't set on the options parameter Object or globally on window - * @param {PolyfillOptions} [opts] Options for invoking + * @param {PolyfillOptions} [opts] Options for invoking the polyfill * @returns {SmoothscrollAnchorPolyfill} Polyfill Instance, allows for chaining * * @typedef {Object} PolyfillOptions * @prop {boolean} [force] Enable despite native support, overrides global flag */ this.polyfill = function(opts) { - opts = opts || {}; - if (isBrowser) { - var globalFlag = w.__forceSmoothscrollAnchorPolyfill__; - var force = typeof opts.force === 'boolean' ? opts.force : globalFlag; - - // Abort if smoothscroll has native support and force flag isn't set - if ('scrollBehavior' in docEl.style && !force) return instance; - - d.addEventListener('click', handleClick, false); - d.addEventListener('scroll', trackScrollPositions); - w.addEventListener('hashchange', handleHashChange); - } - return instance; + opts = opts || {}; + if (isBrowser) { + var globalFlag = w.__forceSmoothscrollAnchorPolyfill__; + var force = typeof opts.force === 'boolean' ? opts.force : globalFlag; + + // Abort if smoothscroll has native support and force flag isn't set + if ('scrollBehavior' in dummy.style && !force) return instance; + + instance.destroy(); // Remove previous listeners + d.addEventListener('click', handleClick, false); + d.addEventListener('scroll', trackScrollPositions); + w.addEventListener('hashchange', handleHashChange); + } + return instance; }; /** * Stops the polyfill by removing all EventListeners * @returns {SmoothscrollAnchorPolyfill} Polyfill Instance, allows for chaining + * @param {DestroyOptions} [opts] Options for destroying the polyfill + * + * @typedef {Object} DestroyOptions + * @prop {HTMLAnchorElement} [_dummy] DON'T USE THIS. INTERNAL USE ONLY. */ - this.destroy = function() { - if (isBrowser) { - d.removeEventListener('click', handleClick, false); - d.removeEventListener('scroll', trackScrollPositions); - w.removeEventListener('hashchange', handleHashChange); - } - return instance; + this.destroy = function(opts) { + opts = opts || {}; + // For testing purposes only: support injection of external dummy element + // Allows controlling whether env is treated as env with native support + if (opts._dummy) dummy = opts._dummy; + + if (isBrowser) { + d.removeEventListener('click', handleClick, false); + d.removeEventListener('scroll', trackScrollPositions); + w.removeEventListener('hashchange', handleHashChange); + } + return instance; }; if (!isBrowser) return; @@ -74,56 +81,62 @@ // 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); + // 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 + dummy.focus(preppedFocusOption); } catch (e) { } // Regex to extract the value following the scroll-behavior property name var extractValue = /scroll-behavior:[\s]*([^;"`'\s]+)/; + var docElStyle = getComputedStyle(docEl) /** * 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 assigned to style 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; + // Values to check for set scroll-behavior in order of priority/specificity + var valuesToCheck = [ + // Priority 1: behavior assigned to style property + // Allows toggling smoothscroll from JS (docEl.style.scrollBehavior = ...) + // @deprecated + docEl.style.scrollBehavior, + // Priority 2: behavior specified inline in style attribute + (extractValue.exec(docEl.getAttribute('style')) || [])[1], + // Priority 3: custom property + // Behaves like regular CSS, e.g. allows using media queries + docElStyle.getPropertyValue('--scroll-behavior'), + // Priority 4: behavior specified in fontFamily + // Same use case as priority 3, but supports legacy browsers without CSS vars + (extractValue.exec(docElStyle.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 + * @param {?string} 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; + var status = null; + value = value && value.trim(); + if (/^smooth$/.test(value)) status = true; + if (/^(initial|inherit|auto|unset)$/.test(value)) status = false; + return status; } /** @@ -132,8 +145,8 @@ * @returns {HTMLElement} */ function getEventTarget(evt) { - evt = evt || w.event; - return /** @type {HTMLElement} */ (evt.target || evt.srcElement); + evt = evt || w.event; + return /** @type {HTMLElement} */ (evt.target || evt.srcElement); } /** @@ -142,17 +155,15 @@ * @returns {boolean} */ function isAnchorToLocalElement(el) { - // Check if element is an anchor with a fragment in the url - if (!/^a$/i.test(el.tagName) || !/#/.test(el.href)) return false; + // Check if element is an anchor with a fragment in the url + if (!/^a$/i.test(el.tagName) || !/#/.test(el.href)) return false; - // Fix bug in IE9 where anchor.pathname misses leading slash - var anchorPath = el.pathname; - if (anchorPath[0] !== '/') anchorPath = '/' + anchorPath; + // Fix bug in IE9 where anchor.pathname misses leading slash + var anchorPath = el.pathname; + if (anchorPath[0] !== '/') anchorPath = '/' + anchorPath; - // Check if anchor targets an element on the current page - return ( - el.hostname === location.hostname && anchorPath === location.pathname - ); + // Check if anchor targets an element on the current page + return (el.hostname === location.hostname && anchorPath === location.pathname); } /** @@ -161,13 +172,23 @@ * @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 }); + el.focus({ preventScroll: true }); + if (d.activeElement !== el) { + var prevTabIndex = el.getAttribute('tabindex'); + el.setAttribute('tabindex', '-1'); + + if (getComputedStyle(el).outlineStyle === 'none') { + var prevOutline = el.style.outlineStyle; + el.style.outlineStyle = 'none'; + el.addEventListener('blur', function undoOutlineChange() { + el.style.outlineStyle = prevOutline; + el.setAttribute('tabindex', prevTabIndex); + el.removeEventListener('blur', undoOutlineChange); + }) } + + el.focus({ preventScroll: true }); + } } /** @@ -176,15 +197,15 @@ * @param {string} hash */ function getScrollTarget(hash) { - if (typeof hash !== 'string') return null; - hash = decodeHash(hash); - - // 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; + if (typeof hash !== 'string') return null; + hash = decodeHash(hash); + + // 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; } /** @@ -192,11 +213,11 @@ * @param {string} hash Hash to decode */ function decodeHash(hash) { - try { - // "#%F0%9F%91%8D%F0%9F%8F%BB" -> "#👍🏻" - hash = decodeURIComponent(hash); - } catch (e) { /* */ } - return hash; + try { + // "#%F0%9F%91%8D%F0%9F%8F%BB" -> "#👍🏻" + hash = decodeURIComponent(hash); + } catch (e) { /* */ } + return hash; } /** @@ -206,9 +227,9 @@ * @returns {?HTMLElement} The found element or null */ function findInParents(element, validate) { - if (validate(element)) return element; - if (element.parentElement) return findInParents(element.parentElement, validate); - return null; + if (validate(element)) return element; + if (element.parentElement) return findInParents(element.parentElement, validate); + return null; } // Stores the setTimeout id of pending focus changes, allows aborting them @@ -220,19 +241,19 @@ * @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); + // 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); } /** @@ -242,31 +263,31 @@ * @param {MouseEvent} 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 = /** @type {?HTMLAnchorElement} */ ( - 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 || '#')); - } + // 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 = /** @type {?HTMLAnchorElement} */ ( + 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 @@ -284,40 +305,43 @@ * and instead scrolls smoothly to the new hash target */ function handleHashChange() { - // scroll-behavior not set to smooth or body not parsed yet? Abort - if (!d.body || !shouldSmoothscroll()) 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]; - - // @ts-ignore - // Undo the scroll caused by the hashchange... - // Using {behavior: 'instant'} even though it's not in the spec anymore as - // Blink & Gecko support it – once an engine with native support doesn't, - // we need to disable scroll-behavior during scroll reset, then restore - w.scroll({ top: top, behavior: 'instant' }); - // ...and instead smoothscroll to the target - triggerSmoothscroll(target); + // scroll-behavior not set to smooth or body not parsed yet? Abort + if (!d.body || !shouldSmoothscroll()) 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]; + + // @ts-ignore + // Undo the scroll caused by the hashchange... + // Using {behavior: 'instant'} even though it's not in the spec anymore as + // Blink & Gecko support it – once an engine with native support doesn't, + // we need to disable scroll-behavior during scroll reset, then restore + 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; + 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(); + if (!d.body) return; // Body not parsed yet? Abort + lastTwoScrollPos[0] = lastTwoScrollPos[1]; + lastTwoScrollPos[1] = getScrollTop(); } + } + + return new SmoothscrollAnchorPolyfill().polyfill(); }); diff --git a/package.json b/package.json index 78efeee..c069233 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "smoothscroll-anchor-polyfill", - "version": "1.2.0", + "version": "1.3.0-beta.0", "description": "Apply smooth scroll to anchor links to replicate CSS scroll-behavior", "main": "dist/index.js", "unpkg": "dist/index.min.js", @@ -17,7 +17,7 @@ "not dead" ], "scripts": { - "test": "jest --verbose", + "test": "node test/warning && jest --verbose", "build": "npx copyfiles index.js dist && node scripts/esm.js", "minify": "npx terser dist/index.js -m -o dist/index.min.js --comments", "minify:esm": "npx terser dist/index.mjs -m -o dist/index.min.mjs --comments --module", diff --git a/scripts/esm.js b/scripts/esm.js index 4a5811c..afe74c9 100755 --- a/scripts/esm.js +++ b/scripts/esm.js @@ -11,12 +11,12 @@ const originalCode = fs.readFileSync( // We provide an Object as thisArg and tell the script to bind // to it (even if run in CommonJS env) through a special property. // Then we export the polyfill now bound to the provided Object. -const esmCode = `const esmMock = { __ESM_MOCK__: true }; +const esmCode = `const rootObj = { __sap_ES_MODULE__: true }; (function() { ${originalCode} -}).call(esmMock); +}).call(rootObj); -const { SmoothscrollAnchorPolyfill } = esmMock; +const { SmoothscrollAnchorPolyfill } = rootObj; const { destroy, polyfill } = SmoothscrollAnchorPolyfill; export { destroy, polyfill }; diff --git a/test/browser.test.js b/test/browser.test.js index 08a89f2..0b596ac 100755 --- a/test/browser.test.js +++ b/test/browser.test.js @@ -1,6 +1,10 @@ const SmoothscrollAnchorPolyfill = require('../index') const { polyfill, destroy } = SmoothscrollAnchorPolyfill +// Dummy used to control whether env is treated as env with native support +const dummy = document.createElement('a') +const mockNativeSupport = () => dummy.style.scrollBehavior = '' + const insertElement = (type, attrs) => { const el = document.createElement(type) if (attrs) Object.entries(attrs).forEach(([name, value]) => { @@ -10,7 +14,7 @@ const insertElement = (type, attrs) => { return el } -beforeAll(destroy); // Undo auto-execution of polyfill on load +beforeAll(() => destroy({ _dummy: dummy })); beforeEach(() => { window.scroll = f => f Element.prototype.scrollIntoView = f => f @@ -19,6 +23,7 @@ afterEach(() => { delete window.scroll delete Element.prototype.scrollIntoView delete document.documentElement.style.scrollBehavior + delete dummy.style.scrollBehavior delete document.documentElement.style.font delete window.__forceSmoothscrollAnchorPolyfill__ @@ -29,10 +34,10 @@ afterEach(() => { describe('General', () => { it('Bails out if scrollBehavior is natively supported', () => { + document.documentElement.setAttribute('style', 'scroll-behavior:smooth') const anchor = insertElement('a', { href: '#' }) - // Mock native support - document.documentElement.style.scrollBehavior = 'smooth' + mockNativeSupport() const spy = jest.spyOn(window, 'scroll') polyfill() @@ -49,11 +54,10 @@ describe('General', () => { }) it('Runs even with native support if force flag is set on window', () => { + document.documentElement.setAttribute('style', 'scroll-behavior:smooth') const anchor = insertElement('a', { href: '#' }) - // Mock native support - document.documentElement.style.scrollBehavior = 'smooth' - // Set force flag + mockNativeSupport() window.__forceSmoothscrollAnchorPolyfill__ = true const spy = jest.spyOn(window, 'scroll') @@ -64,10 +68,10 @@ describe('General', () => { }) it('Runs even with native support if force flag is passed as arg', () => { + document.documentElement.setAttribute('style', 'scroll-behavior:smooth') const anchor = insertElement('a', { href: '#' }) - // Mock native support - document.documentElement.style.scrollBehavior = 'smooth' + mockNativeSupport() const spy = jest.spyOn(window, 'scroll') // Pass force flag in options object @@ -78,10 +82,10 @@ describe('General', () => { }) it('Allows force flag passed as arg to override global force flag', () => { + document.documentElement.setAttribute('style', 'scroll-behavior:smooth') const anchor = insertElement('a', { href: '#' }) - // Mock native support - document.documentElement.style.scrollBehavior = 'smooth' + mockNativeSupport() // Force-enable polyfill with global flag window.__forceSmoothscrollAnchorPolyfill__ = true @@ -192,15 +196,12 @@ describe('Ways to enable/disable', () => { }) it('Can be enabled by documentElement.style.scrollBehavior', () => { + document.documentElement.style.scrollBehavior = 'smooth' const anchor = insertElement('a', { href: '#' }) const spy = jest.spyOn(window, 'scroll') polyfill() - // Only set scrollBehavior after the polyfill ran, so it doesn't bail - // due to checking for native support ('scrollBehavior' in docEl.style) - document.documentElement.style.scrollBehavior = 'smooth' - anchor.click() expect(spy).toHaveBeenCalled() }) diff --git a/test/warning.js b/test/warning.js new file mode 100755 index 0000000..e00b7a0 --- /dev/null +++ b/test/warning.js @@ -0,0 +1 @@ +console.log("⚠\nNo tests for '--scroll-behavior' as JSDOM doesn't support CSS custom properties.\n") \ No newline at end of file From 9ba7c7ab495b83abd94a07ac4d84ec426e53d8f9 Mon Sep 17 00:00:00 2001 From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com> Date: Tue, 11 Jun 2019 21:05:53 +0200 Subject: [PATCH 3/6] style: update formatting --- .prettierignore | 1 + CHANGELOG.md | 111 ++++++++++++++++++++++++++++++++++-------------- index.js | 2 +- 3 files changed, 80 insertions(+), 34 deletions(-) create mode 100755 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100755 index 0000000..f59ec20 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 20f9bc9..b2a93a5 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,90 +1,135 @@ # 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). ## [1.3.0-beta.0] - 2019-03-26 + ### Added - - The CSS custom property `--scroll-behavior` can now be used to adjust scroll behavior. The `font-family` workaround still works for supporting legacy browsers that don't implement CSS variables - - Now usable as UMD module with `define()` syntax + +- The CSS custom property `--scroll-behavior` can now be used to adjust scroll behavior. The `font-family` workaround still works for supporting legacy browsers that don't implement CSS variables +- Now usable as UMD module with `define()` syntax + ### Fixed - - After focusing a scroll target, the outline is only forcefully removed if it wasn't set by the user + +- After focusing a scroll target, the outline is only forcefully removed if it wasn't set by the user ## [1.2.0] - 2018-12-14 + ### Added - - ES Module versions are now provided as `index.mjs` and `index.min.mjs`! - - Firefox bug where `` doesn't smooth scroll now mentioned in docs + +- ES Module versions are now provided as `index.mjs` and `index.min.mjs`! +- Firefox bug where `` doesn't smooth scroll now mentioned in docs + ### Changed - - Update docs to mention Firefox supporting both `scroll-behavior` and `prefers-reduced-motion` + +- Update docs to mention Firefox supporting both `scroll-behavior` and `prefers-reduced-motion` + ### Fixed - - Support for (URL-encoded) special characters in fragments, e.g. "#👍🏻" (which the browser will encode to "#%F0%9F%91%8D%F0%9F%8F%BB") + +- Support for (URL-encoded) special characters in fragments, e.g. "#👍🏻" (which the browser will encode to "#%F0%9F%91%8D%F0%9F%8F%BB") ## [1.1.3] - 2018-12-10 + ### Changed - - Updated README to include bundle size and SSR-compatibility as features - - Minor improvements to JSDoc typings + +- Updated README to include bundle size and SSR-compatibility as features +- Minor improvements to JSDoc typings + ### Fixed - - Automatically prefix `anchor.pathname` with leading slash if it's missing in `isAnchorToLocalElement()`, fixes flickering in IE9 (caused by clicks being handled by `handleHashchange()` instead of `handleClick` due to click handler not detecting the anchor) - - Use correct property value in `font-family` example in README + +- Automatically prefix `anchor.pathname` with leading slash if it's missing in `isAnchorToLocalElement()`, fixes flickering in IE9 (caused by clicks being handled by `handleHashchange()` instead of `handleClick` due to click handler not detecting the anchor) +- Use correct property value in `font-family` example in README ## [1.1.2] - 2018-12-10 + ### Fixed - - Include minified version in bundle published to npm + +- Include minified version in bundle published to npm ## [1.1.1] - 2018-12-10 + ### Fixed - - Entry `"unpkg"` now actually points at minified version + +- Entry `"unpkg"` now actually points at minified version ## [1.1.0] - 2018-12-10 + ### Added - - `destroy()` and `polyfill()` now return the polyfill instance so you can chain them - - Tests for Node environment (→ SSR), `destroy()`, `polyfill()` and `{ force }` override - - Improved JSDoc typing for better IntelliSense completion - - Entry `"unpkg"` in `package.json`, points at minified version so CDN serves smaller file + +- `destroy()` and `polyfill()` now return the polyfill instance so you can chain them +- Tests for Node environment (→ SSR), `destroy()`, `polyfill()` and `{ force }` override +- Improved JSDoc typing for better IntelliSense completion +- Entry `"unpkg"` in `package.json`, points at minified version so CDN serves smaller file + ### Changed - - You can now override `window.__forceSmoothscrollAnchorPolyfill__` with the `{ force: boolean }` argument of `polyfill()` - - Package entry (`"main"`) now points to unminified file so typing hints are kept - - Explain usage of `{ behavior: 'instant' }` (not in spec anymore) + outline alternative + +- You can now override `window.__forceSmoothscrollAnchorPolyfill__` with the `{ force: boolean }` argument of `polyfill()` +- Package entry (`"main"`) now points to unminified file so typing hints are kept +- Explain usage of `{ behavior: 'instant' }` (not in spec anymore) + outline alternative + ### Fixed - - (Regression) Prevent 'window is not defined' error in Node environments + +- (Regression) Prevent 'window is not defined' error in Node environments ## [1.0.1] - 2018-12-08 + ### Added - - Added feature overview to README + +- Added feature overview to README ## [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 + +- 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 + +- 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 - - 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) + +- The README.md file has been updated to match the API of v1.0.0 +- 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 + +- 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 - + +- Fixed 'window is not defined' error in Node environments, important for usage with SSR + ## [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. + +- 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 ## [0.9.4] - 2018-11-11 + ### Fixed + - After a click, the DOM is now searched upwards for a matching anchor link instead of just checking the event target alone. Fixes issue with nested elements inside anchor links diff --git a/index.js b/index.js index e9bc820..1ba9a2d 100755 --- a/index.js +++ b/index.js @@ -7,7 +7,7 @@ * Released under the MIT License. */ -(function(root, factory) { +(function(/** @type {any} */ root, factory) { var isESModule = root && root.__sap_ES_MODULE__ // @ts-ignore if (!isESModule && typeof define === 'function' && define.amd) define([], factory); From f49770ef7198b8a86a965d600384e9fe8f3a609e Mon Sep 17 00:00:00 2001 From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com> Date: Tue, 11 Jun 2019 21:31:13 +0200 Subject: [PATCH 4/6] chore: improve minification, dead code elimination --- CHANGELOG.md | 6 ++++++ index.js | 12 ++++-------- package.json | 13 ++++++++----- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2a93a5..c785f40 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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.3.0-beta.1] - 2019-06-11 + +### Changed + +- Minor adjustments to improve bundle size + ## [1.3.0-beta.0] - 2019-03-26 ### Added diff --git a/index.js b/index.js index 1ba9a2d..3aea6d6 100755 --- a/index.js +++ b/index.js @@ -1,11 +1,8 @@ // @ts-check -/** - * @license - * smoothscroll-anchor-polyfill __VERSION__ - * (c) 2019 Jonas Kuske - * Released under the MIT License. - */ +/** @license MIT smoothscroll-anchor-polyfill __VERSION__ (c) 2019 Jonas Kuske */ + +var _DEBUG_ = true; // removed during minification (function(/** @type {any} */ root, factory) { var isESModule = root && root.__sap_ES_MODULE__ @@ -66,7 +63,7 @@ opts = opts || {}; // For testing purposes only: support injection of external dummy element // Allows controlling whether env is treated as env with native support - if (opts._dummy) dummy = opts._dummy; + if (_DEBUG_ && opts._dummy) dummy = opts._dummy; if (isBrowser) { d.removeEventListener('click', handleClick, false); @@ -104,7 +101,6 @@ var valuesToCheck = [ // Priority 1: behavior assigned to style property // Allows toggling smoothscroll from JS (docEl.style.scrollBehavior = ...) - // @deprecated docEl.style.scrollBehavior, // Priority 2: behavior specified inline in style attribute (extractValue.exec(docEl.getAttribute('style')) || [])[1], diff --git a/package.json b/package.json index c069233..c5ab78a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "smoothscroll-anchor-polyfill", - "version": "1.3.0-beta.0", + "version": "1.3.0-beta.1", "description": "Apply smooth scroll to anchor links to replicate CSS scroll-behavior", "main": "dist/index.js", "unpkg": "dist/index.min.js", @@ -18,11 +18,14 @@ ], "scripts": { "test": "node test/warning && jest --verbose", - "build": "npx copyfiles index.js dist && node scripts/esm.js", - "minify": "npx terser dist/index.js -m -o dist/index.min.js --comments", - "minify:esm": "npx terser dist/index.mjs -m -o dist/index.min.mjs --comments --module", + "build": "npm run copy && npm run build:esm && npm run minify && npm run version", + "copy": "npx copyfiles index.js dist", + "build:esm": "node scripts/esm.js", + "minify": "npm run minify:umd && npm run minify:esm", + "minify:umd": "npx terser dist/index.js -o dist/index.min.js -m -c passes=3 -d _DEBUG_=false --toplevel", + "minify:esm": "npx terser dist/index.mjs -o dist/index.min.mjs -m -c passes=3 -d _DEBUG_=false --module", "version": "npx replace '__VERSION__' $npm_package_version dist/*", - "prepublishOnly": "npm run build && npm run minify && npm run minify:esm && npm run version" + "prepublishOnly": "npm run build" }, "repository": { "type": "git", From 12334a70881d8c35388ba671080bfbbfc16217a3 Mon Sep 17 00:00:00 2001 From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com> Date: Wed, 12 Jun 2019 00:05:32 +0200 Subject: [PATCH 5/6] docs: update README & documentation --- README.md | 60 +++++++++++++++----- docs/index.html | 148 ++++++++++++++++++++---------------------------- index.js | 3 +- 3 files changed, 109 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index ef5380a..432f8a8 100644 --- a/README.md +++ b/README.md @@ -41,37 +41,69 @@ ## 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#global-only) +> ⚠ 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 +  + +Because CSS properties unknown to a browser can't efficiently be parsed from JavaScript, just specyfing the normal `scroll-behavior` property is not enough unfortunately. +You need to add an additional CSS variable so the polyfill can read it: + +```css +html { + --scroll-behavior: smooth; + scroll-behavior: smooth; +} +``` + +You can also use media queries, toggle classes etc. to control the smooth scroll. The following only enables smooth scroll on Desktop devices, for example: + +```css + html { + --scroll-behavior: auto; + scroll-behavior: auto; + } + + @media screen and (min-width: 1150px) { + html { + --scroll-behavior: smooth; + scroll-behavior: smooth; + } + } +``` + +  + +> 💡 This process can be automated using a [PostCSS plugin](https://github.com/jonaskuske/postcss-smoothscroll-anchor-polyfill), so you can write regular CSS and it'll be transformed to work with the polyfill automatically. +The plugin will also read your [browserslist](https://github.com/browserslist/browserslist) and choose the right transformation depending on if all your browsers support CSS variables or not. It just works™ + +  + +#### Need to support Internet Explorer? + +Legacy browsers like Internet Explorer do not support CSS variables, so you need another way to specify `scroll-behavior`. There are two options: + +##### Using the inline `style` attribute ```html ... ``` -#### 1b. Using `font-family` as workaround -Alternatively, you can specify the property as the name of a custom font family. Your actual fonts will still work the way they should (plus, you can simply declare actual fonts on `body`). Unlike inline styles, this allows you to use normal CSS features like media queries. The following only enables smooth scroll on desktop devices, for example: +##### Using `font-family` +Alternatively, you can specify the property as the name of a custom font family. Your actual fonts will still work the way they should (plus, you can simply declare actual fonts on `body`). As with CSS variables (and unlike inline styles), this allows you to use normal CSS features like media queries. ```html ``` -> This process can be automated using a [PostCSS plugin](https://github.com/jonaskuske/postcss-smoothscroll-anchor-polyfill), so you can write regular CSS and don't have to bother with font-families. It just works™ + +  ### 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. +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#usage) #### 2a. From a CDN ```html diff --git a/docs/index.html b/docs/index.html index 346508a..05b14d7 100755 --- a/docs/index.html +++ b/docs/index.html @@ -108,12 +108,40 @@

    1. Setting scroll-behavior in

    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-behavior as an inline style on - the html element:

    + a performance hit). Instead, you need to additionally specify scroll-behavior + using a CSS variable:

    + +
    <style>
    +  html {
    +    /* CSS custom property for the polyfill */
    +    --scroll-behavior: smooth;
    +
    +    /* Normal CSS property for browsers with native support */
    +    scroll-behavior: smooth;
    +  }
    +<style>
    + +

    You can treat it like any other property, for example use media queries or toggle classes.
    + The following only enables smooth scroll on Desktop devices:

    + +
    <style>
    +  html {
    +    --scroll-behavior: auto;
    +    scroll-behavior: auto;
    +  }
    +
    +  @media screen and (min-width: 1150px) {
    +    html {
    +      --scroll-behavior: smooth;
    +      scroll-behavior: smooth;
    +    }
    +  }
    +<style>
    + +

    Need to support Internet Explorer?

    +

    In legacy browsers like Internet Explorer, CSS Custom Properties are not supported. To specify scroll-behavior, use one of the following options instead:

    + +

    Using the inline style attribute:

    <html style="scroll-behavior: smooth;">
     ...
     </html>
    @@ -122,10 +150,11 @@ 

    Option 1: Using the inline style property using getAttribute('style') even if the browser doesn't parse it.

    -

    Option 2: Using font-family as - workaround

    +

    Using font-family as workaround

    Alternatively, you can specify the property as the name of a custom - font family: + font family. Your actual fonts will still work the way they should (plus, you can + simply declare actual fonts on body instead of html).
    + As with CSS variables (and unlike inline styles), this allows you to use normal CSS features like media queries or classes.

    <style>
       html {
         /* Normal CSS property for browsers with native support */
    @@ -135,30 +164,12 @@ 

    Option 2: Using font-family as 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:

    -
    <style>
    -  html {
    -    scroll-behavior: auto;
    -    font-family: "scroll-behavior: auto";
    -  }
    -
    -  @media screen and (min-width: 1150px) {
    -    html {
    -      scroll-behavior: smooth;
    -      font-family: "scroll-behavior: smooth";
    -    }
    -  }
    -<style>
    -

    💡 Redeclaring your scroll-behavior properties as font - names can be automated using a +

    💡 Redeclaring your scroll-behavior properties to work with this polyfill 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™

    + plugin. You simply write regular CSS and the plugin will intelligently transform it using one of the above options, depending on your supported browsers (detected via browserslist). It just works™

    + +

    2. Installing the polyfill

    Option 1: Using <script>

    @@ -224,83 +235,45 @@

    Docs

    natively, the code won't do anything.

    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. The documentation site is using this method: click +

    The prefered way to dynamically adjust the scroll behavior is through CSS, e.g. toggling a class or using a media query. The documentation site is using this method: click the "Toggle smooth scroll" button and notice how the class smooth-scroll is toggled on <html>.

    -

    Valid property values are smooth for - enabling smooth scroll and auto, initial, - inherit or unset for enabling instant, - jumping scroll.

    +

    Valid property values for scroll-behavior are smooth to + enable smooth scroll, or auto, initial, + inherit or unset to use 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 a - value to it before a polyfill was loaded will break this one and - most other polyfills related to smooth scrolling. ⚠

    + it will have precedence over all other ways of specyfing the scroll behavior.
    + Assigning to .scrollBehavior is not recommened + however, as some packages use this property for feature detection and can break if you mess with it.

    Using the polyfill even if there is native support

    -
    -
    - window.__forceSmoothscrollAnchorPolyfill__: -
    -
    -

    If this is set to true, anchor navigation - will be handled by this script even if the browser supports - native smooth scroll. Not recommended.

    -
    -
    -

    Methods: destroy and - polyfill

    -

    This package exports two methods, destroy and - polyfill. + polyfill.
    If loaded through a script tag, these methods are exposed on window.smoothscrollAnchorPolyfill.

    -

    Both methods return the polyfill instance so you can chain them - (e.g. .destroy().polyfill() to restart the script).

    -
    -
    - destroy(): +
    + polyfill({ force: boolean }):
    -

    The polyfill runs automatically when it's loaded, setting up - the EventListeners it needs. This method disables the - polyfill and removes all EventListeners.

    +

    The polyfill method starts the polyfill. If it is already active, it will simply restart – so you can safely call it without running destroy() first.
    + The method takes an (optional) Object as argument, if you set the property force to true, anchor navigation will be handled by this script even if the browser supports native smooth scroll. Not recommended.

    -
    - polyfill({ force: boolean }): +
    + destroy():
    -

    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 - like the global force flag, but - overrides it if both are set.

    +

    Disables the polyfill and removes all EventListeners.

    -

    ⚠ 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

    @@ -354,6 +327,7 @@

    Inconsistencies in native Anchors pointing to #top don't smooth scroll. Use anchors pointing at # for an easy fix.

    +

    FAQ

    Will this break Server Side Rendering?

    No.

    @@ -379,8 +353,8 @@

    Does this support the same way Firefox does – via a media query:
    @media (prefers-reduced-motion: reduce) {
       html {
    +    --scroll-behavior: auto;
         scroll-behavior: auto;
    -    font-family: "scroll-behavior: auto";
       }
     }

    diff --git a/index.js b/index.js index 3aea6d6..0c89688 100755 --- a/index.js +++ b/index.js @@ -17,7 +17,7 @@ var _DEBUG_ = true; // removed during minification if (isBrowser) { /** * Add flag to Window interface, workaround for type check - * @typedef {{__forceSmoothscrollAnchorPolyfill__: [boolean]}} GlobalFlag + * @typedef {{__forceSmoothscrollAnchorPolyfill__: [boolean]}} GlobalFlag @deprecated * @typedef {Window & GlobalFlag} WindowWithFlag * @type {WindowWithFlag} */ var w = (window), d = document, docEl = d.documentElement, dummy = d.createElement('a'); @@ -37,6 +37,7 @@ var _DEBUG_ = true; // removed during minification this.polyfill = function(opts) { opts = opts || {}; if (isBrowser) { + /** @deprecated */ var globalFlag = w.__forceSmoothscrollAnchorPolyfill__; var force = typeof opts.force === 'boolean' ? opts.force : globalFlag; From f63c548b88611351b4180b0e193e18c8cd4a2139 Mon Sep 17 00:00:00 2001 From: Jonas Kuske <30421456+jonaskuske@users.noreply.github.com> Date: Wed, 12 Jun 2019 00:10:17 +0200 Subject: [PATCH 6/6] chore: bump version, add changelog --- CHANGELOG.md | 15 +++++++++++++++ package.json | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c785f40..db660ad 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ 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.3.0] - 2019-06-12 + +### Added + +- Support Css variable `scroll-behavior` +- UMD/AMD module with `define()` + +### Changed + +- Deprecated `window.__forceSmoothscrollAnchorPolyfill__` (use `polyfill({ force: true })` instead) + +### Fixed + +- After focusing a scroll target, the outline is only forcefully removed if it wasn't set by the user + ## [1.3.0-beta.1] - 2019-06-11 ### Changed diff --git a/package.json b/package.json index c5ab78a..a25c460 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "smoothscroll-anchor-polyfill", - "version": "1.3.0-beta.1", + "version": "1.3.0", "description": "Apply smooth scroll to anchor links to replicate CSS scroll-behavior", "main": "dist/index.js", "unpkg": "dist/index.min.js",