Angular 4 - 滚动动画
Angular 4 - Scroll Animation
我正在创建一个包含整页 width/height 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?
这个很有趣。与大多数事情一样,解决方案 angular 2 是可观察的。
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 绑定到一个 click
这只适用于向一个方向滚动,但是这应该可以帮助您入门。您可以使 scan 更智能,以便它在需要上升时减去,而是在 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
您也可以使用 CSS 属性
scroll-behavior: smooth
结合
var yPosition = 1000;
window.scrollTo(0,yPosition)
@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>
我花了好几天时间想弄清楚这个问题。作为新手,我尝试了很多东西,其中 none 成功了。最后,我有一个解决方案,所以我会 post 在这里。
有两个步骤:
- 当事物出现时动画化。
- 滚动时显示内容。
第 1 部分: 我找到了这两个适合新手的很棒的教程:
第 2 部分: 我只是在
中找到了解决方案
第 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
- 在 class 的某个地方做:
hasAppeared : boolean = false;
onAppear(){
this.hasAppeared = true;
console.log("I have appeared!"); // This is a good idea for debugging
}
- 最后,在HTML中添加以下两个:
(appear)="onAppear()" *ngIf="hasAppeared"
您可以通过检查控制台中的消息 "I have appeared!" 来检查它是否正常工作。
我正在创建一个包含整页 width/height 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?
这个很有趣。与大多数事情一样,解决方案 angular 2 是可观察的。
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 绑定到一个 click
这只适用于向一个方向滚动,但是这应该可以帮助您入门。您可以使 scan 更智能,以便它在需要上升时减去,而是在 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
您也可以使用 CSS 属性
scroll-behavior: smooth
结合
var yPosition = 1000;
window.scrollTo(0,yPosition)
@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>
我花了好几天时间想弄清楚这个问题。作为新手,我尝试了很多东西,其中 none 成功了。最后,我有一个解决方案,所以我会 post 在这里。
有两个步骤:
- 当事物出现时动画化。
- 滚动时显示内容。
第 1 部分: 我找到了这两个适合新手的很棒的教程:
第 2 部分: 我只是在
第 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
- 在 class 的某个地方做:
hasAppeared : boolean = false;
onAppear(){
this.hasAppeared = true;
console.log("I have appeared!"); // This is a good idea for debugging
}
- 最后,在HTML中添加以下两个:
(appear)="onAppear()" *ngIf="hasAppeared"
您可以通过检查控制台中的消息 "I have appeared!" 来检查它是否正常工作。