scroll-behavior-polyfill
Advanced tools
Comparing version 2.0.1 to 2.0.3
@@ -0,1 +1,14 @@ | ||
## [2.0.2](https://github.com/wessberg/scroll-behavior-polyfill/compare/v2.0.1...v2.0.2) (2019-01-11) | ||
### Features | ||
* **improvements:** Bug fixes, support for scrolling via the scrollTop and scrollLeft setters, and more ([4989d15](https://github.com/wessberg/scroll-behavior-polyfill/commit/4989d15)) | ||
## [2.0.1](https://github.com/wessberg/scroll-behavior-polyfill/compare/v2.0.0...v2.0.1) (2019-01-09) | ||
# [2.0.0](https://github.com/wessberg/scroll-behavior-polyfill/compare/v1.0.2...v2.0.0) (2019-01-09) | ||
@@ -2,0 +15,0 @@ |
@@ -53,8 +53,2 @@ (function () { | ||
var ELEMENT_ORIGINAL_SCROLL = Element.prototype.scroll; | ||
var ELEMENT_ORIGINAL_SCROLL_BY = Element.prototype.scrollBy; | ||
var ELEMENT_ORIGINAL_SCROLL_TO = Element.prototype.scrollTo; | ||
var styleDeclarationPropertyName = "scrollBehavior"; | ||
@@ -162,5 +156,92 @@ var styleAttributePropertyName = "scroll-behavior"; | ||
var ELEMENT_ORIGINAL_SCROLL = Element.prototype.scroll; | ||
var WINDOW_ORIGINAL_SCROLL = window.scroll; | ||
var ELEMENT_ORIGINAL_SCROLL_BY = Element.prototype.scrollBy; | ||
var WINDOW_ORIGINAL_SCROLL_BY = window.scrollBy; | ||
var ELEMENT_ORIGINAL_SCROLL_TO = Element.prototype.scrollTo; | ||
var WINDOW_ORIGINAL_SCROLL_TO = window.scrollTo; | ||
/** | ||
* A fallback if Element.prototype.scroll is not defined | ||
* @param {number} x | ||
* @param {number} y | ||
*/ | ||
function elementPrototypeScrollFallback(x, y) { | ||
this.__adjustingScrollPosition = true; | ||
this.scrollLeft = x; | ||
this.scrollTop = y; | ||
delete this.__adjustingScrollPosition; | ||
} | ||
/** | ||
* A fallback if Element.prototype.scrollTo is not defined | ||
* @param {number} x | ||
* @param {number} y | ||
*/ | ||
function elementPrototypeScrollToFallback(x, y) { | ||
return elementPrototypeScrollFallback.call(this, x, y); | ||
} | ||
/** | ||
* A fallback if Element.prototype.scrollBy is not defined | ||
* @param {number} x | ||
* @param {number} y | ||
*/ | ||
function elementPrototypeScrollByFallback(x, y) { | ||
this.__adjustingScrollPosition = true; | ||
this.scrollLeft += x; | ||
this.scrollTop += y; | ||
delete this.__adjustingScrollPosition; | ||
} | ||
/** | ||
* Gets the original non-patched prototype method for the given kind | ||
* @param {ScrollMethodName} kind | ||
* @param {Element|Window} element | ||
* @return {Function} | ||
*/ | ||
function getOriginalScrollMethodForKind(kind, element) { | ||
switch (kind) { | ||
case "scroll": | ||
if (element instanceof Element) { | ||
if (ELEMENT_ORIGINAL_SCROLL != null) { | ||
return ELEMENT_ORIGINAL_SCROLL; | ||
} | ||
else { | ||
return elementPrototypeScrollFallback; | ||
} | ||
} | ||
else { | ||
return WINDOW_ORIGINAL_SCROLL; | ||
} | ||
case "scrollBy": | ||
if (element instanceof Element) { | ||
if (ELEMENT_ORIGINAL_SCROLL_BY != null) { | ||
return ELEMENT_ORIGINAL_SCROLL_BY; | ||
} | ||
else { | ||
return elementPrototypeScrollByFallback; | ||
} | ||
} | ||
else { | ||
return WINDOW_ORIGINAL_SCROLL_BY; | ||
} | ||
case "scrollTo": | ||
if (element instanceof Element) { | ||
if (ELEMENT_ORIGINAL_SCROLL_TO != null) { | ||
return ELEMENT_ORIGINAL_SCROLL_TO; | ||
} | ||
else { | ||
return elementPrototypeScrollToFallback; | ||
} | ||
} | ||
else { | ||
return WINDOW_ORIGINAL_SCROLL_TO; | ||
} | ||
} | ||
} | ||
/** | ||
* Gets the Smooth Scroll Options to use for the step function | ||
@@ -190,3 +271,3 @@ * @param {Element|Window} element | ||
: y), | ||
method: WINDOW_ORIGINAL_SCROLL_TO.bind(window) | ||
method: getOriginalScrollMethodForKind("scrollTo", window).bind(window) | ||
}; | ||
@@ -202,9 +283,9 @@ } | ||
startY: startY, | ||
endX: kind === "scrollBy" | ||
endX: Math.floor(kind === "scrollBy" | ||
? startX + x | ||
: x, | ||
endY: kind === "scrollBy" | ||
: x), | ||
endY: Math.floor(kind === "scrollBy" | ||
? startY + y | ||
: y, | ||
method: ELEMENT_ORIGINAL_SCROLL_TO.bind(element) | ||
: y), | ||
method: getOriginalScrollMethodForKind("scrollTo", element).bind(element) | ||
}; | ||
@@ -215,13 +296,2 @@ } | ||
/** | ||
* Gets the scrollLeft version of an element. If a window is provided, the 'pageXOffset' is used. | ||
* @param {Element | Window} element | ||
* @returns {number} | ||
*/ | ||
function getScrollLeft(element) { | ||
if (element instanceof Element) | ||
return element.scrollLeft; | ||
return element.pageXOffset; | ||
} | ||
/** | ||
* Ensures that the given value is numeric | ||
@@ -246,13 +316,2 @@ * @param {number} value | ||
/** | ||
* Gets the scrollTop version of an element. If a window is provided, the 'pageYOffset' is used. | ||
* @param {Element | Window} element | ||
* @returns {number} | ||
*/ | ||
function getScrollTop(element) { | ||
if (element instanceof Element) | ||
return element.scrollTop; | ||
return element.pageYOffset; | ||
} | ||
/** | ||
* Returns true if the given value is some ScrollToOptions | ||
@@ -266,6 +325,2 @@ * @param {number | ScrollToOptions} value | ||
var WINDOW_ORIGINAL_SCROLL = window.scroll; | ||
var WINDOW_ORIGINAL_SCROLL_BY = window.scrollBy; | ||
/** | ||
@@ -279,33 +334,5 @@ * Handles a scroll method | ||
function handleScrollMethod(element, kind, optionsOrX, y) { | ||
// If only one argument is given, and it isn't an options object, throw a TypeError | ||
if (y === undefined && !isScrollToOptions(optionsOrX)) { | ||
throw new TypeError("Failed to execute 'scroll' on 'Element': parameter 1 ('options') is not an object."); | ||
} | ||
// Scroll based on the primitive values given as arguments | ||
if (!isScrollToOptions(optionsOrX)) { | ||
var _a = normalizeScrollCoordinates(optionsOrX, y, element, kind), left = _a.left, top_1 = _a.top; | ||
onScrollPrimitive(left, top_1, element, kind); | ||
} | ||
// Scroll based on the received options object | ||
else { | ||
onScrollWithOptions(__assign({}, normalizeScrollCoordinates(optionsOrX.left, optionsOrX.top, element, kind), { behavior: optionsOrX.behavior == null ? "auto" : optionsOrX.behavior }), element, kind); | ||
} | ||
onScrollWithOptions(getScrollToOptionsWithValidation(optionsOrX, y), element, kind); | ||
} | ||
/** | ||
* Gets the original non-patched prototype method for the given kind | ||
* @param {ScrollMethodName} kind | ||
* @param {Element|Window} element | ||
* @return {Function} | ||
*/ | ||
function getOriginalPrototypeMethodForKind(kind, element) { | ||
switch (kind) { | ||
case "scroll": | ||
return element instanceof Element ? ELEMENT_ORIGINAL_SCROLL : WINDOW_ORIGINAL_SCROLL; | ||
case "scrollBy": | ||
return element instanceof Element ? ELEMENT_ORIGINAL_SCROLL_BY : WINDOW_ORIGINAL_SCROLL_BY; | ||
case "scrollTo": | ||
return element instanceof Element ? ELEMENT_ORIGINAL_SCROLL_TO : WINDOW_ORIGINAL_SCROLL_TO; | ||
} | ||
} | ||
/** | ||
* Invoked when a 'ScrollToOptions' dict is provided to 'scroll()' as the first argument | ||
@@ -320,3 +347,3 @@ * @param {ScrollToOptions} options | ||
if (behavior == null || behavior === "auto") { | ||
getOriginalPrototypeMethodForKind(kind, element).call(element, options.left, options.top); | ||
getOriginalScrollMethodForKind(kind, element).call(element, options.left, options.top); | ||
} | ||
@@ -328,37 +355,32 @@ else { | ||
/** | ||
* Invoked when 'scroll()' is invoked with primitive x or y values | ||
* @param {number} x | ||
* @param {number} y | ||
* @param {ScrollMethodName} kind | ||
* @param {Element|Window} element | ||
*/ | ||
function onScrollPrimitive(x, y, element, kind) { | ||
// noinspection SuspiciousTypeOfGuard | ||
return onScrollWithOptions({ | ||
left: x, | ||
top: y, | ||
behavior: "auto" | ||
}, element, kind); | ||
} | ||
/** | ||
* Normalizes the given scroll coordinates | ||
* @param {number?} x | ||
* @param {number?} y | ||
* @param {Element|Window} element | ||
* @param {ScrollMethodName} kind | ||
* @return {Required<Pick<ScrollToOptions, "top" | "left">>} | ||
*/ | ||
function normalizeScrollCoordinates(x, y, element, kind) { | ||
switch (kind) { | ||
case "scrollBy": | ||
return { | ||
left: getScrollLeft(element) + ensureNumeric(x), | ||
top: getScrollTop(element) + ensureNumeric(y) | ||
}; | ||
default: | ||
return { | ||
left: ensureNumeric(x), | ||
top: ensureNumeric(y) | ||
}; | ||
function normalizeScrollCoordinates(x, y) { | ||
return { | ||
left: ensureNumeric(x), | ||
top: ensureNumeric(y) | ||
}; | ||
} | ||
/** | ||
* Gets ScrollToOptions based on the given arguments. Will throw if validation fails | ||
* @param {number | ScrollToOptions} optionsOrX | ||
* @param {number} y | ||
* @return {Required<ScrollToOptions>} | ||
*/ | ||
function getScrollToOptionsWithValidation(optionsOrX, y) { | ||
// If only one argument is given, and it isn't an options object, throw a TypeError | ||
if (y === undefined && !isScrollToOptions(optionsOrX)) { | ||
throw new TypeError("Failed to execute 'scroll' on 'Element': parameter 1 ('options') is not an object."); | ||
} | ||
// Scroll based on the primitive values given as arguments | ||
if (!isScrollToOptions(optionsOrX)) { | ||
return __assign({}, normalizeScrollCoordinates(optionsOrX, y), { behavior: "auto" }); | ||
} | ||
// Scroll based on the received options object | ||
else { | ||
return __assign({}, normalizeScrollCoordinates(optionsOrX.left, optionsOrX.top), { behavior: optionsOrX.behavior == null ? "auto" : optionsOrX.behavior }); | ||
} | ||
} | ||
@@ -441,3 +463,25 @@ | ||
var scrollingElement = document.scrollingElement != null ? document.scrollingElement : document.documentElement; | ||
/** | ||
* Returns true if the given overflow property represents a scrollable overflow value | ||
* @param {string | null} overflow | ||
* @return {boolean} | ||
*/ | ||
function canOverflow(overflow) { | ||
return overflow !== "visible" && overflow !== "clip"; | ||
} | ||
/** | ||
* Returns true if the given element is scrollable | ||
* @param {Element} element | ||
* @return {boolean} | ||
*/ | ||
function isScrollable(element) { | ||
if (element.clientHeight < element.scrollHeight || element.clientWidth < element.scrollWidth) { | ||
var style = getComputedStyle(element, null); | ||
return (canOverflow(style.overflowY) || | ||
canOverflow(style.overflowX)); | ||
} | ||
return false; | ||
} | ||
/** | ||
* Finds the nearest ancestor of an element that can scroll | ||
@@ -451,11 +495,19 @@ * @param {Element} target | ||
var behavior = getScrollBehavior(currentElement); | ||
if (behavior != null) | ||
if (behavior != null && (currentElement === scrollingElement || isScrollable(currentElement))) { | ||
return [currentElement, behavior]; | ||
} | ||
var parent_1 = getParent(currentElement); | ||
// If the last Node is equal to the latest parentNode, break immediately | ||
if (parent_1 === currentElement) | ||
break; | ||
currentElement = parent_1; | ||
} | ||
return undefined; | ||
// No such element could be found. Start over, but this time find the nearest ancestor that can simply scroll | ||
currentElement = target; | ||
while (currentElement != null) { | ||
if (currentElement === scrollingElement || isScrollable(currentElement)) { | ||
return [currentElement, "auto"]; | ||
} | ||
var parent_2 = getParent(currentElement); | ||
currentElement = parent_2; | ||
} | ||
// Default to the scrolling element | ||
return [scrollingElement, "auto"]; | ||
} | ||
@@ -504,12 +556,2 @@ | ||
return; | ||
// Find the nearest ancestor that can be scrolled | ||
var ancestorWithScrollBehaviorResult = findNearestAncestorsWithScrollBehavior(e.target); | ||
// If there is none, don't proceed | ||
if (ancestorWithScrollBehaviorResult == null) | ||
return; | ||
// Take the scroll behavior for that ancestor | ||
var _a = __read(ancestorWithScrollBehaviorResult, 2), ancestorWithScrollBehavior = _a[0], behavior = _a[1]; | ||
// If the behavior isn't smooth, don't proceed | ||
if (behavior !== "smooth") | ||
return; | ||
// Find the nearest root, whether it be a ShadowRoot or the document itself | ||
@@ -524,2 +566,7 @@ var root = findNearestRoot(e.target); | ||
return; | ||
// Find the nearest ancestor that can be scrolled | ||
var _a = __read(findNearestAncestorsWithScrollBehavior(elementMatch), 2), ancestorWithScrollBehavior = _a[0], behavior = _a[1]; | ||
// If the behavior isn't smooth, don't proceed | ||
if (behavior !== "smooth") | ||
return; | ||
// Otherwise, first prevent the default action. | ||
@@ -805,13 +852,17 @@ e.preventDefault(); | ||
// Find the nearest ancestor that can be scrolled | ||
var ancestorWithScrollBehaviorResult = findNearestAncestorsWithScrollBehavior(this); | ||
// If there is none, opt-out by calling the original implementation | ||
if (ancestorWithScrollBehaviorResult == null) { | ||
ELEMENT_ORIGINAL_SCROLL_INTO_VIEW.call(this, normalizedOptions); | ||
return; | ||
} | ||
var _a = __read(ancestorWithScrollBehaviorResult, 2), ancestorWithScroll = _a[0], ancestorWithScrollBehavior = _a[1]; | ||
var behavior = normalizedOptions.behavior != null ? normalizedOptions.behavior : ancestorWithScrollBehavior; | ||
var _a = __read(findNearestAncestorsWithScrollBehavior(this), 2), ancestorWithScroll = _a[0], ancestorWithScrollBehavior = _a[1]; | ||
var behavior = normalizedOptions.behavior != null | ||
? normalizedOptions.behavior | ||
: ancestorWithScrollBehavior; | ||
// If the behavior isn't smooth, simply invoke the original implementation and do no more | ||
if (behavior !== "smooth") { | ||
ELEMENT_ORIGINAL_SCROLL_INTO_VIEW.call(this, normalizedOptions); | ||
// Assert that 'scrollIntoView' is actually defined | ||
if (ELEMENT_ORIGINAL_SCROLL_INTO_VIEW != null) { | ||
ELEMENT_ORIGINAL_SCROLL_INTO_VIEW.call(this, normalizedOptions); | ||
} | ||
// Otherwise, invoke 'scrollTo' instead and provide the scroll coordinates | ||
else { | ||
var _b = computeScrollIntoView(this, ancestorWithScroll, normalizedOptions), top_1 = _b.top, left = _b.left; | ||
getOriginalScrollMethodForKind("scrollTo", this).call(this, left, top_1); | ||
} | ||
return; | ||
@@ -823,6 +874,41 @@ } | ||
var ELEMENT_ORIGINAL_SCROLL_TOP_SET_DESCRIPTOR = Object.getOwnPropertyDescriptor(Element.prototype, "scrollTop").set; | ||
/** | ||
* Patches the 'scrollTop' property descriptor on the Element prototype | ||
*/ | ||
function patchElementScrollTop() { | ||
Object.defineProperty(Element.prototype, "scrollTop", { | ||
set: function (scrollTop) { | ||
if (this.__adjustingScrollPosition) { | ||
return ELEMENT_ORIGINAL_SCROLL_TOP_SET_DESCRIPTOR.call(this, scrollTop); | ||
} | ||
handleScrollMethod(this, "scrollTo", this.scrollLeft, scrollTop); | ||
return scrollTop; | ||
} | ||
}); | ||
} | ||
var ELEMENT_ORIGINAL_SCROLL_LEFT_SET_DESCRIPTOR = Object.getOwnPropertyDescriptor(Element.prototype, "scrollLeft").set; | ||
/** | ||
* Patches the 'scrollLeft' property descriptor on the Element prototype | ||
*/ | ||
function patchElementScrollLeft() { | ||
Object.defineProperty(Element.prototype, "scrollLeft", { | ||
set: function (scrollLeft) { | ||
if (this.__adjustingScrollPosition) { | ||
return ELEMENT_ORIGINAL_SCROLL_LEFT_SET_DESCRIPTOR.call(this, scrollLeft); | ||
} | ||
handleScrollMethod(this, "scrollTo", scrollLeft, this.scrollTop); | ||
return scrollLeft; | ||
} | ||
}); | ||
} | ||
/** | ||
* Applies the polyfill | ||
*/ | ||
function patch() { | ||
// Element.prototype methods | ||
patchElementScroll(); | ||
@@ -832,9 +918,20 @@ patchElementScrollBy(); | ||
patchElementScrollIntoView(); | ||
// Element.prototype descriptors | ||
patchElementScrollLeft(); | ||
patchElementScrollTop(); | ||
// window methods | ||
patchWindowScroll(); | ||
patchWindowScrollBy(); | ||
patchWindowScrollTo(); | ||
// Navigation | ||
catchNavigation(); | ||
} | ||
if (!SUPPORTS_SCROLL_BEHAVIOR) { | ||
/** | ||
* Is true if the browser natively supports the Element.prototype.[scroll|scrollTo|scrollBy|scrollIntoView] methods | ||
* @type {boolean} | ||
*/ | ||
var SUPPORTS_ELEMENT_PROTOTYPE_SCROLL_METHODS = "scroll" in Element.prototype && "scrollTo" in Element.prototype && "scrollBy" in Element.prototype && "scrollIntoView" in Element.prototype; | ||
if (!SUPPORTS_SCROLL_BEHAVIOR || !SUPPORTS_ELEMENT_PROTOTYPE_SCROLL_METHODS) { | ||
patch(); | ||
@@ -841,0 +938,0 @@ } |
{ | ||
"name": "scroll-behavior-polyfill", | ||
"version": "2.0.1", | ||
"version": "2.0.3", | ||
"description": "A polyfill for the 'scroll-behavior' CSS-property", | ||
@@ -24,3 +24,3 @@ "repository": { | ||
"prepare": "npm run rollup", | ||
"publish:before": "NODE_ENV=production npm run lint && NODE_ENV=production npm run prepare && npm run generate:all && git add . && git commit -am \"Bumped version\" || true", | ||
"publish:before": "NODE_ENV=production npm run lint && NODE_ENV=production npm run prepare && npm run generate:all && git add . && (git commit -am \"Bumped version\" && git push) || true", | ||
"publish:after": "git push && npm publish", | ||
@@ -27,0 +27,0 @@ "publish:patch": "npm run publish:before && npm version patch && npm run publish:after", |
@@ -10,8 +10,13 @@ <a href="https://npmcharts.com/compare/scroll-behavior-polyfill?minimal=true"><img alt="Downloads per month" src="https://img.shields.io/npm/dm/scroll-behavior-polyfill.svg" height="20"></img></a> | ||
> A polyfill for the [`scroll-behavior`](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior) CSS-property | ||
> A polyfill for the [`scroll-behavior`](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior) CSS-property as well as the extensions to the Element interface in the [CSSOM View Module](https://drafts.csswg.org/cssom-view/#dom-element-scrollto-options-options) | ||
## Description | ||
The scroll-behavior CSS property sets the behavior for a scrolling box when scrolling is triggered by the navigation or CSSOM scrolling APIs. | ||
The `scroll-behavior` CSS property sets the behavior for a scrolling box when scrolling is triggered by the navigation or CSSOM scrolling APIs. | ||
This polyfill brings this new feature to all browsers. | ||
It is very efficient, tiny, and works with the latest browser technologies such as Shadow DOM. | ||
This polyfill also implements the extensions to the Element interface in the [CSSOM View Module](https://drafts.csswg.org/cssom-view/#dom-element-scrollto-options-options) such as `Element.prototype.scroll`, `Element.prototype.scrollTo`, `Element.protype.scrollBy`, and `Element.prototype.scrollIntoView`. | ||
## Install | ||
@@ -68,7 +73,7 @@ | ||
<div scroll-behavior="smooth"></div> | ||
``` | ||
```typescript | ||
// Works jut fine when given as a style property | ||
element.style.scrollBehavior = "smooth"; | ||
<script> | ||
// Works jut fine when given as a style property | ||
element.style.scrollBehavior = "smooth"; | ||
</script> | ||
``` | ||
@@ -102,2 +107,9 @@ | ||
You can also use the `scrollTop` and `scrollLeft` setters, both of which works with the polyfill too: | ||
```typescript | ||
element.scrollTop += 100; | ||
element.scrollLeft += 50; | ||
``` | ||
## Dependencies & Browser support | ||
@@ -108,3 +120,2 @@ | ||
- `requestAnimationFrame` | ||
- `Element.prototype.scrollIntoView` | ||
@@ -126,3 +137,2 @@ For by far the most browsers, these features will already be natively available. | ||
- You cannot set `scrollLeft` or `scrollTop`. There is no way to overwrite the property descriptors for those operations. Instead, use `scroll()`, `scrollTo` or `scrollBy` which does the exact same thing. | ||
- `scroll-behavior` properties declared only in stylesheets won't be discovered. This is because [polyfilling CSS is hard and really bad for performance](https://philipwalton.com/articles/the-dark-side-of-polyfilling-css/). | ||
@@ -129,0 +139,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
119590
894
142
0