├── index.ts ├── bower.json ├── package.json ├── README.md └── lib └── ng2-SmoothScroll.directive.ts /index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/ng2-SmoothScroll.directive'; 2 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng2SmoothScroll", 3 | "version": "2.0.0", 4 | "authors": [ 5 | "kavil@qq.com" 6 | ], 7 | "description": "A pure-javascript library and set of directives to scroll smoothly to an element with easing.", 8 | "main": "lib/ng2-SmoothScroll.directive.ts", 9 | "keywords": [ 10 | "angularjs2", 11 | "smooth scroll", 12 | "scrolling", 13 | "scroll effects", 14 | "scroll animations", 15 | "smooth", 16 | "scroll" 17 | ], 18 | "license": "MIT", 19 | "homepage": "https://github.com/kavil/ng2SmoothScroll", 20 | "ignore": [ 21 | "**/.*", 22 | "node_modules", 23 | "bower_components", 24 | "test", 25 | "tests" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng2-smooth-scroll", 3 | "version": "2.0.0", 4 | "description": "A pure-javascript library and set of directives to scroll smoothly to an element with easing.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/kavil/ng2SmoothScroll.git" 12 | }, 13 | "keywords": [ 14 | "angularjs2", 15 | "smooth scroll", 16 | "scrolling", 17 | "scroll effects", 18 | "scroll animations", 19 | "smooth", 20 | "scroll" 21 | ], 22 | "author": "kavil", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/kavil/ng2SmoothScroll/issues" 26 | }, 27 | "homepage": "https://github.com/kavil/ng2SmoothScroll#readme" 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Angular2 smooth scroll 2 | ============== 3 | 4 | > base on[Angular smooth scroll](https://github.com/d-oliveros/ngSmoothScroll) 5 | 6 | A pure-javascript library and set of directives to scroll smoothly to an element with easing. Easing support contributed by Willem Liu with code from Gaëtan Renaudeau. 7 | 8 | No jQuery required. 9 | 10 | # Features 11 | 12 | * Exposes a service that scrolls the window to an element's location 13 | * Provides two directives that enable smooth scrolling to elements. 14 | * Clean: No classes are added, no jQuery is required, no CSS files or configuration is needed. 15 | * Scrolling within a custom container added in 2.0.0 16 | 17 | # Installation 18 | 19 | ```js 20 | // bower: 21 | import { SmoothScrollToDirective, SmoothScrollDirective } from "ng2SmoothScroll"; 22 | 23 | // npm: 24 | import { SmoothScrollToDirective, SmoothScrollDirective } from "ng2-smooth-scroll"; 25 | ... 26 | declarations[ 27 | ... 28 | SmoothScrollToDirective, 29 | SmoothScrollDirective, 30 | ... 31 | ] 32 | ``` 33 | 34 | # Bower 35 | 36 | Install with bower with: 37 | 38 | ```bash 39 | bower install ng2SmoothScroll 40 | ``` 41 | 42 | # Npm 43 | 44 | Install with npm with: 45 | 46 | ```bash 47 | npm install ng2-smooth-scroll 48 | ``` 49 | 50 | # Usage - As a directive 51 | 52 | This module provides two directives: 53 | 54 | #### smoothScroll: 55 | 56 | Attribute. Scrolls the window to this element, optionally validating the expression inside scroll-if. 57 | 58 | Example: 59 | ```html 60 | 61 | // Basic - The window will scroll to this element's position when compiling this directive 62 |
63 | 64 | // With options 65 |
72 | {{...}} 73 |
74 | 75 | // Inside a custom container 76 |
83 | {{...}} 84 |
85 | 86 | // With condition 87 |
89 | {{...}} 90 |
91 | 92 | // Inside ng-repeat 93 |
96 | {{...}} 97 |
98 | ``` 99 | 100 | ####scrollTo: 101 | 102 | Attribute. Scrolls the window to the specified element ID when clicking this element. 103 | 104 | Example: 105 | ```html 106 | 107 | // Basic 108 | 110 | Click me! 111 | 112 | 113 | // Custom containers 114 | 117 | Click me! 118 | 119 | 120 | // onClick for non-anchor tags 121 |
123 | Click me! 124 |
125 | 126 | // With options 127 | 135 | 136 | 137 | ``` 138 | 139 | ### Options 140 | 141 | #### duration 142 | Type: `Integer` 143 | Default: `800` 144 | 145 | The duration of the smooth scroll, in miliseconds. 146 | 147 | #### offset 148 | Type: `Integer` 149 | Default: `0` 150 | 151 | The offset from the top of the page in which the scroll should stop. 152 | 153 | #### easing 154 | type: `string` 155 | default: `easeInOutQuart` 156 | 157 | the easing function to be used for this scroll. 158 | 159 | #### middleAlign 160 | type: `boolean` 161 | default: `false` 162 | 163 | Middle align the scrolled element 164 | 165 | #### scrollOnClick 166 | type: `boolean` 167 | default: `false` 168 | 169 | (smoothScroll directive only) Scroll to element when it is clicked 170 | 171 | #### callbackBefore 172 | type: `function` 173 | default: `function(element) {}` 174 | 175 | a callback function to run before the scroll has started. It is passed the 176 | element that will be scrolled to. 177 | 178 | #### callbackAfter 179 | type: `function` 180 | default: `function(element) {}` 181 | 182 | a callback function to run after the scroll has completed. It is passed the 183 | element that was scrolled to. 184 | 185 | #### containerId 186 | type: `string` 187 | default: null 188 | 189 | ID of the scrollable container which the element is a child of. 190 | 191 | ### Easing functions 192 | 193 | The available easing functions are: 194 | * 'easeInQuad' 195 | * 'easeOutQuad' 196 | * 'easeInOutQuad' 197 | * 'easeInCubic' 198 | * 'easeOutCubic' 199 | * 'easeInOutCubic' 200 | * 'easeInQuart' 201 | * 'easeOutQuart' 202 | * 'easeInOutQuart' 203 | * 'easeInQuint' 204 | * 'easeOutQuint' 205 | * 'easeInOutQuint' 206 | 207 | Cheers. 208 | -------------------------------------------------------------------------------- /lib/ng2-SmoothScroll.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input, HostListener, OnInit, ElementRef } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[scrollTo]' 5 | }) 6 | export class SmoothScrollToDirective { 7 | targetElement: any; 8 | 9 | constructor() {} 10 | 11 | @Input('scrollTo') public scrollTo: string; 12 | @Input('duration') public duration: number; 13 | @Input('offset') public offset: number; 14 | @Input('easing') public easing: string; 15 | @Input('callbackBefore') public callbackBefore: any; 16 | @Input('callbackAfter') public callbackAfter: any; 17 | @Input('containerId') public containerId: string; 18 | @Input('middleAlign') public middleAlign: any; 19 | 20 | @HostListener('click') onClick() { 21 | this.targetElement = document.getElementById(this.scrollTo); 22 | if (!this.targetElement) return; 23 | 24 | new SmoothScroll(this.targetElement, { 25 | duration: this.duration, 26 | offset: this.offset, 27 | easing: this.easing, 28 | callbackBefore: this.callbackBefore, 29 | callbackAfter: this.callbackAfter, 30 | containerId: this.containerId, 31 | middleAlign: this.middleAlign 32 | }); 33 | }; 34 | 35 | } 36 | 37 | @Directive({ 38 | selector: '[smoothScroll]' 39 | }) 40 | export class SmoothScrollDirective implements OnInit { 41 | private el; 42 | 43 | constructor(el: ElementRef) { 44 | this.el = el; 45 | } 46 | 47 | @Input('scrollIf') public scrollIf: boolean; 48 | @Input('duration') public duration: number; 49 | @Input('offset') public offset: number; 50 | @Input('easing') public easing: string; 51 | @Input('callbackBefore') public callbackBefore: any; 52 | @Input('callbackAfter') public callbackAfter: any; 53 | @Input('containerId') public containerId: string; 54 | @Input('scrollOnClick') public scrollOnClick: boolean; 55 | @Input('middleAlign') public middleAlign: any; 56 | 57 | @HostListener('click', ['$event.target']) onClick(target) { 58 | if (this.scrollOnClick) { 59 | this.scroll(); 60 | } 61 | }; 62 | 63 | public ngOnInit() { 64 | this.scroll(); 65 | } 66 | 67 | private scroll() { 68 | if (typeof this.scrollIf === 'undefined' || this.scrollIf === true) { 69 | setTimeout(() => { 70 | new SmoothScroll(this.el.nativeElement, { 71 | duration: this.duration, 72 | offset: this.offset, 73 | easing: this.easing, 74 | callbackBefore: this.callbackBefore, 75 | callbackAfter: this.callbackAfter, 76 | containerId: this.containerId, 77 | middleAlign: this.middleAlign 78 | }); 79 | }, 0); 80 | } 81 | } 82 | 83 | } 84 | 85 | 86 | export class SmoothScroll { 87 | constructor(element: any, options: any) { 88 | this.smoothScroll(element, options); 89 | } 90 | private smoothScroll(element, options) { 91 | options = options || {}; 92 | 93 | // Options 94 | let duration = options.duration || 800, 95 | offset = options.offset || 0, 96 | easing = options.easing || 'easeInOutQuart', 97 | callbackBefore = options.callbackBefore || function(){}, 98 | callbackAfter = options.callbackAfter || function(){}, 99 | container = document.getElementById(options.containerId) || null, 100 | containerPresent = (container != undefined && container != null), 101 | middleAlign = options.middleAlign || false; 102 | 103 | /** 104 | * Retrieve current location 105 | */ 106 | let getScrollLocation = function () { 107 | if (containerPresent) { 108 | return container.scrollTop; 109 | } else { 110 | if (window.pageYOffset) { 111 | return window.pageYOffset; 112 | } else { 113 | return document.documentElement.scrollTop; 114 | } 115 | } 116 | }; 117 | 118 | /** 119 | * Calculate easing pattern. 120 | * 121 | * 20150713 edit - zephinzer 122 | * - changed if-else to switch 123 | * @see http://archive.oreilly.com/pub/a/server-administration/excerpts/even-faster-websites/writing-efficient-javascript.html 124 | */ 125 | let getEasingPattern = function (type, time) { 126 | switch (type) { 127 | case 'easeInQuad': return time * time; // accelerating from zero velocity 128 | case 'easeOutQuad': return time * (2 - time); // decelerating to zero velocity 129 | case 'easeInOutQuad': return time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration 130 | case 'easeInCubic': return time * time * time; // accelerating from zero velocity 131 | case 'easeOutCubic': return (--time) * time * time + 1; // decelerating to zero velocity 132 | case 'easeInOutCubic': return time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // acceleration until halfway, then deceleration 133 | case 'easeInQuart': return time * time * time * time; // accelerating from zero velocity 134 | case 'easeOutQuart': return 1 - (--time) * time * time * time; // decelerating to zero velocity 135 | case 'easeInOutQuart': return time < 0.5 ? 8 * time * time * time * time : 1 - 8 * (--time) * time * time * time; // acceleration until halfway, then deceleration 136 | case 'easeInQuint': return time * time * time * time * time; // accelerating from zero velocity 137 | case 'easeOutQuint': return 1 + (--time) * time * time * time * time; // decelerating to zero velocity 138 | case 'easeInOutQuint': return time < 0.5 ? 16 * time * time * time * time * time : 1 + 16 * (--time) * time * time * time * time; // acceleration until halfway, then deceleration 139 | default: return time; 140 | } 141 | }; 142 | 143 | /** 144 | * Calculate how far to scroll 145 | */ 146 | let getEndLocation = function (element) { 147 | let location = 0, 148 | elementRect = element.getBoundingClientRect(), 149 | absoluteElementTop = elementRect.top + window.pageYOffset; 150 | 151 | if (middleAlign) { 152 | location = (absoluteElementTop + (element.offsetHeight / 2)) - (window.innerHeight / 2); 153 | } else { 154 | location = absoluteElementTop; 155 | } 156 | 157 | if (offset) { 158 | location = location - offset; 159 | } 160 | 161 | return Math.max(location, 0); 162 | }; 163 | 164 | // Initialize the whole thing 165 | setTimeout(function () { 166 | let currentLocation = null, 167 | startLocation = getScrollLocation(), 168 | endLocation = getEndLocation(element), 169 | timeLapsed = 0, 170 | distance = endLocation - startLocation, 171 | percentage, 172 | position, 173 | scrollHeight, 174 | internalHeight; 175 | 176 | /** 177 | * Stop the scrolling animation when the anchor is reached (or at the top/bottom of the page) 178 | */ 179 | let stopAnimation = function () { 180 | currentLocation = getScrollLocation(); 181 | if (containerPresent) { 182 | scrollHeight = container.scrollHeight; 183 | internalHeight = container.clientHeight + currentLocation; 184 | } else { 185 | scrollHeight = document.body.scrollHeight; 186 | internalHeight = window.innerHeight + currentLocation; 187 | } 188 | 189 | if ( 190 | ( // condition 1 191 | position == endLocation 192 | ) || 193 | ( // condition 2 194 | currentLocation == endLocation 195 | ) || 196 | ( // condition 3 197 | internalHeight > scrollHeight 198 | ) 199 | ) { // stop 200 | clearInterval(runAnimation); 201 | 202 | callbackAfter(element); 203 | } 204 | }; 205 | 206 | /** 207 | * Scroll the page by an increment, and check if it's time to stop 208 | */ 209 | let animateScroll = function () { 210 | timeLapsed += 16; 211 | percentage = (timeLapsed / duration); 212 | percentage = (percentage > 1) ? 1 : percentage; 213 | position = startLocation + (distance * getEasingPattern(easing, percentage)); 214 | if (containerPresent) { 215 | container.scrollTop = position; 216 | } else { 217 | window.scrollTo(0, position); 218 | } 219 | stopAnimation(); 220 | }; 221 | 222 | callbackBefore(element); 223 | 224 | let runAnimation = setInterval(animateScroll, 16); 225 | }, 0); 226 | 227 | } 228 | } 229 | --------------------------------------------------------------------------------