使用指令在 Angular 8 中制作动画的更好方法
Better way to Animate in Angular 8 using Directives
更新:找到了一个比我自己尝试编码的更好的替代方案。请参阅下面的答案。
我正在使用 Angular 的浏览器动画来制作 Web 应用程序的动画。为了在用户向下滚动页面时为对象制作动画,我想我会创建一个指令,该指令可以调用 DOM 获取像素参考点,并使用 window 服务来确定向下滚动多远用户滚动的页面。一旦用户开始向下滚动页面超过某个像素宽度,我就使用相同的 window 服务淡化我们的导航栏。
我 运行 遇到麻烦的地方是从我的动画指令发出一个事件。我想将这个事件绑定到一个局部变量 在我试图动画化的 individual 元素上
<div [appAnimateLocation]="200" [@slideFromLeft]="vendmfg" (showAnimation)="showAnimation($event, 'vendmfg')" class="container-fluid">
<h6>Content to Animate</h6>
</div>
目前代码通过将一些像素传递给我的指令来工作。该指令然后确定用户何时滚动以满足特定条件(在本例中,div 的顶部比屏幕底部高出 200 像素),然后应该 return 是真还是假。然后一个组件方法被触发并显式地将 属性 设置为 true。当此 属性 为真时,可以触发 slideFromLeft 动画。
我的问题是,有没有一种方法可以在我想要设置动画的每个元素上设置一个局部变量,而不必在组件上设置显式属性?我说的是这样的东西(或者至少会像这样起作用的东西)
<div [appAnimateLocation]="200" [@slideFromLeft]="var" (showAnimation)="let var as $event" class="container-fluid">
<h6>Content to Animate</h6>
</div>
这可能吗?或者有没有更好的方法来做到这一点我忽略了?这样我就可以重复 div 的动画部分,而不必执行以下操作(我目前正在做的):
<div [appAnimateLocation]="200" [@slideFromLeft]="vendmfg" (showAnimation)="showAnimation($event, 'vendmfg')" class="container-fluid">
<h6>Content to Animate</h6>
</div>
<div [appAnimateLocation]="200" [@slideFromLeft]="anotherVar" (showAnimation)="showAnimation($event, 'anotherVar')" class="container-fluid">
<h6>More Content to Animate</h6>
</div>
我的文件如下:
animations.directive.ts
import {Directive, ElementRef, EventEmitter, HostListener, Inject, Input, Output} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {WINDOW} from './window.service';
@Directive({
selector: '[appAnimateLocation]'
})
export class AppAnimateLocation {
@Output() showAnimation = new EventEmitter();
constructor(
private el: ElementRef,
@Inject(DOCUMENT) private document: Document,
@Inject(WINDOW) private window: Window
){}
@Input('appAnimateLocation') offset: number;
@HostListener('window:scroll', []) onWindowScroll() {
const screen = window.screen.availHeight;
const scrollPosition = window.pageYOffset;
const componentPosition = this.el.nativeElement.offsetTop;
if (screen + scrollPosition >= componentPosition + this.offset) {
this.showAnimation.emit(true);
} else {
this.showAnimation.emit(false);
}
}
}
animation.ts
import {animate, state, style, transition, trigger} from '@angular/animations';
export let fade =
trigger('fade', [
state('void', style({opacity: 0})),
state('false', style({opacity: 0})),
transition(':enter', [
animate(1500)
]),
transition('false => true', [
animate(1500)
])
]);
windows.service
import { isPlatformBrowser } from '@angular/common';
import { ClassProvider, FactoryProvider, InjectionToken, PLATFORM_ID } from '@angular/core';
/* Create a new injection token for injecting the window into a component. */
export const WINDOW = new InjectionToken('WindowToken');
/* Define abstract class for obtaining reference to the global window object. */
export abstract class WindowRef {
get nativeWindow(): Window | Object {
throw new Error('Not implemented.');
}
}
/* Define class that implements the abstract class and returns the native window object. */
export class BrowserWindowRef extends WindowRef {
constructor() {
super();
}
get nativeWindow(): Window | Object {
return window;
}
}
/* Create an factory function that returns the native window object. */
export function windowFactory(browserWindowRef: BrowserWindowRef, platformId: Object): Window | Object {
if (isPlatformBrowser(platformId)) {
return browserWindowRef.nativeWindow;
}
return new Object();
}
/* Create a injectable provider for the WindowRef token that uses the BrowserWindowRef class. */
export const browserWindowProvider: ClassProvider = {
provide: WindowRef,
useClass: BrowserWindowRef
};
/* Create an injectable provider that uses the windowFactory function for returning the native window object. */
export const windowProvider: FactoryProvider = {
provide: WINDOW,
useFactory: windowFactory,
deps: [ WindowRef, PLATFORM_ID ]
};
/* Create an array of providers. */
export const WINDOW_PROVIDERS = [
browserWindowProvider,
windowProvider
];
最后是我的组件
import {Component, OnInit} from '@angular/core';
import {Router} from '@angular/router';
import {fade, slideFromLeft, slideFromRight} from '../animations';
@Component({
selector: 'app-home-page',
templateUrl: './home-page.component.html',
styleUrls: ['./home-page.component.css'],
animations: [
fade,
slideFromRight,
slideFromLeft,
]
})
export class HomePageComponent implements OnInit {
constructor(
private router: Router
) { }
vendmfg = false;
ngOnInit() {
}
showAnimation(event, el: string){
if(event === true) {
if (el === 'vendmfg') {
this.vendmfg = true;
}
}
}
}
非常感谢任何帮助或建议。
当我继续研究这个问题,并试图弄清楚如何在向上滚动页面时设置动画,我看到了 this stackblitz by Lucio Francisco which does what I was attempting to do in a way more concise manner. Explained by this Medium 文章。希望对动画新手有所帮助。
更新:找到了一个比我自己尝试编码的更好的替代方案。请参阅下面的答案。
我正在使用 Angular 的浏览器动画来制作 Web 应用程序的动画。为了在用户向下滚动页面时为对象制作动画,我想我会创建一个指令,该指令可以调用 DOM 获取像素参考点,并使用 window 服务来确定向下滚动多远用户滚动的页面。一旦用户开始向下滚动页面超过某个像素宽度,我就使用相同的 window 服务淡化我们的导航栏。
我 运行 遇到麻烦的地方是从我的动画指令发出一个事件。我想将这个事件绑定到一个局部变量 在我试图动画化的 individual 元素上
<div [appAnimateLocation]="200" [@slideFromLeft]="vendmfg" (showAnimation)="showAnimation($event, 'vendmfg')" class="container-fluid">
<h6>Content to Animate</h6>
</div>
目前代码通过将一些像素传递给我的指令来工作。该指令然后确定用户何时滚动以满足特定条件(在本例中,div 的顶部比屏幕底部高出 200 像素),然后应该 return 是真还是假。然后一个组件方法被触发并显式地将 属性 设置为 true。当此 属性 为真时,可以触发 slideFromLeft 动画。
我的问题是,有没有一种方法可以在我想要设置动画的每个元素上设置一个局部变量,而不必在组件上设置显式属性?我说的是这样的东西(或者至少会像这样起作用的东西)
<div [appAnimateLocation]="200" [@slideFromLeft]="var" (showAnimation)="let var as $event" class="container-fluid">
<h6>Content to Animate</h6>
</div>
这可能吗?或者有没有更好的方法来做到这一点我忽略了?这样我就可以重复 div 的动画部分,而不必执行以下操作(我目前正在做的):
<div [appAnimateLocation]="200" [@slideFromLeft]="vendmfg" (showAnimation)="showAnimation($event, 'vendmfg')" class="container-fluid">
<h6>Content to Animate</h6>
</div>
<div [appAnimateLocation]="200" [@slideFromLeft]="anotherVar" (showAnimation)="showAnimation($event, 'anotherVar')" class="container-fluid">
<h6>More Content to Animate</h6>
</div>
我的文件如下: animations.directive.ts
import {Directive, ElementRef, EventEmitter, HostListener, Inject, Input, Output} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {WINDOW} from './window.service';
@Directive({
selector: '[appAnimateLocation]'
})
export class AppAnimateLocation {
@Output() showAnimation = new EventEmitter();
constructor(
private el: ElementRef,
@Inject(DOCUMENT) private document: Document,
@Inject(WINDOW) private window: Window
){}
@Input('appAnimateLocation') offset: number;
@HostListener('window:scroll', []) onWindowScroll() {
const screen = window.screen.availHeight;
const scrollPosition = window.pageYOffset;
const componentPosition = this.el.nativeElement.offsetTop;
if (screen + scrollPosition >= componentPosition + this.offset) {
this.showAnimation.emit(true);
} else {
this.showAnimation.emit(false);
}
}
}
animation.ts
import {animate, state, style, transition, trigger} from '@angular/animations';
export let fade =
trigger('fade', [
state('void', style({opacity: 0})),
state('false', style({opacity: 0})),
transition(':enter', [
animate(1500)
]),
transition('false => true', [
animate(1500)
])
]);
windows.service
import { isPlatformBrowser } from '@angular/common';
import { ClassProvider, FactoryProvider, InjectionToken, PLATFORM_ID } from '@angular/core';
/* Create a new injection token for injecting the window into a component. */
export const WINDOW = new InjectionToken('WindowToken');
/* Define abstract class for obtaining reference to the global window object. */
export abstract class WindowRef {
get nativeWindow(): Window | Object {
throw new Error('Not implemented.');
}
}
/* Define class that implements the abstract class and returns the native window object. */
export class BrowserWindowRef extends WindowRef {
constructor() {
super();
}
get nativeWindow(): Window | Object {
return window;
}
}
/* Create an factory function that returns the native window object. */
export function windowFactory(browserWindowRef: BrowserWindowRef, platformId: Object): Window | Object {
if (isPlatformBrowser(platformId)) {
return browserWindowRef.nativeWindow;
}
return new Object();
}
/* Create a injectable provider for the WindowRef token that uses the BrowserWindowRef class. */
export const browserWindowProvider: ClassProvider = {
provide: WindowRef,
useClass: BrowserWindowRef
};
/* Create an injectable provider that uses the windowFactory function for returning the native window object. */
export const windowProvider: FactoryProvider = {
provide: WINDOW,
useFactory: windowFactory,
deps: [ WindowRef, PLATFORM_ID ]
};
/* Create an array of providers. */
export const WINDOW_PROVIDERS = [
browserWindowProvider,
windowProvider
];
最后是我的组件
import {Component, OnInit} from '@angular/core';
import {Router} from '@angular/router';
import {fade, slideFromLeft, slideFromRight} from '../animations';
@Component({
selector: 'app-home-page',
templateUrl: './home-page.component.html',
styleUrls: ['./home-page.component.css'],
animations: [
fade,
slideFromRight,
slideFromLeft,
]
})
export class HomePageComponent implements OnInit {
constructor(
private router: Router
) { }
vendmfg = false;
ngOnInit() {
}
showAnimation(event, el: string){
if(event === true) {
if (el === 'vendmfg') {
this.vendmfg = true;
}
}
}
}
非常感谢任何帮助或建议。
当我继续研究这个问题,并试图弄清楚如何在向上滚动页面时设置动画,我看到了 this stackblitz by Lucio Francisco which does what I was attempting to do in a way more concise manner. Explained by this Medium 文章。希望对动画新手有所帮助。