CSS&JS/⚡Thinkers

[JS] canvas로 벚꽃잎 흩날리기

arancia_ 2026. 3. 26. 16:45

canvas 하나만으로 벚꽃잎을 흩날려야 할 일이 생겼다.
- 벚꽃잎 이미지 없음. (알아서 그려주면 좋겠음)
- 벚꽃잎 사이즈 랜덤, 색상은 주어진 색상들 중에 랜덤이면 좋겠음
- 기왕이면 좀 더 리얼하게 flip 하는 효과가 있음 좋겠음
- 벚꽃잎이 날리는 방향도 초기에 설정할 수 있음 좋겠음. (여기선 우상단 > 좌하단)

Gemini로 프롬프트 넣어서 작성하였고, 재사용 쉽도록 class로 묶었습니다.

See the Pen cherry blossom petals by Oh Ikmyeong (@dpffpself) on CodePen.

HTML,CSS

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
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>벚꽃잎 날리기 정리좀 함</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #f0f0f0;
        }
 
        /* 캔버스는 투명하게, 화면 전체를 채움 */
        #cherry-blossom-canvas {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            /* 클릭 방해 금지 */
        }
    </style>
    <script src="./js/main.js" type="module"></script>
</head>
 
<body>
    <canvas id="cherry-blossom-canvas"></canvas>
</body>
 
</html>
cs

main.js

1
2
3
import { PetalCtrl } from "./PetalCtrl.js";
 
new PetalCtrl().init();
cs

Petal.js

꽃잎을 개별로 그리고 움직이는 클래스

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
 * 벚꽃잎 생성자
 */
export class Petal {
    constructor() {
    }
 
    set_canvas($canvas) {
        this.$canvas = $canvas;
        return this;
    }
 
    set_ctx(ctx) {
        this.ctx = ctx;
        return this;
    }
 
    set_colors(COLORS) {
        this.COLORS = COLORS;
        return this;
    }
 
    init() {
        // 우측 상단 영역에서 시작하도록 설정
        this.x = Math.random() * (this.$canvas.width * 1.5);
        this.y = Math.random() * this.$canvas.height * -1// 화면 위쪽에서 생성
 
        this.size = Math.random() * 5 + 5// 크기 5~10
        this.color = this.COLORS[Math.floor(Math.random() * this.COLORS.length)];
 
        // 이동 속도 (우상단 -> 좌하단)
        this.horizontalSpeed = Math.random() * -2 - 1// 왼쪽으로 이동
        this.verticalSpeed = Math.random() * 2 + 1;   // 아래로 이동
 
        this.opacity = Math.random() * 0.5 + 0.3;
        this.flip = Math.random(); // 팔랑거리는 효과를 위한 변수
        this.flipSpeed = Math.random() * 0.03;
 
        /* 최종 */
        return this;
    }
 
    update() {
        this.x += this.horizontalSpeed;
        this.y += this.verticalSpeed;
        this.flip += this.flipSpeed;
 
        // 화면 밖으로 완전히 벗어나면 다시 생성 (Infinite Loop)
        if (this.y > this.$canvas.height || this.x < -this.size) {
            this.init();
        }
    }
 
    draw() {
        if (this.y < -this.size || this.x > this.$canvas.width + this.size) return;
 
        this.ctx.save();
        this.ctx.beginPath();
        this.ctx.translate(this.x, this.y);
        // 팔랑거리는 회전 효과
        this.ctx.rotate(this.flip);
        this.ctx.scale(Math.sin(this.flip), 1);
 
        // 벚꽃잎 모양 그리기
        this.ctx.fillStyle = this.color;
        this.ctx.globalAlpha = this.opacity;
 
        // 단순 원형이 아닌 타원형으로 잎 표현
        this.ctx.moveTo(00);
        this.ctx.bezierCurveTo(-this.size, -this.size, -this.size, this.size, 0this.size);
        this.ctx.bezierCurveTo(this.size, this.size, this.size, -this.size, 00);
 
        this.ctx.fill();
        this.ctx.restore();
    }
}//class:Petal
 
cs

PetalCtrl.js

캔버스 전체 관장

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import { Petal } from "./Petal.js";
 
export class PetalCtrl {
    constructor() {
        /* DOM */
        this.$canvas = document.getElementById('cherry-blossom-canvas');
        this.ctx = this.$canvas.getContext('2d');
 
        /* 설정 변수 */
        this.TOTAL_PETALS = 50;
        this.COLORS = ['#FFC0CB''#FFB7C5''#FFA1B2'];
        this.petals = [];
 
        /* 타이머 */
        this.resizeTimer = null;
    }
    /**
     * 
     */
    init() {
        /* 리사이즈 */
        this.on_resize();
        window.addEventListener('resize', () => {
            /* 타이머 취소 */
            clearTimeout(this.resizeTimer);
 
            this.resizeTimer = setTimeout(() => {
                this.on_resize();
            }, 300);
        });
 
        /* 꽃잎들을 생성하고 */
        this.create_petals();
 
        /* 움직이기 */
        this.animate();
    }//init
 
    /**
     * 윈도우 크기 변경시
     */
    on_resize = () => {
        this.$canvas.width = window.innerWidth;
        this.$canvas.height = window.innerHeight;
    }//on_resize
 
    /**
     * 초기 벚꽃잎들 생성
     */
    create_petals = () => {
        for (let i = 0; i < this.TOTAL_PETALS; i++) {
            this.petals.push(
                new Petal()
                    .set_colors(this.COLORS)
                    .set_canvas(this.$canvas)
                    .set_ctx(this.ctx)
                    .init()
            );//push
        }//for
    }//create_petals
 
    /**
     * 움직이기 시작
     */
    animate = () => {
        // 배경을 투명하게 유지하기 위해 이전 프레임을 지움
        this.ctx.clearRect(00this.$canvas.width, this.$canvas.height);
 
        this.petals.forEach(petal => {
            petal.update();
            petal.draw();
        });
 
        requestAnimationFrame(this.animate);
    }//animate
}//PetalCtrl
cs