I am having trouble getting a smooth scroll service to work in angular 2. Are there any services for smooth scrolling, or plain anchor scrolling, that might work until the
Thanks to the accepted answer I was able to implement a smooth "scroll to top". Scrolling to the top is actually even easier than scrolling to a particular target element since we are scrolling to the 0-position always. Here is the code:
scrollTo(yPoint: number, duration: number) {
setTimeout(() => {
window.scrollTo(0, yPoint)
}, duration);
return;
}
smoothScrollToTop() {
let startY = this.currentYPosition();
let stopY = 0; // window top
let distance = stopY > startY ? stopY - startY : startY - stopY;
if (distance < 100) {
window.scrollTo(0, stopY);
return;
}
let speed = Math.round(distance / 100);
let step = speed;
speed = Math.max(9, speed); //min 9 otherwise it won't look smooth
let leapY = stopY > startY ? startY + step : startY - step;
let timer = 0;
if (stopY > startY) {
for (let i = startY; i < stopY; i += step) {
// since setTimeout is asynchronous, the for-loop will will fire all scrolls
// nearly simoultaniously. Therefore, we need to multiply the speed with
// a counter which lets the scrolls start with a growing offset which lets the
// setTimeout wait for a growing time till it scrolls there
// that way, we prevent the window to scroll instantly to the target Yposition
this.scrollTo(leapY, timer * speed);
leapY += step; if (leapY > stopY) leapY = stopY; timer++;
}
return;
} else {
for (let i = startY; i > stopY; i -= step) {
this.scrollTo(leapY, timer * speed);
leapY -= step; if (leapY < stopY) leapY = stopY; timer++;
}
}
}
currentYPosition() {
// Firefox, Chrome, Opera, Safari
if (self.pageYOffset) return self.pageYOffset;
// Internet Explorer 6 - standards mode
if (document.documentElement && document.documentElement.scrollTop)
return document.documentElement.scrollTop;
// Internet Explorer 6, 7 and 8
if (document.body.scrollTop) return document.body.scrollTop;
return 0;
}
If you want, you can let your "Scroll-To-Top" button appear dynamically when user scrolls:
@HostListener('window:scroll', ['$event'])
onWindowScroll(event) {
this.onScrollFadeInOutScrollToTopButton();
}
shouldShowScrollToTop: boolean = false;
onScrollFadeInOutScrollToTopButton() {
this.shouldShowScrollToTop = (window.pageYOffset >= window.screen.height/2);
}
And the HTML for the Scroll-to-top Button:
<div class="back-to-top">
<button *ngIf="shouldShowScrollToTop" [@fadeInOutTrigger]="animateButtonEntryState" class="mat-primary" md-fab (click)="smoothScrollToTop()">^</button>
As you can see, that button also has an animation trigger. You can think about using an icon for the button and ideally, your button should have a position:fixed;
style.
there is a method in the window
object called scrollTo()
.
If you set the behavior to 'smooth' the page will handle the smooth scroll.
example (scroll to top of page):
window.scrollTo({ left: 0, top: 0, behavior: 'smooth' });
And with fallback example:
try
{
window.scrollTo({ left: 0, top: 0, behavior: 'smooth' });
} catch (e) {
window.scrollTo(0, 0);
}
Alright, after scratching my head a little bit, here is a solution that seems to be working ok.
Same as before, I declared my conditional id and a button with the scrollTo function call when clicked.
Now, there are only two files in the solution is a service that will help return the document window and the template's component. Nothing was changed in the window service from the state above but I will include it again for the sake of a good answer.
window.service.ts : shout out to https://gist.github.com/lokanx/cc022ee0b8999cd3b7f5 for helping with this piece
import {Injectable, Provider} from 'angular2/core';
import {window} from 'angular2/src/facade/browser';
import {unimplemented} from 'angular2/src/facade/exceptions';
function _window(): Window {
return window
}
export abstract class WINDOW {
get nativeWindow(): Window {
return unimplemented();
}
}
class WindowRef_ extends WINDOW {
constructor() {
super();
}
get nativeWindow(): Window {
return _window();
}
}
export const WINDOW_PROVIDERS = [
new Provider(WINDOW, { useClass: WindowRef_ }),
];
app.component.ts
import { bootstrap } from 'angular2/platform/browser';
import { Component } from 'angular2/core';
import {WINDOW, WINDOW_PROVIDERS} from './window.service';
@Component({
selector: 'my-app',
templateUrl: 'app.tpl.html',
providers: [WINDOW_PROVIDERS]
})
class AppComponent {
win: Window;
private offSet: number;
constructor(
private _win: WINDOW) {
this.win = _win.nativeWindow;
}
title = 'Ultra Racing';
things = new Array(200);
scrollTo(yPoint: number, duration: number) {
setTimeout(() => {
this.win.window.scrollTo(0, yPoint)
}, duration);
return;
}
smoothScroll(eID) {
var startY = currentYPosition();
var stopY = elmYPosition(eID);
var distance = stopY > startY ? stopY - startY : startY - stopY;
if (distance < 100) {
this.win.window.scrollTo(0, stopY); return;
}
var speed = Math.round(distance / 100);
if (speed >= 20) speed = 20;
var step = Math.round(distance / 100);
var leapY = stopY > startY ? startY + step : startY - step;
var timer = 0;
if (stopY > startY) {
for (var i = startY; i < stopY; i += step) {
this.scrollTo(leapY, timer * speed);
leapY += step; if (leapY > stopY) leapY = stopY; timer++;
} return;
}
for (var i = startY; i > stopY; i -= step) {
this.scrollTo(leapY, timer * speed);
leapY -= step; if (leapY < stopY) leapY = stopY; timer++;
}
}
}
function currentYPosition() {
// Firefox, Chrome, Opera, Safari
if (self.pageYOffset) return self.pageYOffset;
// Internet Explorer 6 - standards mode
if (document.documentElement && document.documentElement.scrollTop)
return document.documentElement.scrollTop;
// Internet Explorer 6, 7 and 8
if (document.body.scrollTop) return document.body.scrollTop;
return 0;
}
function elmYPosition(eID) {
var elm = document.getElementById(eID);
var y = elm.offsetTop;
var node = elm;
while (node.offsetParent && node.offsetParent != document.body) {
node = node.offsetParent;
y += node.offsetTop;
} return y;
}
bootstrap(AppComponent)
I created a plunk to show this example working: Plunk Example
i use this code .
var dis = distance ;
var interval = setInterval(() => {
this.document.body.scrollTop = dis;
dis=dis-5 ;
if (dis<10){
clearInterval(interval);
}
}, 5);
The easier way to achieve this is by using this polyfill: http://iamdustan.com/smoothscroll/
Now you can use behavior option of scrollIntoView as:
(document.querySelector('#'+ anchor)).scrollIntoView({ behavior: 'smooth' });
There is another approach, which sould be considered: using jQuery.
Maybe it is not so elegant as the native solutions, but very easy and works perfectly.
In your index.html you have to add this to the end of the body:
<script
src="https://code.jquery.com/jquery-3.1.1.min.js"
integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
crossorigin="anonymous"></script>
<script>
$(document).on("click", "a[href*='#']:not([href='#'])", function() {
if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {
var target = $(this.hash);
target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
if (target.length) {
$('html, body').animate({
scrollTop: target.offset().top - 100
}, 1000);
return false;
}
}
});
</script>
And now you can use the simple <a href("#section")>
navigation like this:
<a href="#section2">Link</a>
It also works with routing:
<a class="btn" role="button" routerLink="/contact" fragment="contact_form">Contact us!</a>