Javascript Angular 4 - 滚动动画

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/45199319/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 02:57:19  来源:igfitidea点击:

Angular 4 - Scroll Animation

javascriptcssangularangular-animations

提问by Sumit Ridhal

I'm creating a webpage having full page width/height div's. While scrolling down I've two types of methods.

我正在创建一个具有完整页面宽度/高度 div 的网页。向下滚动时,我有两种方法。

Scroll on Click

滚动点击

//HTML
<a (click)="goToDiv('about')"></a>

//JS
    goToDiv(id) {
        let element = document.querySelector("#"+id);
        element.scrollIntoView(element);
      }

Scroll on HostListener

在 HostListener 上滚动

  @HostListener("window:scroll", ['$event'])
  onWindowScroll($event: any): void {
    this.topOffSet = window.pageYOffset;
    //window.scrollTo(0, this.topOffSet+662);
  }

1. How to add a scrolling animation effects?

1.如何添加滚动动画效果?

Just like :

就像 :

$('.scroll').on('click', function(e) {
    $('html, body').animate({
        scrollTop: $(window).height()
    }, 1200);
});

2. And how to use HostListener to scroll to next div?

2. 以及如何使用 HostListener 滚动到下一个 div?

采纳答案by bryan60

This one is fun. The solution, as with most things angular 2, is observables.

这个很好玩。与大多数 angular 2 一样,解决方案是 observables。

  getTargetElementRef(currentYPos: int): ElementRef {
      // you need to figure out how this works
      // I can't comment much on it without knowing more about the page
      // but you inject the host ElementRef in the component / directive constructor and use normal vanillaJS functions to find other elements
  }
  //capture the scroll event and pass to a function that triggers your own event for clarity and so you can manually trigger
  scrollToSource: Subject<int> = new Subject<int>();
  @HostListener("window:scroll", ['$event'])
  onWindowScroll($event: any): void {
    var target = getTargetElementRef(window.pageYOffset);
    this.scrollTo(target);
  }

  scrollTo(target: ElementRef): void {
     // this assumes you're passing in an ElementRef, it may or may not be appropriate, you can pass them to functions in templates with template variable syntax such as: <div #targetDiv>Scroll Target</div> <button (click)="scrollTo(targetDiv)">Click To Scroll</button>
     this.scrollToSource.next(target.nativeElement.offsetTop);
  }

  //switch map takes the last value emitted by an observable sequence, in this case, the user's latest scroll position, and transforms it into a new observable stream
  this.scrollToSource.switchMap(targetYPos => {
       return Observable.interval(100) //interval just creates an observable stream corresponding to time, this emits every 1/10th of a second. This can be fixed or make it dynamic depending on the distance to scroll
           .scan((acc, curr) =>  acc + 5, window.pageYOffset) // scan takes all values from an emitted observable stream and accumulates them, here you're taking the current position, adding a scroll step (fixed at 5, though this could also be dynamic), and then so on, its like a for loop with +=, but you emit every value to the next operator which scrolls, the second argument is the start position
           .do(position => window.scrollTo(0, position)) /// here is where you scroll with the results from scan
           .takeWhile(val => val < targetYPos); // stop when you get to the target
  }).subscribe(); //don't forget!

With a click this is easy to use. You just bind scrollTo to a click

只需单击一下,即可轻松使用。您只需将 scrollTo 绑定到单击

This only works for scrolling in one direction, However this should get you started. You can make scan smarter so it subtracts if you need to go up, and instead use a function inside takeWhile that figures out the correct termination condition based on if going up or down.

这仅适用于向一个方向滚动,但是这应该可以帮助您入门。您可以使扫描更智能,因此如果您需要上升,它会减去,而是使用 takeWhile 内部的函数,该函数根据上升或下降确定正确的终止条件。

edit: rxjs 5+ compatible version

编辑:rxjs 5+ 兼容版本

  this.scrollToSource.pipe(switchMap(targetYPos => 
       interval(100).pipe( //interval just creates an observable stream corresponding to time, this emits every 1/10th of a second. This can be fixed or make it dynamic depending on the distance to scroll
           scan((acc, curr) =>  acc + 5, window.pageYOffset), // scan takes all values from an emitted observable stream and accumulates them, here you're taking the current position, adding a scroll step (fixed at 5, though this could also be dynamic), and then so on, its like a for loop with +=, but you emit every value to the next operator which scrolls, the second argument is the start position
           takeWhile(val => val < targetYPos)) // stop when you get to the target
  )).subscribe(position => window.scrollTo(0, position)); // here is where you scroll with the results from scan

回答by Julien

You can also use the CSS property scroll-behavior: smooth

您还可以使用 CSS 属性 scroll-behavior: smooth

in combination with

结合

var yPosition = 1000;
window.scrollTo(0,yPosition)

Ref: developer.mozilla.org/docs/Web/CSS/scroll-behavior

参考:developer.mozilla.org/docs/Web/CSS/scroll-behavior

回答by Mohammad Kermani

The @bryan60 answer works, but I was not comfortable with it, and I preferred to use TimerObservablewhich seems less confusing for other teammates and also easier to customize for future uses.

@bryan60 的答案有效,但我对此并不满意,我更喜欢使用TimerObservable它,这对其他队友来说似乎不那么令人困惑,并且也更容易为将来的使用进行定制。

I suggest you have a shared service for times you're touching DOM, or working with scroll and other HTML element related issues; Then you can have this method on that service (otherwise having it on a component does not make any problem)

我建议您在接触 DOM 或处理滚动和其他 HTML 元素相关问题时使用共享服务;然后您可以在该服务上使用此方法(否则在组件上使用它不会产生任何问题)

  // Choose the target element (see the HTML code bellow):
  @ViewChild('myElement') myElement: ElementRef;

  this.scrollAnimateAvailable:boolean;

animateScrollTo(target: ElementRef) {
    if (this.helperService.isBrowser()) {
      this.scrollAnimateAvailable = true;
      TimerObservable
        .create(0, 20).pipe(
        takeWhile(() => this.scrollAnimateAvailable)).subscribe((e) => {
        if (window.pageYOffset >= target.nativeElement.offsetTop) {
          window.scrollTo(0, window.pageYOffset - e);
        } else if (window.pageYOffset <= target.nativeElement.offsetTop) {
          window.scrollTo(0, window.pageYOffset + e);
        }

        if (window.pageYOffset + 30 > target.nativeElement.offsetTop && window.pageYOffset - 30 < target.nativeElement.offsetTop) {
          this.scrollAnimateAvailable = false;
        }

      });
    }

  }



 scrollToMyElement(){
   this.animateScrollTo(this.myElement)
  }

You need to pass the element to this method, here is how you can do it:

您需要将元素传递给此方法,这是您的方法:

<a (click)="scrollToMyElement()"></a>
<!-- Lots of things here... -->
<div #myElement></div>

回答by Agustin Barrachina

I spent days trying to figure this out. Being a newbie I tried many things and none of them work. Finally, I have a solution so I will post it here.

我花了几天时间试图弄清楚这一点。作为一个新手,我尝试了很多东西,但都没有奏效。最后,我有一个解决方案,所以我会在这里发布。

There are 2 steps:

有2个步骤:

  1. Animate when things appear.
  2. Make things appear when scrolling.
  1. 当事物出现时动画。
  2. 滚动时显示内容。

Part 1:I found out these two great tutorials for newbies:

第 1 部分:我为新手找到了这两个很棒的教程:

  1. The most basicone
  2. The onethat actually animates when stuff appears
  1. 根本一个
  2. 一个实际的东西动画时出现

Part 2:I simply find the solution in this answer

第 2 部分:我只是在这个答案中找到了解决方案



Part 1 Step by Step:

第 1 部分逐步:

  1. Add the line import { BrowserAnimationsModule } from '@angular/platform-browser/animations';to /src/app/app.module.tsand then also:
  1. 该行添加import { BrowserAnimationsModule } from '@angular/platform-browser/animations';/src/app/app.module.ts,然后又:
@NgModule({
  // Other arrays removed
  imports: [
    // Other imports
    BrowserAnimationsModule
  ],
})
  1. In the component.ts you want to animate, add: import { trigger,state,style,transition,animate } from '@angular/animations';And then:
  1. 在要设置动画的 component.ts 中,添加:import { trigger,state,style,transition,animate } from '@angular/animations';然后:
@Component({
  // Here goes the selector and templates and etc.
  animations: [
    trigger('fadeInOut', [
      state('void', style({
        opacity: 0
      })),
      transition('void <=> *', animate(1000)),
    ]),
  ]
})
  1. Finally, in the HTML item you want to animate, add [@fadeInOut].
  1. 最后,在要设置动画的 HTML 项目中,添加[@fadeInOut].

If everything was done correctly, you should now have an animation (but it happens as soon as the webpage loads and not when you scroll.

如果一切都正确完成,您现在应该有一个动画(但它会在网页加载后立即发生,而不是在您滚动时发生。

Part 2 Step by Step:

第 2 部分逐步:

  1. Create a file .ts like for example appear.tsand copy-paste this code:
  1. 例如创建一个文件 .tsappear.ts并复制粘贴以下代码:
import {
    ElementRef, Output, Directive, AfterViewInit, OnDestroy, EventEmitter
  } from '@angular/core';
  import { Observable, Subscription, fromEvent } from 'rxjs';
  import { startWith } from 'rxjs/operators';
  //import 'rxjs/add/observable/fromEvent';
  //import 'rxjs/add/operator/startWith';



  @Directive({
    selector: '[appear]'
  })
  export class AppearDirective implements AfterViewInit, OnDestroy {
    @Output()
    appear: EventEmitter<void>;

    elementPos: number;
    elementHeight: number;

    scrollPos: number;
    windowHeight: number;

    subscriptionScroll: Subscription;
    subscriptionResize: Subscription;

    constructor(private element: ElementRef){
      this.appear = new EventEmitter<void>();
    }

    saveDimensions() {
      this.elementPos = this.getOffsetTop(this.element.nativeElement);
      this.elementHeight = this.element.nativeElement.offsetHeight;
      this.windowHeight = window.innerHeight;
    }
    saveScrollPos() {
      this.scrollPos = window.scrollY;
    }
    getOffsetTop(element: any){
      let offsetTop = element.offsetTop || 0;
      if(element.offsetParent){
        offsetTop += this.getOffsetTop(element.offsetParent);
      }
      return offsetTop;
    }
    checkVisibility(){
      if(this.isVisible()){
        // double check dimensions (due to async loaded contents, e.g. images)
        this.saveDimensions();
        if(this.isVisible()){
          this.unsubscribe();
          this.appear.emit();
        }
      }
    }
    isVisible(){
      return this.scrollPos >= this.elementPos || (this.scrollPos + this.windowHeight) >= (this.elementPos + this.elementHeight);
    }

    subscribe(){
      this.subscriptionScroll = fromEvent(window, 'scroll').pipe(startWith(null))
        .subscribe(() => {
          this.saveScrollPos();
          this.checkVisibility();
        });
      this.subscriptionResize = fromEvent(window, 'resize').pipe(startWith(null))
        .subscribe(() => {
          this.saveDimensions();
          this.checkVisibility();
        });
    }
    unsubscribe(){
      if(this.subscriptionScroll){
        this.subscriptionScroll.unsubscribe();
      }
      if(this.subscriptionResize){
        this.subscriptionResize.unsubscribe();
      }
    }

    ngAfterViewInit(){
      this.subscribe();
    }
    ngOnDestroy(){
      this.unsubscribe();
    }
  }
  1. Import it using import {AppearDirective} from './timeline/appear';and add it to the imports as:
  1. 使用导入import {AppearDirective} from './timeline/appear';并将其添加到导入中:
@NgModule({
  declarations: [
    // Other declarations
    AppearDirective
  ],
  // Imports and stuff
  1. Somewhere in the class do:
  1. 在课堂上的某个地方做:
hasAppeared : boolean = false;
onAppear(){
    this.hasAppeared = true;
    console.log("I have appeared!");   // This is a good idea for debugging
  }
  1. Finally, in the HTML add the two following:
  1. 最后,在 HTML 中添加以下两个:
(appear)="onAppear()" *ngIf="hasAppeared" 

You can check this is working by checking the console for the message "I have appeared!".

您可以通过检查控制台中的消息“我出现了!”来检查这是否有效。