Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- 호버
- 앵귤러 모달
- prisma
- Oracle LISTAGG 사용 예시
- flex-1
- egov spring ajax 사용 예시
- route
- Angular Router
- angular button
- angular route
- Ionic modal
- scroll
- 옵저버블
- TAILWIND
- angular modal
- 셀렉트박스 커스텀
- angular animation
- mysql if
- 스크롤 이벤트
- summary
- 스크롤 이벤트 감지
- 아이오닉 스크롤 이벤트
- 앵귤러 애니메이션
- modal
- Router
- ApexChart
- formgroup
- 검색
- 모달
- ajax 사용 예시
Archives
- Today
- Total
깜놀하는 해므찌로
Angular Stagger Animation 앵귤러 스태거 애니메이션 예시 / 한줄씩 등장하는 애니메이션 본문
반응형
SMALL
ng generate directive MyDirective
0. 디렉티브 생성 cli
import { AnimationPlayer } from '@angular/animations';
import {
coerceBooleanProperty,
coerceNumberProperty,
} from '@angular/cdk/coercion';
import {
AfterContentInit,
AfterViewInit,
Directive,
ElementRef,
Input,
Renderer2,
} from '@angular/core';
import {
ScrollAnimation,
ScrollAnimationService,
} from './scroll-animation.service';
@Directive({
selector: '[scrollAnimation]',
standalone: true,
})
export class ScrollAnimationDirective
implements AfterContentInit, AfterViewInit
{
@Input() scrollAnimation: ScrollAnimation = 'fadeIn';
@Input()
get delay() {
return this._delay;
}
set delay(value: any) {
this._delay = coerceNumberProperty(value);
}
private _delay: number = 0;
@Input()
get stagger() {
return this._stagger;
}
set stagger(value: any) {
this._stagger = coerceBooleanProperty(value);
}
private _stagger: boolean = false; // 애니메이션에 stagger 효과를 넣을지 여부
private intersectionObserver: IntersectionObserver; // 요소의 가시성 감지
private intersectingCache: boolean = false; // 요소가 현재 가시 상태인지 나타내는 값
private animationPlayer: AnimationPlayer | undefined; // 애니메이션 제어
constructor(
private elementRef: ElementRef,
private renderer2: Renderer2,
private scrollAnimationService: ScrollAnimationService
) {
const intersectionObserverInit: IntersectionObserverInit = {
rootMargin: '0px 0px -40% 0px',
};
this.intersectionObserver = new IntersectionObserver(
(entries) => this.onIntersection(entries[0]),
intersectionObserverInit
);
}
ngAfterContentInit() {
// 애니메이션 효과 빌드
const properties = this.scrollAnimationService.build(
this.scrollAnimation
)?.properties;
const animation = this.scrollAnimationService.build(
this.scrollAnimation
)?.animation;
// 스타일 설정
if (properties && animation) {
this.setStyles(properties);
this.animationPlayer = animation.create(this.elementRef.nativeElement);
}
}
ngAfterViewInit() {
// 옵저버블을 활용하여 요소를 관찰
this.intersectionObserver.observe(this.elementRef.nativeElement);
}
// stagger 유무 적용
setStyles(properties: any) {
if (!this.stagger) {
return this.renderer2.setStyle(
this.elementRef.nativeElement,
'opacity',
0
);
}
for (const style in properties) {
this.renderer2.setStyle(
this.elementRef.nativeElement,
style,
properties[style]
);
}
}
// onIntersection : 메서드는 Intersection Observer의 콜백 함수로서, 요소의 가시성이 변경될 때 호출
onIntersection(entry: IntersectionObserverEntry) {
// intersectingCache 값이 true인 경우, 즉 이미 가시 상태인 경우 함수를 종료합니다.
if (this.intersectingCache) {
return;
}
// intersectingCache 값이 entry.isIntersecting 값과 동일한 경우에도 함수를 종료합니다. 이는 가시 상태가 변경되지 않았음을 의미합니다.
if (this.intersectingCache == entry.isIntersecting) {
return;
}
// intersectingCache 값을 entry.isIntersecting 값으로 설정합니다. 이는 가시 상태가 변경되었음을 나타냅니다.
this.intersectingCache = entry.isIntersecting;
// intersectingCache 값이 false인 경우, 즉 요소가 보이지 않는 경우 함수를 종료합니다.
if (!this.intersectingCache) {
return;
}
// stagger 값이 true인 경우, renderer2를 사용하여 요소의 투명도(opacity)를 1로 설정합니다. 이는 스태거 효과를 적용하는 부분입니다.
if (this.stagger) {
this.renderer2.setStyle(this.elementRef.nativeElement, 'opacity', 1);
}
// setTimeout 함수를 사용하여 animationPlayer가 존재하는 경우 지정된 delay 시간 후에 애니메이션을 실행합니다.
setTimeout(() => this.animationPlayer?.play(), this.delay);
// intersectionObserver의 관찰을 중지합니다. 이는 애니메이션이 한 번만 실행되도록 하기 위해 관찰을 중단하는 것입니다.
this.intersectionObserver.disconnect();
// 요약하면, onIntersection 메서드는 요소의 가시성이 변경되면 해당 요소에 대한 스태거 효과를 적용하고,
// 지정된 지연 시간 후에 애니메이션을 실행합니다. 또한, 애니메이션이 실행된 후에는 관찰을 중지하여 중복 실행을 방지합니다.
}
}
1. directive 내부 (상세 설명은 코드 내에 직접 적었습니다.)
ng generate service <name> [options]
2. 서비스 생성 cli
import { AnimationBuilder, AnimationFactory } from '@angular/animations';
import { Injectable } from '@angular/core';
import {
fromIntroBottom,
fromIntroBottomStagger,
fromIntroLeftStagger,
fromIntroRight,
fromIntroRightStagger,
fromIntroTop,
fromIntroTopStagger,
fromIntroleft,
toIntroBottom,
toIntroLeft,
toIntroRight,
toIntroTop,
} from './intro.animations';
type Result = {
properties: Object;
animation: AnimationFactory;
};
export type ScrollAnimation =
| 'fadeIn'
| 'fadeOut'
| 'introTop'
| 'introRight'
| 'introBottom'
| 'introLeft'
| 'introBottomStagger'
| 'introLeftStagger'
| 'introRightStagger'
| 'introTopStagger'
| 'focusIn';
@Injectable({
providedIn: 'root',
})
export class ScrollAnimationService {
constructor(private animationBuilder: AnimationBuilder) {}
build(scrollAnimation: ScrollAnimation): Result | null {
if (scrollAnimation === 'introBottomStagger') { // stagger : 하나씩 요소 나타내는 애니메이션
return {
properties: toIntroBottom,
animation: this.animationBuilder.build(fromIntroBottomStagger),
};
} else if (scrollAnimation === 'introLeft') {
return {
properties: toIntroLeft,
animation: this.animationBuilder.build(fromIntroleft),
};
} else if (scrollAnimation === 'introLeftStagger') { // stagger
return {
properties: toIntroLeft,
animation: this.animationBuilder.build(fromIntroLeftStagger),
};
}
} else {
return null;
}
}
}
3. service 내부 (굳이 설명이 필요하지 않아 상세 설명 생략)
import { animate, keyframes, query, stagger, style } from '@angular/animations';
export const toIntroTop = {
transform: 'translateY(-50px)',
opacity: 0,
};
export const fromIntroTop = [
animate(
'1s cubic-bezier(0.250, 0.460, 0.450, 0.940)',
keyframes([
style({
transform: 'translateY(-50px)',
opacity: '0',
offset: 0,
}),
style({
transform: 'translateY(0)',
opacity: '1',
offset: 1.0,
}),
])
),
];
export const toIntroRight = {
transform: 'translateX(50px)',
opacity: 0,
};
export const fromIntroRight = [
animate(
'1s cubic-bezier(0.250, 0.460, 0.450, 0.940)',
keyframes([
style({
transform: 'translateX(50px)',
opacity: 0,
offset: 0,
}),
style({
transform: 'translateX(0)',
opacity: 1,
offset: 1.0,
}),
])
),
];
export const toIntroBottom = {
transform: 'translateY(0px)',
opacity: 0,
};
export const fromIntroBottom = [
animate(
'1s cubic-bezier(0.250, 0.460, 0.450, 0.940)',
keyframes([
style({
transform: 'translateY(50px)',
opacity: 0,
offset: 0,
}),
style({
transform: 'translateY(0)',
opacity: 1,
offset: 1.0,
}),
])
),
];
export const toIntroLeft = {
transform: 'translateX(-50px)',
opacity: 0,
};
export const fromIntroleft = [
animate(
'1s cubic-bezier(0.250, 0.460, 0.450, 0.940)',
keyframes([
style({
transform: 'translateX(-50px)',
opacity: 0,
offset: 0,
}),
style({
transform: 'translateX(0)',
opacity: 1,
offset: 1.0,
}),
])
),
];
export const fromIntroBottomStagger = [
query('.animation-item', [
style({
transform: 'translateY(50px)',
opacity: 0,
}),
stagger(200, [
animate(
'1s cubic-bezier(0.250, 0.460, 0.450, 0.940)',
style({
transform: 'translateY(0)',
opacity: 1,
})
),
]),
]),
];
export const fromIntroLeftStagger = [
query('.animation-item', [
style({
transform: 'translateX(-50px)',
opacity: '0',
}),
stagger(200, [
animate(
'1s cubic-bezier(0.250, 0.460, 0.450, 0.940)',
style({
transform: 'translateX(0)',
opacity: '1',
})
),
]),
]),
];
export const fromIntroRightStagger = [
query('.animation-item', [
style({
transform: 'translateX(50px)',
opacity: '0',
}),
stagger(200, [
animate(
'1s cubic-bezier(0.250, 0.460, 0.450, 0.940)',
style({
transform: 'translateX(0)',
opacity: '1',
})
),
]),
]),
];
export const fromIntroTopStagger = [
query('.animation-item', [
style({
transform: 'translateY(-50px)',
opacity: '0',
}),
stagger(200, [
animate(
'1s cubic-bezier(0.250, 0.460, 0.450, 0.940)',
style({
transform: 'translateY(0)',
opacity: '1',
})
),
]),
]),
];
export const fadeIn = [
style({
opacity: 1,
}),
animate(
'2000ms cubic-bezier(0.4, 0.0, 0.2, 1)',
style({
opacity: 0,
})
),
];
4. intro.animations.ts 내부 (opacity 는 투명도입니다. fade 효과를 준 것이겠죠?)
import { ScrollAnimationDirective } from '/animations/scroll-animation/scroll-animation.directive';
imports: [ScrollAnimationDirective],
5. 생성한 애니메이션을 실제로 적용하는 컴포넌트 내부 (심볼 로드)
<div
scrollAnimation="introBottomStagger"
stagger
>
<div class="animation-item">자식태그1</div>
<div class="animation-item">자식태그2</div>
<div class="animation-item">자식태그3</div>
</div>
6. 애니메이션에서 선언한 대로 부모요소에 scrollAnimation 디렉티브에 사용할 애니메이션 이름을 할당
7/ stagger === [stagger]="true" 스태거 사용 여부를 결정
8. 자식요소에 animation-item 클래스 명을 추가
9 자식태그1~3까지 순서대로 촤르르 애니메이션이 작동하게 됩니다.
반응형
LIST
'IT' 카테고리의 다른 글
Angular Swiper CSS is not working 앵귤러 스와이퍼 CSS 먹통 문제 해결 방안 예시 (0) | 2023.07.20 |
---|---|
Angular Template async 활용 / Observable 옵저버블 일반 타입 변환 예시 (0) | 2023.07.19 |
Observable Http통신 배열로 받는 법 예시 / async, await, error (0) | 2023.07.17 |
Ionic Angular google map API 앵귤러 구글 맵 API 활용 예시 (0) | 2023.07.16 |
RXJS Observable 구독 중에 구독 취소하기 / 옵저버블 강제 구독 해제 (0) | 2023.07.15 |