Angular의 Twitter 스타일 생일 풍선
이 글을 쓰는 시점에서 OpenAI의 GPT4가 최근에 출시되었으며 잠재적으로 이를 쉽게 구현할 수 있습니다. 그럼에도 불구하고 스스로 작업을 수행하고 싶다면 계속 읽어도 좋습니다. 실제로 스스로 작업을 수행하는 것에 대해 만족스러운 것이 있습니다.
약 1년 전, 저는 회사의 애플리케이션에 생일 풍선을 추가할 생각을 했습니다. 나는 영감을 찾기 시작했고 CodePen에서 이 순수한 html/javascript 구현을 발견했습니다. 이것은 실제 풍선의 모양과 그림자에 큰 영감을 주었지만, 그것들을 렌더링하기 위한 논리는 (적어도 나에게는) Angular로 바로 이식하기가 조금 더 어려웠습니다.
이 게시물에서는 Angular CLI만으로 처음부터 시작하여 몇 가지 터지는 작업으로 끝나는 Angular Material CDK의 오버레이를 사용하여 사용자 지정 가능한 생일 풍선을 만드는 방법을 보여줍니다.
아이디어는 다음과 같습니다.
- 사용자 지정 지시문을 사용하여 조건부로 "풍선 컨테이너"를 오버레이로 렌더링합니다.
- 이 컨테이너 내부의 풍선에 애니메이션을 적용합니다.
- 추가 재미: "터지는" 풍선을 추가하여 더욱 축제 분위기를 연출하세요."
Angular CLI를 사용하여 "AngularBalloons"라는 앱을 만드는 것으로 시작합니다.
ng n AngularBalloons
먼저 렌더링하려는 모든 풍선을 포함하는 컨테이너를 만듭니다.
ng g c BalloonContainer. 추적을 위한 ID 속성(나중에 유용함)과 화면에서 애니메이션 속도 및 배치를 무작위화하기 위한 스타일이 있는 풍선 인터페이스를 추가합니다. 또한 이러한 사용자 지정 스타일을 생성하기 위해 몇 가지 도우미 메서드를 추가합니다.
// balloon-container.component.ts
import { Component } from '@angular/core';
import { BalloonComponent } from '../balloon/balloon.component';
export interface Balloon {
id: number;
style: string;
}
export function getRandomInt(max: number) {
return Math.floor(Math.random() * max);
}
// Helper function to set animation duration and some offset
function getRandomStyles() {
return `margin: ${getRandomInt(200)}px 0 0 ${getRandomInt(50)}px;
animation-duration: ${getRandomInt(10) + 7}s`;
}
@Component({
selector: 'app-balloon-container',
standalone: true,
template: `
<app-balloon
*ngFor="let balloon of balloons"
[balloon]="balloon">
</app-balloon>
`,
styleUrls: ['./balloon-container.component.scss'],
imports: [BalloonComponent],
})
export class BalloonContainerComponent {
readonly nBalloons = 20;
balloons: Balloon[] = [...new Array(this.nBalloons)].map((_, idx) => ({
id: idx,
style: getRandomStyles(),
}));
}
// balloon.component.ts
/* imports */
@Component({
selector: 'app-balloon',
standalone: true,
template: `
<div
*ngIf="balloon"
class="c-balloon"
[style]="balloon.style"
[style.left]="position">
</div>`,
styleUrls: ['./balloon.component.scss'],
})
export class BalloonComponent {
@Input()
balloon?: Balloon;
position: string;
constructor() {
// place the balloon a random place covering the screen width
this.position = `${getRandomInt(100)}vw`;
}
}
// balloon.component.scss
.c-balloon {
position: absolute;
width: 105px;
height: 125px;
pointer-events: auto;
border-radius: 75% 75% 70% 70%;
animation-name: float;
animation-timing-function: ease-in;
animation-iteration-count: infinite;
// You can take color as an input to your balloon as you please
color: blue;
background-color: blue;
box-shadow: inset -7px -3px 10px darkblue;
&::before {
position: absolute;
top: 125px;
right: 0;
left: 0;
display: block;
width: 1px;
height: 75px;
margin: auto;
content: "";
}
&::after {
position: relative;
top: 118px;
right: 0;
left: 0;
display: block;
margin: auto;
color: inherit;
text-align: center;
content: "▲";
}
}
@keyframes float {
from {
opacity: 0.7;
transform: translateY(100vh);
}
to {
opacity: 0.2;
transform: translateY(-100vh);
}
}
그림에서 눈치채지 못한 경우 풍선이 다른 요소 뒤에 렌더링될 수 있습니다(html에서의 배치로 인해). Angular cdk의 오버레이를 사용하여 문제를 해결할 수 있습니다.
풍선 오버레이 지시문
모든 html 요소를 전달할 수 있는 지시문을 추가해 보겠습니다 appBalloons=”conditionToRenderOrNot”. 먼저 cdk: 를 추가해야 합니다 ng add @angular/cdk. 이 구현은 rxjs와의 일부 반응성도 사용합니다.
// balloon.directive.ts
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { Directive, Input, OnDestroy, OnInit } from '@angular/core';
import { BehaviorSubject, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { BalloonContainerComponent } from './balloon-container/balloon-container.component';
@Directive({
selector: '[appBalloon]',
})
export class BalloonDirective implements OnInit, OnDestroy {
private _overlayRef?: OverlayRef;
private _onDestroy$ = new Subject<void>();
private _shouldShow$ = new BehaviorSubject<boolean>(false);
constructor(private _overlay: Overlay) {}
@Input()
set appBalloon(value: boolean) {
this._shouldShow$.next(value);
}
ngOnInit(): void {
this._shouldShow$
.pipe(takeUntil(this._onDestroy$))
.subscribe((shouldShow) => {
if (shouldShow) {
this._show();
} else {
this._hide();
}
});
}
ngOnDestroy(): void {
this._hide();
this._onDestroy$.next();
this._onDestroy$.complete();
}
private _isVisible(): boolean {
return this._overlayRef !== undefined && this._overlayRef.hasAttached();
}
private _show(): void {
if (this._isVisible()) {
return;
}
this._overlayRef = this._overlay.create({
panelClass: 'o-overlay--no-pointer-events',
positionStrategy: this._overlay.position().global().bottom(),
width: '100vw',
height: '100vh',
});
const balloonPortal = new ComponentPortal(BalloonContainerComponent);
this._overlayRef.attach(balloonPortal);
}
private _hide(): void {
if (!this._isVisible()) {
return;
}
this._overlayRef?.detach();
}
}
앱에 간단한 토글을 추가하고 새 지시문을 사용하겠습니다! 매우 간단한 애플리케이션(balloon-container 지시문을 app.module로 가져와야 함):
// app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h1 [appBalloon]="showBalloons">Balloons!</h1>
<input type="checkbox" (change)="onChange()" />`
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
showBalloons = false;
onChange(): void {
this.showBalloons = !this.showBalloons;
}
}
이제 Angular에서 떠다니는 풍선을 조건부로 표시하기 위해 원하는 논리를 사용할 수 있습니다!
이 방법은 당신이 원하는 어떤 멋진 장면에도 사용할 수 있다는 것을 깨닫기를 바랍니다! 생일 풍선뿐만 아니라 겨울의 눈, 가을의 낙엽 등 계절별 테마도 있습니다.
기타: 풍선 터뜨리기
이제 한 단계 더 나아가 풍선을 터뜨릴 수 있으면 좋을 것입니다.
balloon-component의 — 이벤트 에 바인딩을 추가 click하고 balloon-container로 방출하여 풍선을 클릭 가능하게 만들 것입니다. 그러나 컨테이너는 제거할 풍선을 어떻게 알 수 있습니까? 여기에서 ID가 유용합니다.
// balloon.component.ts
@Component({
selector: 'app-balloon',
standalone: true,
imports: [NgIf],
template: ` <div
*ngIf="balloon"
class="c-balloon"typ
[style]="balloon.style"
[style.left]="position"
(click)="onClick()"
></div>`,
styleUrls: ['./balloon.component.scss'],
})
export class BalloonComponent {
@Input()
balloon?: Balloon;
@Output()
pop = new EventEmitter<number>();
position: string;
constructor() {
this.position = `${getRandomInt(100)}vw`;
}
onClick() {
if (this.balloon) {
this.pop.emit(this.balloon.id);
}
}
}
// balloon-container.component.ts
@Component({
selector: 'app-balloon-container',
standalone: true,
template: `
<app-balloon
*ngFor="let balloon of balloons"
[balloon]="balloon">
(pop)="onPop($event)"
</app-balloon>
`,
imports: [BalloonComponent],
})
export class BalloonContainerComponent {
readonly nBalloons = 20;
balloons: Balloon[] = [...new Array(this.nBalloons)].map((_, idx) => ({
id: idx,
style: getRandomStyles(),
}));
onPop(id: number) {
this.balloons = this.balloons.filter((b) => b.id !== id);
}
}
이제 이를 앱, 회사 등으로 가져올 수 있으며 Angular 에서 풍선으로 기쁨을 더할 수 있습니다.
이 게시물이 영감을 주거나 적어도 다소 재미있었기를 바랍니다. 의견이 있으시면 언제든지 문의해 주세요.

![연결된 목록이란 무엇입니까? [1 부]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































