1
1
import { animate , style , transition , trigger } from '@angular/animations' ;
2
- import { Component , HostBinding , HostListener , Input } from '@angular/core' ;
3
-
4
- const w = window ;
2
+ import {
3
+ Component ,
4
+ HostBinding ,
5
+ HostListener ,
6
+ Input ,
7
+ OnInit ,
8
+ } from '@angular/core' ;
5
9
6
10
@Component ( {
7
11
selector : 'ngx-scroll-top' ,
8
12
template : `
9
- <button *ngIf="!isHidden" (click)="scrollTop()" [@easeInOutAnimation]>
10
- Up
13
+ <button
14
+ *ngIf="!isHidden"
15
+ (click)="scrollTop()"
16
+ [style.background-color]="backgroundColor"
17
+ [@easeInOutAnimation]
18
+ >
19
+ <div #content>
20
+ <ng-content></ng-content>
21
+ </div>
22
+ <svg
23
+ *ngIf="!content?.innerHTML?.length"
24
+ xmlns="http://www.w3.org/2000/svg"
25
+ viewBox="0 0 24 24"
26
+ fill="currentColor"
27
+ aria-hidden="true"
28
+ focusable="false"
29
+ >
30
+ <desc>
31
+ https://fonts.gstatic.com/s/i/materialicons/expand_less/v12/24px.svg
32
+ </desc>
33
+ <path d="M0 0h24v24H0z" fill="none" />
34
+ <path d="M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z" />
35
+ </svg>
11
36
</button>
12
37
` ,
13
38
animations : [
@@ -26,31 +51,115 @@ const w = window;
26
51
`
27
52
:host {
28
53
position: fixed;
29
- right: 2rem;
30
- width: 25px;
31
54
transition: all 0.1s ease-in-out;
32
- z-index: 9999;
55
+ }
56
+ button {
57
+ display: inline-block;
58
+ position: relative;
59
+ transition: all 0.1s ease-in-out;
60
+ color: white;
61
+ box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2),
62
+ 0px 6px 10px 0px rgba(0, 0, 0, 0.14),
63
+ 0px 1px 18px 0px rgba(0, 0, 0, 0.12);
64
+ user-select: none;
65
+ cursor: pointer;
66
+ outline: none;
67
+ border: none;
68
+ box-sizing: border-box;
69
+ white-space: nowrap;
70
+ text-decoration: none;
71
+ vertical-align: baseline;
72
+ text-align: center;
73
+ margin: 0;
74
+ /** line-height: 36px; */
75
+ border-radius: 50%;
76
+ padding: 0;
77
+ flex-shrink: 0;
78
+ }
79
+ button,
80
+ button > svg {
81
+ width: 100%;
82
+ height: 100%;
33
83
}
34
84
` ,
35
85
] ,
36
86
} )
37
- export class NgxScrollTopComponent {
38
- private readonly styleBottomDefault = '32px' ;
39
- public isHidden = true ;
40
-
87
+ export class NgxScrollTopComponent implements OnInit {
41
88
/**
42
- * Offset button from bottom of page dynamically by setting this value.
89
+ * Background color of the back to top button (hex string).
90
+ *
91
+ * Default: `#1775d1` (blue)
92
+ */
93
+ @Input ( ) public backgroundColor = '#1775d1' ;
94
+ /**
95
+ * Offset `px` from bottom of page when scrolled to bottom. For example this
96
+ * can be used to make sure the back to top button never overlaps a footer.
97
+ *
98
+ * Default: `0px`
99
+ *
100
+ * Example: `250px` or `250` because my footer is 250px in height
43
101
*/
44
102
@Input ( ) public bottomOffset : string | number = '0px' ;
103
+ /**
104
+ * The back to top button will not be displayed until the user scrolls to the
105
+ * provided Y (vertical `px`) coordinate on the page.
106
+ *
107
+ * Default: `420px`
108
+ *
109
+ * Example: `100px` or `100`
110
+ */
45
111
@Input ( ) public displayAtYPosition : string | number = '420px' ;
112
+ /**
113
+ * Position on-screen where the back to top button is displayed.
114
+ *
115
+ * Default: `right`
116
+ */
117
+ @Input ( ) public position : 'left' | 'right' = 'right' ;
118
+ /**
119
+ * Height of back to top button in string px format.
120
+ *
121
+ * Default: `25px`
122
+ */
123
+ @Input ( ) @HostBinding ( 'style.height' ) public styleHeight = '40px' ;
124
+ /**
125
+ * Width of back to top button in string px format.
126
+ *
127
+ * Default: `25px`
128
+ */
129
+ @Input ( ) @HostBinding ( 'style.width' ) public styleWidth = '40px' ;
130
+ /**
131
+ * Style the `z-index` for the back to top button as needed for correct layer
132
+ * height adjustment. This can be useful when working with sticky headers.
133
+ *
134
+ * Default: `999`
135
+ */
136
+ @Input ( ) @HostBinding ( 'style.z-index' ) public styleZIndex = 999 ;
46
137
47
- @HostBinding ( 'style.bottom' ) public styleBottom = this . styleBottomDefault ;
48
- @HostBinding ( 'style.height' ) public styleHeight = '25px' ;
138
+ public isHidden = true ;
139
+ private readonly defaultPadding = '16px' ;
140
+
141
+ @HostBinding ( 'style.bottom' ) public styleBottom = this . defaultPadding ;
142
+ @HostBinding ( 'style.left' ) public styleLeft = 'unset' ;
143
+ @HostBinding ( 'style.right' ) public styleRight = this . defaultPadding ;
49
144
@HostListener ( 'window:scroll' , [ ] ) public onWindowScroll ( ) : void {
50
145
this . updateIsHidden ( ) ;
51
146
this . updatePosition ( ) ;
52
147
}
53
148
149
+ public ngOnInit ( ) : void {
150
+ switch ( this . position ) {
151
+ case 'left' :
152
+ this . styleRight = 'unset' ;
153
+ this . styleLeft = this . defaultPadding ;
154
+ break ;
155
+ case 'right' :
156
+ default :
157
+ this . styleRight = this . defaultPadding ;
158
+ this . styleLeft = 'unset' ;
159
+ break ;
160
+ }
161
+ }
162
+
54
163
public scrollTop ( ) : void {
55
164
w . scroll ( {
56
165
top : 0 ,
@@ -61,8 +170,7 @@ export class NgxScrollTopComponent {
61
170
62
171
private updatePosition ( ) : void {
63
172
const useDefaultPosition = ( ) : void => {
64
- // Use the default position.
65
- this . styleBottom = this . styleBottomDefault ;
173
+ this . styleBottom = this . defaultPadding ;
66
174
} ;
67
175
68
176
if ( this . isHidden ) {
@@ -74,13 +182,16 @@ export class NgxScrollTopComponent {
74
182
const { document, scrollY } = w ;
75
183
const { documentElement : docEl } = document ;
76
184
const bottomY = docEl . scrollHeight - docEl . clientHeight ;
77
- const bottomOffset = parseInt ( this . bottomOffset . toString ( ) , 10 ) ;
185
+ const bottomOffset = parsePxStringToInt ( this . bottomOffset ) ;
78
186
const distanceFromBottom = bottomY - scrollY ;
79
- const height = parseInt ( this . styleHeight , 10 ) ;
187
+ const halfHeight = parsePxStringToInt ( this . styleHeight ) / 2 ;
188
+ const defaultPadding = parsePxStringToInt ( this . defaultPadding ) ;
80
189
81
- if ( distanceFromBottom + ( height - height / 2 ) < bottomOffset ) {
190
+ if ( distanceFromBottom + ( halfHeight - defaultPadding ) < bottomOffset ) {
82
191
// Scroll up button exceeded bottom offset, update position.
83
- this . styleBottom = `${ bottomOffset - distanceFromBottom + height } px` ;
192
+ this . styleBottom = `${
193
+ bottomOffset - distanceFromBottom + defaultPadding
194
+ } px`;
84
195
} else {
85
196
useDefaultPosition ( ) ;
86
197
}
@@ -95,10 +206,24 @@ export class NgxScrollTopComponent {
95
206
96
207
private updateIsHidden ( ) : void {
97
208
const { scrollY } = w ;
98
- if ( this . isHidden && scrollY > this . displayAtYPosition ) {
209
+ const displayAtYPosition = parsePxStringToInt ( this . displayAtYPosition ) ;
210
+
211
+ if ( this . isHidden && scrollY > displayAtYPosition ) {
99
212
this . isHidden = false ;
100
- } else if ( ! this . isHidden && scrollY <= this . displayAtYPosition ) {
213
+ } else if ( ! this . isHidden && scrollY <= displayAtYPosition ) {
101
214
this . isHidden = true ;
102
215
}
103
216
}
104
217
}
218
+
219
+ const w = window ;
220
+
221
+ function parsePxStringToInt ( value : string | number ) : number {
222
+ try {
223
+ return parseInt ( value . toString ( ) , 10 ) ;
224
+ } catch ( error : unknown ) {
225
+ throw new Error (
226
+ `Failed to parse value "${ value } " with error: ${ String ( error ) } ` ,
227
+ ) ;
228
+ }
229
+ }
0 commit comments