Skip to content

Commit

Permalink
fix(bug): fixes an issue when using this polyfill along with css-scro…
Browse files Browse the repository at this point in the history
…ll-snap. Closes #5

feat(node): makes it possible to parse and evaluate the polyfill in a non-browser environment without errors. Closes #7
  • Loading branch information
wessberg committed Jul 18, 2019
1 parent 3b688b0 commit c50582b
Show file tree
Hide file tree
Showing 28 changed files with 266 additions and 42 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ This polyfill is distributed in ES3-compatible syntax, but is using some modern
- `requestAnimationFrame`
- `Object.getOwnPropertyDescriptor`
- `Object.defineProperty`
- `WeakMap`

For by far the most browsers, these features will already be natively available.
Generally, I would highly recommend using something like [Polyfill.app](https://github.com/wessberg/Polyfiller) which takes care of this stuff automatically.
Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,18 @@
],
"license": "MIT",
"devDependencies": {
"@wessberg/rollup-plugin-ts": "1.1.59",
"@wessberg/scaffold": "1.0.18",
"@wessberg/rollup-plugin-ts": "1.1.62",
"@wessberg/scaffold": "1.0.19",
"@wessberg/ts-config": "^0.0.41",
"rollup": "^1.16.1",
"rollup-plugin-node-resolve": "^5.0.3",
"rollup": "^1.17.0",
"rollup-plugin-node-resolve": "^5.2.0",
"tslib": "^1.10.0",
"tslint": "^5.18.0",
"typescript": "^3.5.2",
"typescript": "^3.5.3",
"standard-changelog": "^2.0.11",
"prettier": "^1.18.2",
"pretty-quick": "^1.11.1",
"husky": "^2.4.1",
"husky": "^3.0.0",
"np": "^5.0.3"
},
"dependencies": {},
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {SUPPORTS_SCROLL_BEHAVIOR} from "./support/supports-scroll-behavior";
import {patch} from "./patch/patch";
import {SUPPORTS_ELEMENT_PROTOTYPE_SCROLL_METHODS} from "./support/supports-element-prototype-scroll-methods";
import {UNSUPPORTED_ENVIRONMENT} from "./support/unsupported-environment";

if (!SUPPORTS_SCROLL_BEHAVIOR || !SUPPORTS_ELEMENT_PROTOTYPE_SCROLL_METHODS) {
if (!UNSUPPORTED_ENVIRONMENT && (!SUPPORTS_SCROLL_BEHAVIOR || !SUPPORTS_ELEMENT_PROTOTYPE_SCROLL_METHODS)) {
patch();
}
4 changes: 3 additions & 1 deletion src/original/element/scroll-by.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const ELEMENT_ORIGINAL_SCROLL_BY = Element.prototype.scrollBy;
import {UNSUPPORTED_ENVIRONMENT} from "../../support/unsupported-environment";

export const ELEMENT_ORIGINAL_SCROLL_BY = UNSUPPORTED_ENVIRONMENT ? undefined : Element.prototype.scrollBy;
4 changes: 3 additions & 1 deletion src/original/element/scroll-into-view.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const ELEMENT_ORIGINAL_SCROLL_INTO_VIEW = Element.prototype.scrollIntoView;
import {UNSUPPORTED_ENVIRONMENT} from "../../support/unsupported-environment";

export const ELEMENT_ORIGINAL_SCROLL_INTO_VIEW = UNSUPPORTED_ENVIRONMENT ? undefined : Element.prototype.scrollIntoView;
6 changes: 5 additions & 1 deletion src/original/element/scroll-left.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export const ELEMENT_ORIGINAL_SCROLL_LEFT_SET_DESCRIPTOR = Object.getOwnPropertyDescriptor(Element.prototype, "scrollLeft")!.set!;
import {UNSUPPORTED_ENVIRONMENT} from "../../support/unsupported-environment";

export const ELEMENT_ORIGINAL_SCROLL_LEFT_SET_DESCRIPTOR = UNSUPPORTED_ENVIRONMENT
? undefined
: Object.getOwnPropertyDescriptor(Element.prototype, "scrollLeft")!.set!;
4 changes: 3 additions & 1 deletion src/original/element/scroll-to.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const ELEMENT_ORIGINAL_SCROLL_TO = Element.prototype.scrollTo;
import {UNSUPPORTED_ENVIRONMENT} from "../../support/unsupported-environment";

export const ELEMENT_ORIGINAL_SCROLL_TO = UNSUPPORTED_ENVIRONMENT ? undefined : Element.prototype.scrollTo;
6 changes: 5 additions & 1 deletion src/original/element/scroll-top.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export const ELEMENT_ORIGINAL_SCROLL_TOP_SET_DESCRIPTOR = Object.getOwnPropertyDescriptor(Element.prototype, "scrollTop")!.set!;
import {UNSUPPORTED_ENVIRONMENT} from "../../support/unsupported-environment";

export const ELEMENT_ORIGINAL_SCROLL_TOP_SET_DESCRIPTOR = UNSUPPORTED_ENVIRONMENT
? undefined
: Object.getOwnPropertyDescriptor(Element.prototype, "scrollTop")!.set!;
4 changes: 3 additions & 1 deletion src/original/element/scroll.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const ELEMENT_ORIGINAL_SCROLL = Element.prototype.scroll;
import {UNSUPPORTED_ENVIRONMENT} from "../../support/unsupported-environment";

export const ELEMENT_ORIGINAL_SCROLL = UNSUPPORTED_ENVIRONMENT ? undefined : Element.prototype.scroll;
4 changes: 3 additions & 1 deletion src/original/window/scroll-by.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const WINDOW_ORIGINAL_SCROLL_BY = window.scrollBy;
import {UNSUPPORTED_ENVIRONMENT} from "../../support/unsupported-environment";

export const WINDOW_ORIGINAL_SCROLL_BY = UNSUPPORTED_ENVIRONMENT ? undefined : window.scrollBy;
4 changes: 3 additions & 1 deletion src/original/window/scroll-to.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const WINDOW_ORIGINAL_SCROLL_TO = window.scrollTo;
import {UNSUPPORTED_ENVIRONMENT} from "../../support/unsupported-environment";

export const WINDOW_ORIGINAL_SCROLL_TO = UNSUPPORTED_ENVIRONMENT ? undefined : window.scrollTo;
4 changes: 3 additions & 1 deletion src/original/window/scroll.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const WINDOW_ORIGINAL_SCROLL = window.scroll;
import {UNSUPPORTED_ENVIRONMENT} from "../../support/unsupported-environment";

export const WINDOW_ORIGINAL_SCROLL = UNSUPPORTED_ENVIRONMENT ? undefined : window.scroll;
3 changes: 2 additions & 1 deletion src/patch/element/compute-scroll-into-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* The majority of this file is based on https://github.com/stipsan/compute-scroll-into-view (MIT license),
* but has been rewritten to accept a scroller as an argument.
*/
import {getScrollingElement} from "../../util/scrolling-element";

// tslint:disable

Expand Down Expand Up @@ -161,7 +162,7 @@ export function computeScrollIntoView(target: Element, scroller: Element, option
const {block, inline} = options;

// Used to handle the top most element that can be scrolled
const scrollingElement = document.scrollingElement || document.documentElement;
const scrollingElement = getScrollingElement();

// Support pinch-zooming properly, making sure elements scroll into the visual viewport
// Browsers that don't support visualViewport will report the layout viewport dimensions on document.documentElement.clientWidth/Height
Expand Down
2 changes: 1 addition & 1 deletion src/patch/element/scroll-left.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function patchElementScrollLeft(): void {
Object.defineProperty(Element.prototype, "scrollLeft", {
set(scrollLeft: number) {
if (this.__adjustingScrollPosition) {
return ELEMENT_ORIGINAL_SCROLL_LEFT_SET_DESCRIPTOR.call(this, scrollLeft);
return ELEMENT_ORIGINAL_SCROLL_LEFT_SET_DESCRIPTOR!.call(this, scrollLeft);
}

handleScrollMethod(this, "scrollTo", scrollLeft, this.scrollTop);
Expand Down
2 changes: 1 addition & 1 deletion src/patch/element/scroll-top.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function patchElementScrollTop(): void {
Object.defineProperty(Element.prototype, "scrollTop", {
set(scrollTop: number) {
if (this.__adjustingScrollPosition) {
return ELEMENT_ORIGINAL_SCROLL_TOP_SET_DESCRIPTOR.call(this, scrollTop);
return ELEMENT_ORIGINAL_SCROLL_TOP_SET_DESCRIPTOR!.call(this, scrollTop);
}

handleScrollMethod(this, "scrollTo", this.scrollLeft, scrollTop);
Expand Down
6 changes: 3 additions & 3 deletions src/scroll-method/get-original-scroll-method-for-kind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function getOriginalScrollMethodForKind(kind: ScrollMethodName, element:
return elementPrototypeScrollFallback;
}
} else {
return WINDOW_ORIGINAL_SCROLL;
return WINDOW_ORIGINAL_SCROLL!;
}

case "scrollBy":
Expand All @@ -67,7 +67,7 @@ export function getOriginalScrollMethodForKind(kind: ScrollMethodName, element:
return elementPrototypeScrollByFallback;
}
} else {
return WINDOW_ORIGINAL_SCROLL_BY;
return WINDOW_ORIGINAL_SCROLL_BY!;
}

case "scrollTo":
Expand All @@ -78,7 +78,7 @@ export function getOriginalScrollMethodForKind(kind: ScrollMethodName, element:
return elementPrototypeScrollToFallback;
}
} else {
return WINDOW_ORIGINAL_SCROLL_TO;
return WINDOW_ORIGINAL_SCROLL_TO!;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {ISmoothScrollOptions} from "../smooth-scroll-options/i-smooth-scroll-opt
import {now} from "../../util/now";
import {ScrollMethodName} from "../../scroll-method/scroll-method-name";
import {getOriginalScrollMethodForKind} from "../../scroll-method/get-original-scroll-method-for-kind";
import {getScrollingElement} from "../../util/scrolling-element";
import {ScrollSnappable} from "../../util/scroll-snappable";

/**
* Gets the Smooth Scroll Options to use for the step function
Expand All @@ -25,7 +27,8 @@ export function getSmoothScrollOptions(element: Element | Window, x: number, y:
startY,
endX: Math.floor(kind === "scrollBy" ? startX + x : x),
endY: Math.floor(kind === "scrollBy" ? startY + y : y),
method: getOriginalScrollMethodForKind("scrollTo", window).bind(window)
method: getOriginalScrollMethodForKind("scrollTo", window).bind(window),
scroller: getScrollingElement() as ScrollSnappable
};
} else {
const {scrollLeft, scrollTop} = element;
Expand All @@ -37,7 +40,8 @@ export function getSmoothScrollOptions(element: Element | Window, x: number, y:
startY,
endX: Math.floor(kind === "scrollBy" ? startX + x : x),
endY: Math.floor(kind === "scrollBy" ? startY + y : y),
method: getOriginalScrollMethodForKind("scrollTo", element).bind(element)
method: getOriginalScrollMethodForKind("scrollTo", element).bind(element),
scroller: element as ScrollSnappable
};
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {ScrollSnappable} from "../../util/scroll-snappable";

export interface ISmoothScrollOptions {
startX: number;
startY: number;
endX: number;
endY: number;
startTime: number;
scroller: ScrollSnappable;
method(x: number, y: number): void;
}
11 changes: 10 additions & 1 deletion src/smooth-scroll/smooth-scroll/smooth-scroll.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {ISmoothScrollOptions} from "../smooth-scroll-options/i-smooth-scroll-options";
import {ease} from "../../util/easing";
import {disableScrollSnap, DisableScrollSnapResult} from "../../util/disable-scroll-snap";

/**
* The duration of a smooth scroll
Expand All @@ -12,7 +13,7 @@ const SCROLL_TIME = 15000;
* @param {ISmoothScrollOptions} options
*/
export function smoothScroll(options: ISmoothScrollOptions): void {
const {startTime, startX, startY, endX, endY, method} = options;
const {startTime, startX, startY, endX, endY, method, scroller} = options;

let timeLapsed = 0;
let start: number | undefined;
Expand All @@ -21,6 +22,9 @@ export function smoothScroll(options: ISmoothScrollOptions): void {
const distanceY = endY - startY;
const speed = Math.max(Math.abs((distanceX / 1000) * SCROLL_TIME), Math.abs((distanceY / 1000) * SCROLL_TIME));

// Temporarily disables any scroll snapping that may be active since it fights for control over the scroller with this polyfill
let scrollSnapFix: DisableScrollSnapResult | undefined = disableScrollSnap(scroller);

requestAnimationFrame(function animate(timestamp: number) {
if (start == null) {
start = timestamp;
Expand All @@ -35,6 +39,11 @@ export function smoothScroll(options: ISmoothScrollOptions): void {
if (positionX !== endX || positionY !== endY) {
requestAnimationFrame(animate);
start = timestamp;
} else {
if (scrollSnapFix != null) {
scrollSnapFix.reset();
scrollSnapFix = undefined;
}
}
});
}
6 changes: 5 additions & 1 deletion src/support/supports-element-prototype-scroll-methods.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import {UNSUPPORTED_ENVIRONMENT} from "./unsupported-environment";

/**
* Is true if the browser natively supports the Element.prototype.[scroll|scrollTo|scrollBy|scrollIntoView] methods
* @type {boolean}
*/
export const SUPPORTS_ELEMENT_PROTOTYPE_SCROLL_METHODS = "scroll" in Element.prototype && "scrollTo" in Element.prototype && "scrollBy" in Element.prototype && "scrollIntoView" in Element.prototype;
export const SUPPORTS_ELEMENT_PROTOTYPE_SCROLL_METHODS = UNSUPPORTED_ENVIRONMENT
? false
: "scroll" in Element.prototype && "scrollTo" in Element.prototype && "scrollBy" in Element.prototype && "scrollIntoView" in Element.prototype;
4 changes: 3 additions & 1 deletion src/support/supports-scroll-behavior.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {UNSUPPORTED_ENVIRONMENT} from "./unsupported-environment";

/**
* Is true if the browser natively supports the 'scroll-behavior' CSS-property.
* @type {boolean}
*/
export const SUPPORTS_SCROLL_BEHAVIOR = "scrollBehavior" in document.documentElement.style;
export const SUPPORTS_SCROLL_BEHAVIOR = UNSUPPORTED_ENVIRONMENT ? false : "scrollBehavior" in document.documentElement.style;
1 change: 1 addition & 0 deletions src/support/unsupported-environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const UNSUPPORTED_ENVIRONMENT = typeof window === "undefined";
50 changes: 50 additions & 0 deletions src/util/attribute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const STYLE_ATTRIBUTE_PROPERTY_NAME = "scroll-behavior";
const STYLE_ATTRIBUTE_PROPERTY_REGEXP = new RegExp(`${STYLE_ATTRIBUTE_PROPERTY_NAME}:\\s*([^;]*)`);

/**
* Given an Element, this function appends the given ScrollBehavior CSS property value to the elements' 'style' attribute.
* If it doesnt already have one, it will add it.
* @param {Element} element
* @param {ScrollBehavior} behavior
*/
export function appendScrollBehaviorToStyleAttribute(element: Element, behavior: ScrollBehavior): void {
const addition = `${STYLE_ATTRIBUTE_PROPERTY_NAME}:${behavior}`;
let attributeValue = element.getAttribute("style");
if (attributeValue == null || attributeValue === "") {
element.setAttribute("style", addition);
return;
}

// The style attribute may already include a 'scroll-behavior:<something>' in which case that should be replaced
const existingValueForProperty = parseScrollBehaviorFromStyleAttribute(element);
if (existingValueForProperty != null) {
const replacementProperty = `${STYLE_ATTRIBUTE_PROPERTY_NAME}:${existingValueForProperty}`;
// Replace the variant that ends with a semi-colon which it may
attributeValue = attributeValue.replace(`${replacementProperty};`, "");
// Replace the variant that *doesn't* end with a semi-colon
attributeValue = attributeValue.replace(replacementProperty, "");
}

// Now, append the behavior to the string.
element.setAttribute("style", attributeValue.endsWith(";") ? `${attributeValue}${addition}` : `;${attributeValue}${addition}`);
}

/**
* Given an Element, this function attempts to parse its 'style' attribute (if it has one)' to extract
* a value for the 'scroll-behavior' CSS property (if it is given within that style attribute)
* @param {Element} element
* @returns {ScrollBehavior?}
*/
export function parseScrollBehaviorFromStyleAttribute(element: Element): ScrollBehavior | undefined {
const styleAttributeValue = element.getAttribute("style");
if (styleAttributeValue != null && styleAttributeValue.includes(STYLE_ATTRIBUTE_PROPERTY_NAME)) {
const match = styleAttributeValue.match(STYLE_ATTRIBUTE_PROPERTY_REGEXP);
if (match != null) {
const [, behavior] = match;
if (behavior != null && behavior !== "") {
return behavior as ScrollBehavior;
}
}
}
return undefined;
}
Loading

0 comments on commit c50582b

Please sign in to comment.