Angular 4 - 滚动动画

Posted

技术标签:

【中文标题】Angular 4 - 滚动动画【英文标题】:Angular 4 - Scroll Animation 【发布时间】:2017-12-25 05:48:55 【问题描述】:

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

点击滚动

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

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

在 HostListener 上滚动

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

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

就像:

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

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

【问题讨论】:

我认为这是解决方案https://***.com/questions/38748572/scroll-event-on-hostlistener @ObaidulHaque HostListener 工作正常。不知道如何添加动画。 在 Host Listener 中调用的函数中使用 CSS 动画。这里的好指南:css-tricks.com/aos-css-driven-scroll-animation-library 【参考方案1】:

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

结合

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

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

【讨论】:

最简单的方法 我试试那个(代码少一点,你们不是懒人吗?) 最好链接英文 MDN 页面 :) developer.mozilla.org/docs/Web/CSS/scroll-behavior 有一个错字,是:scroll-behavior: smooth; Scroll-behaviour 在 Firefox 和 Chrome 中运行良好,但在 IE 或 Edge 中不受支持。【参考方案2】:

这个很有趣。与大多数 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!

点击一下就可以轻松使用。您只需将 scrollTo 绑定到单击

这仅适用于向一个方向滚动,但是这应该可以帮助您入门。您可以使扫描更智能,以便在您需要上升时进行减法,而是在 takeWhile 中使用一个函数,该函数根据上升或下降来计算正确的终止条件。

编辑: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

【讨论】:

您的代码非常适合我,谢谢。只是想补充一下,对于角度 4>,您需要使用 import 'rxjs/add/operator/switchMap'; 否则您将面临“..switchMap 不是函数”问题。 知道如何将此代码转换为与 rxjs 6 一起使用吗? 大体上是一样的,只是在初级序列中,酌情使用管道操作符,直接导入interval,将do替换成tap,虽然现在看这个,我可能会移动do/点击操作进入订阅处理程序。【参考方案3】:

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

有两个步骤:

    当事物出现时动画化。 滚动时显示内容。

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

    最basic一个 one 在东西出现时实际动画

第2部分:我只是在this answer找到解决方案


第 1 部分一步一步:

    import BrowserAnimationsModule from '@angular/platform-browser/animations'; 行添加到/src/app/app.module.ts,然后:
@NgModule(
  // Other arrays removed
  imports: [
    // Other imports
    BrowserAnimationsModule
  ],
)
    在要动画的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)),
    ]),
  ]
)
    最后,在要制作动画的 HTML 项目中,添加 [@fadeInOut]

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

第 2 部分分步说明:

    创建一个 .ts 文件,例如 appear.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();
    
  
    使用import AppearDirective from './timeline/appear';将其导入并将其添加到导入中:
@NgModule(
  declarations: [
    // Other declarations
    AppearDirective
  ],
  // Imports and stuff
    班上的某处做:
hasAppeared : boolean = false;
onAppear()
    this.hasAppeared = true;
    console.log("I have appeared!");   // This is a good idea for debugging
  
    最后,在 HTML 中添加以下两个:
(appear)="onAppear()" *ngIf="hasAppeared" 

您可以通过检查控制台中的消息“我已经出现!”来检查它是否正常工作。

【讨论】:

【参考方案4】:

@bryan60 答案有效,但我对此并不满意,我更喜欢使用 TimerObservable,这似乎不会让其他队友感到困惑,也更容易定制以供将来使用。

我建议您在接触 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)
  

你需要将元素传递给这个方法,你可以这样做:

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

【讨论】:

以上是关于Angular 4 - 滚动动画的主要内容,如果未能解决你的问题,请参考以下文章

处理 Angular 4 生命周期钩子

typescript Angular 4基本动画#angular #js

Angular 4 平滑动画

用于淡入和淡出视图的 Angular 4 动画

使用变量 Angular 4 制作动画

Angular 2 动画不起作用