-
Notifications
You must be signed in to change notification settings - Fork 1
/
scrollToTopOf.ts
147 lines (135 loc) · 5.16 KB
/
scrollToTopOf.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*
* Copyright 2019 LABOR.digital
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Last modified: 2019.02.01 at 15:10
*/
import {getOffset} from '../Dom/getOffset';
import {isBrowser} from '../Environment/isBrowser';
import type {PlainObject} from '../Interfaces/PlainObject';
import {merge} from '../Lists/merge';
import {isBool} from '../Types/isBool';
import {isEmpty} from '../Types/isEmpty';
import {isFunction} from '../Types/isFunction';
import {isNumber} from '../Types/isNumber';
import {isObject} from '../Types/isObject';
import {isString} from '../Types/isString';
import {scrollToPosition} from './scrollToPosition';
interface ScrollOffsetCalculator
{
/**
* Receives the base offset, or 0 if set in the default configuration
* and must return the actual offset to use
* @param offset
*/
(offset: number): number
}
interface ScrollToTopOfConfiguration extends PlainObject
{
/**
* The speed in milliseconds the scroll operation should take
*/
duration?: number;
/**
* The offset to the top of the page when scrolling up
* If you provide a function you can also calculate the offset dynamically,
* which comes in handy if you want to modify the offset based on dynamic values
*/
offset?: number | ScrollOffsetCalculator;
/**
* By default the animation is stopped when the user manually starts to interact with the scrolling.
* If you set this to false the scrolling will continue even on interaction
*/
breakOnManualScroll?: boolean;
/**
* If set this will be used as as scroll container instead of the "window"
* Can be a valid selector for document.querySelector() as a string, as well.
*/
container?: HTMLElement | Window | string;
}
const isInBrowser = isBrowser();
let config: ScrollToTopOfConfiguration = {
duration: 300,
offset: 0,
container: isInBrowser ? window : undefined
};
/**
* Helper to initialize the options without polluting the original options object
* @param options
*/
function initializeOptions(options?: ScrollToTopOfConfiguration): ScrollToTopOfConfiguration
{
const result: any = {...options};
if (!isNumber(result.duration)) {
result.duration = config.duration;
}
if (!isNumber(result.offset)) {
const defaultOffset = isFunction(config.offset) ? (config.offset as any)(0) : config.offset;
result.offset = isFunction(result.offset) ? (result.offset as any)(defaultOffset) : defaultOffset;
}
if (!isObject(result.container) && !isString(result.container)) {
result.container = config.container;
}
if (isEmpty(result.container)) {
result.container = window;
}
if (!isBool(result.breakOnManualScroll)) {
result.breakOnManualScroll = true;
}
return result;
}
/**
* Helper to define the default configuration for the scroll methods
* @param {{}} configuration
* Should receive an object with the following options:
* - duration: (Default 300) The speed in milliseconds the scroll operation should take
* - offset: (Default 0) The offset to the top of the page when scrolling up
* - container: (Default null) The container to scroll instead of the window
*/
export function configureScrollToTopOf(configuration: ScrollToTopOfConfiguration)
{
config = merge(config, configuration) as ScrollToTopOfConfiguration;
}
/**
* Scrolls either the whole page or a specific element with the data attribute "data-scroll-target"
* to the top of the element which is given as $o.
*
* @param target The object to which we should scroll
* @param options Additional options for this scroll operation
* - duration: (Default 300) The duration in milliseconds the scroll operation should take
* - offset: (Default 0) The offset to the top of the page when scrolling up
* - container: (Default null) The container to scroll instead of the window
*/
export function scrollToTopOf(target?: HTMLElement | null, options?: ScrollToTopOfConfiguration)
{
// Noop if not in browser
if (!isBrowser()) {
return;
}
const opt = initializeOptions(options);
if (!target) {
// Simply scroll to the top
return scrollToPosition(0, opt.duration, opt.container);
} else {
// Scroll to a container position
const containerIsWindow = opt.container === window;
const offset = getOffset(target, !containerIsWindow ? opt.container as HTMLElement : undefined);
return scrollToPosition(
offset.top - (opt.offset as number),
opt.duration,
opt.container,
opt.breakOnManualScroll
);
}
}