Smooth scroll angular2

56,384

Solution 1

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

Solution 2

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);
      }

Solution 3

The easier way to achieve this is by using this polyfill: http://iamdustan.com/smoothscroll/

  1. Install it as: npm install smoothscroll-polyfill
  2. Import it in your polyfill.ts file as: require('smoothscroll-polyfill').polyfill();
  3. Now you can use behavior option of scrollIntoView as:

    (document.querySelector('#'+ anchor)).scrollIntoView({ behavior: 'smooth' });

Solution 4

example:

function goToElement(elemId){
 let element = window.getElementById(elemId);
 element.scrollIntoView({behavior: "smooth"});
}

Solution 5

If you want a very simple anchor jump that works after routing and within routed views, you can also use ng2-simple-page-scroll.

<a simplePageScroll href="#myanchor">Go there</a>

Or right after routing:

<a simplePageScroll [routerLink]="['Home']" href="#myanchor">Go there</a>

It does a simple instant jump, but it works.

Share:
56,384
Alex J
Author by

Alex J

Updated on August 02, 2020

Comments

  • Alex J
    Alex J almost 4 years

    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 angular 2 team gets the $anchorScroll angular2 equivalent working?

    So far I have just tried:

    Setting *ngFor loop incremental id on a parent div

    [attr.id]="'point' + i"
    

    Calling a scrollto on a button with the id passed

    <button 
         type="button" 
         class="btn btn-lg btn-default " 
         (click)="smoothScroll('point'+i)">
               Scroll to point
    </button>
    

    And in the associated component I am trying to implement a plain js smooth scroll function

    smoothScroll(eID) {
            var startY = currentYPosition();
            var stopY = elmYPosition(eID);
            var distance = stopY > startY ? stopY - startY : startY - stopY;
            if (distance < 100) {
                scrollTo(0, stopY); return;
            }
            var speed = Math.round(distance / 100);
            if (speed >= 20) speed = 20;
            var step = Math.round(distance / 25);
            var leapY = stopY > startY ? startY + step : startY - step;
            var timer = 0;
            if (stopY > startY) {
                for (var i = startY; i < stopY; i += step) {
                    setTimeout(this.win.scrollTo(0, leapY), timer * speed);
                    leapY += step; if (leapY > stopY) leapY = stopY; timer++;
                } return;
            }
            for (var i = startY; i > stopY; i -= step) {
                setTimeout(this.win.scrollTo(0,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;
    }
    

    I'm also trying to give access to the window for the this._win.scrollTo which is coming from a window provider service

    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_ }),
    ];
    

    ** EDIT ---------------------**

    I changed the this.win.scrollTo to this.win.window.scrollTo and now I am getting an effect similar to angular1.x $anchorscroll where the scroll is a snappy just instead of a smooth transition, but the scroll is not smooth and I am getting the following exception error.

    Exception error

    UPDATE

    I am no longer getting that error after finding out that angular2 is doing the setTimeout a bit differently, but the scroll is still instantaneous and not a smooth scroll.

    I changed

      setTimeout(this.win.scrollTo(0, leapY), timer * speed);
    

    to

     setTimeout(() => this.win.scrollTo(0, leapY), timer * speed);