Angular2平滑滚动

30

我在Angular 2中无法实现平滑滚动服务。是否有任何平滑滚动或纯锚点滚动的服务可用,可以在Angular 2团队使$anchorScroll Angular2等效工作之前使用?

目前我只尝试过:将*ngFor循环增量ID设置在父div上。

[attr.id]="'point' + i"
通过传递的ID,在按钮上调用scrollto。
<button 
     type="button" 
     class="btn btn-lg btn-default " 
     (click)="smoothScroll('point'+i)">
           Scroll to point
</button>

在相关组件中,我正试图实现一个普通的JavaScript平滑滚动函数。

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

我还在尝试为来自窗口提供程序的this._win.scrollTo提供访问权限。

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

**编辑** ---------------------

我将this.win.scrollTo更改为this.win.window.scrollTo,现在我得到了类似于angular1.x $anchorscroll的效果,其中滚动是快速而不是流畅的过渡,但是滚动不够平滑,我收到以下异常错误。

异常错误

更新

在发现angular2的setTimeout稍有不同后,我不再收到该错误,但是滚动仍然是瞬间的,而不是平滑的。

我改变了

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

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

你尝试过什么?有哪些不符合预期的结果?你能否添加一些代码来演示你想要实现的内容? - Günter Zöchbauer
1
我刚刚编辑了这个问题。 - Alex J
9个回答

42

window 对象中有一个名为 scrollTo() 的方法。 如果将其行为设置为 'smooth',页面将处理平滑滚动。 示例(滚动到页面顶部):

 window.scrollTo({ left: 0, top: 0, behavior: 'smooth' });

并且带有回退示例:

    try 
    { 
     window.scrollTo({ left: 0, top: 0, behavior: 'smooth' });
     } catch (e) {
      window.scrollTo(0, 0);
      }

浏览器兼容性是什么样的? - Randy
3
在我的测试中,Vivaldi浏览器和IE浏览器无法工作,因此我使用了一个备用方案。尝试以下代码:try { window.scrollTo({ left: 0, top: 0, behavior: 'smooth' }); } catch (e) { window.scrollTo(0, 0); } - Mert

17

好的,在思考了一会儿后,我找到了一个似乎可以正常工作的解决方案。

与之前相同,我声明了我的条件id和一个按钮,当单击时调用scrollTo函数。

现在,解决方案中只有两个文件是服务,它们将帮助返回文档窗口和模板组件。上面的窗口服务没有进行任何更改,但出于良好回答的目的,我将再次包含它。

window.service.ts:感谢https://gist.github.com/lokanx/cc022ee0b8999cd3b7f5对此部分的帮助。

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

->

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)

我创建了一个示例plunk来展示它的工作方式:Plunk示例


13

更简单的方法是使用这个polyfill:

http://iamdustan.com/smoothscroll/
  1. 安装方式: npm install smoothscroll-polyfill
  2. 在你的polyfill.ts文件中导入它: require('smoothscroll-polyfill').polyfill();
  3. 现在你可以使用scrollIntoView的behavior选项:

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


1
对于 TypeScript,只需执行以下操作:import 'smoothscroll-polyfill'; - Steve Brush
做了所有的事情,但仍然跳动!平滑选项不起作用。 - André Dos Santos
像@AndréDosSantos一样,我的也跳跃。如有进一步建议让其正常工作,将不胜感激。 - Kevin LeStarge
当我在polyfills.ts中使用JIT编译时,通过require('smoothscroll-polyfill').polyfill();它可以正常工作。但是,在AOT构建中,使用require语句无法正常工作(至少在Angular 4中是这样)。然而,import 'smoothscroll-polyfill';似乎也不起作用…有什么建议吗? - Kevin LeStarge
2
我通过在polyfills.ts中添加如下内容使其起作用:import smoothscroll from 'smoothscroll-polyfill/dist/smoothscroll'; smoothscroll.polyfill(); - Kevin LeStarge
显示剩余5条评论

4

例子:

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

2
这个在 Angular 6+ 中对我有效。稍作修改即可:ngOnInit() { this.route.fragment.subscribe(fragment => { if (fragment) { document.querySelector('#' + fragment).scrollIntoView({ behavior: 'smooth' }) } }) } - Gus

2

对于仍在寻找平滑滚动的人,@alex-j的答案在Angular 2.0中非常有效-但我必须将Window服务更改为以下内容:

import { Injectable } from '@angular/core';

function _window() : any {
    // return the global native browser window object
    return window;
}

@Injectable()
export class WindowRef {
    get nativeWindow() : any {
        return _window();
    }
}

感谢这篇博客http://juristr.com/blog/2016/09/ng2-get-window-ref/,现在我有一个可以在任何地方调用的平滑滚动服务 :)


你用那段代码解决了我与其他无关问题的困扰,谢谢!:D - Dunos

2

如果您想要一个非常简单的锚点跳转,可以在路由后和路由视图内使用ng2-simple-page-scroll

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

或者就在路由之后:

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

它只是进行了简单的瞬间跳跃,但它确实有效。

你有没有可能在ng2-simple-page-scroll网站上添加一个演示? - Michael JDI
@MichaelJDI,好的,我会处理这个问题。希望明天能完成。与此同时,请查看https://github.com/bbottema/simple-java-mail/tree/master/src/main/webapp。它展示了一个Java库的工作信息站点,但它包括ng2-simple-page-scroll的Angular2 rc1。 - Benny Bottema
@BennyBottema 这个能在router-deprecated上工作吗,还是只能在新的路由器上使用?我需要使用特定的LocationStrategy吗? - koninos
@Konst 它只能在新路由器上工作,否则您将无法编译它,我想。我没有考虑过特定的哈希策略,但我一直使用HashLocationStrategy。 - Benny Bottema
@BennyBottema 我们可以在没有<a>标签的情况下使用simplePageScroll吗?我们可以通过编程方式触发滚动吗? - BeniaminoBaggins
显示剩余2条评论

1
我使用这段代码。
        var dis = distance  ;
        var interval = setInterval(() => {
            this.document.body.scrollTop = dis;
             dis=dis-5 ;
             if (dis<10){
                 clearInterval(interval);
             }
        }, 5);

0
感谢被接受的答案,我成功地实现了一个平滑的“滚动到顶部”功能。实际上,滚动到顶部比滚动到特定目标元素更容易,因为我们总是滚动到0位置。以下是代码:
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;
}

如果您想要,可以在用户滚动时动态地让您的“回到顶部”按钮出现:
@HostListener('window:scroll', ['$event'])
onWindowScroll(event) {
    this.onScrollFadeInOutScrollToTopButton();
}

shouldShowScrollToTop: boolean = false;

onScrollFadeInOutScrollToTopButton() {
    this.shouldShowScrollToTop = (window.pageYOffset >= window.screen.height/2);
}

回到顶部按钮的HTML代码:

<div class="back-to-top">
<button *ngIf="shouldShowScrollToTop" [@fadeInOutTrigger]="animateButtonEntryState" class="mat-primary" md-fab (click)="smoothScrollToTop()">^</button>

正如您所看到的,该按钮还具有动画触发器。您可以考虑为按钮使用图标,并且理想情况下,您的按钮应具有position:fixed;样式。


-3

还有另一种方法值得考虑:使用jQuery。

也许它不像本地解决方案那样优雅,但非常简单且完美运行。

在您的index.html文件中,您需要将以下内容添加到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>

现在,你可以像这样使用简单的<a href("#section")>导航:
<a href="#section2">Link</a>

它还可以与路由一起使用:

<a class="btn" role="button" routerLink="/contact" fragment="contact_form">Contact us!</a>

6
不可能将整个jQuery库添加到该项目中,仅为了使用scrollTo功能。强烈反对 ;) 在Angular世界里没有必要使用jQuery ;) - DS_web_developer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接