1 | import React from 'react'
|
2 | import PropTypes from 'prop-types'
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | class ConfettiParticle {
|
9 | constructor({ context, width, height, color, speed }) {
|
10 | this.context = context;
|
11 | this.width = width;
|
12 | this.height = height;
|
13 | this.color = color;
|
14 | this.diameter = 0;
|
15 | this.tilt = 0;
|
16 | this.tiltAngleIncrement = 0;
|
17 | this.tiltAngle = 0;
|
18 | this.particleSpeed = speed;
|
19 | this.waveAngle = 0;
|
20 | this.x = 0;
|
21 | this.y = 0;
|
22 | this.reset();
|
23 | }
|
24 |
|
25 | reset() {
|
26 | this.x = Math.random() * this.width;
|
27 | this.y = Math.random() * this.height - this.height;
|
28 | this.diameter = Math.random() * 6 + 4;
|
29 | this.tilt = 0;
|
30 | this.tiltAngleIncrement = Math.random() * 0.1 + 0.04;
|
31 | this.tiltAngle = 0;
|
32 | }
|
33 |
|
34 | update() {
|
35 | this.waveAngle += this.tiltAngleIncrement;
|
36 | this.tiltAngle += this.tiltAngleIncrement;
|
37 | this.tilt = Math.sin(this.tiltAngle) * 12;
|
38 | this.x += Math.sin(this.waveAngle);
|
39 | this.y += (Math.cos(this.waveAngle ) + this.diameter + this.particleSpeed) * 0.4;
|
40 | }
|
41 |
|
42 | complete() {
|
43 | return (this.y > this.height + 20);
|
44 | }
|
45 |
|
46 | draw() {
|
47 | let x = this.x + this.tilt;
|
48 | this.context.beginPath();
|
49 | this.context.lineWidth = this.diameter;
|
50 | this.context.strokeStyle = this.color;
|
51 | this.context.moveTo(x + this.diameter / 2, this.y);
|
52 | this.context.lineTo(x, this.y + this.tilt + this.diameter / 2);
|
53 | this.context.stroke();
|
54 | }
|
55 | }
|
56 |
|
57 | const colorOptions = [
|
58 | '#44D7B6',
|
59 | '#76C2F3',
|
60 | '#F0FF02',
|
61 | '#FEACBE',
|
62 | '#FF511C',
|
63 | '#6236FF',
|
64 | '#0073D1'
|
65 | ]
|
66 |
|
67 | class Confetti extends React.Component {
|
68 | constructor(props) {
|
69 | super(props)
|
70 | this.setCanvasRef = this.setCanvasRef.bind(this)
|
71 | this.animate = this.animate.bind(this)
|
72 | this.setup = this.setup.bind(this)
|
73 | }
|
74 |
|
75 | componentDidMount() {
|
76 | setTimeout(this.setup, 500)
|
77 | }
|
78 |
|
79 | setCanvasRef(canvas) {
|
80 | this.canvas = canvas
|
81 | }
|
82 |
|
83 | componentWillUnmount() {
|
84 | cancelAnimationFrame(this.animationId)
|
85 | }
|
86 |
|
87 | setup() {
|
88 | this.createParticles()
|
89 | this.animate()
|
90 | }
|
91 |
|
92 |
|
93 | createParticles() {
|
94 | const context = this.getContext()
|
95 | const { width, height, particleCount, particleSpeed } = this.props
|
96 |
|
97 | this.particles = [];
|
98 |
|
99 | for (let i = 0; i < particleCount; ++i) {
|
100 | const index = Math.floor(Math.random() * colorOptions.length)
|
101 | const color = colorOptions[index]
|
102 | const particle = new ConfettiParticle({
|
103 | context, width, height, color, speed: particleSpeed
|
104 | })
|
105 | this.particles.push(particle);
|
106 | }
|
107 | }
|
108 |
|
109 | getContext() {
|
110 | return this.canvas.getContext('2d')
|
111 | }
|
112 |
|
113 | animate() {
|
114 | const { width, height, onComplete } = this.props
|
115 | const context = this.getContext()
|
116 | context.clearRect(0, 0, width, height)
|
117 |
|
118 | let complete = true
|
119 | for (let p of this.particles) {
|
120 | p.width = width;
|
121 | p.height = height;
|
122 | p.update();
|
123 | p.draw();
|
124 | complete = complete && p.complete()
|
125 | }
|
126 |
|
127 | if (complete) {
|
128 | onComplete()
|
129 | } else {
|
130 | this.animationId = requestAnimationFrame(this.animate)
|
131 | }
|
132 | }
|
133 |
|
134 | render() {
|
135 | const { width, height } = this.props
|
136 | return (
|
137 | <canvas ref={this.setCanvasRef} width={width} height={height}></canvas>
|
138 | )
|
139 | }
|
140 | }
|
141 |
|
142 | Confetti.propTypes = {
|
143 | width: PropTypes.number.isRequired,
|
144 | height: PropTypes.number.isRequired,
|
145 | particleCount: PropTypes.number,
|
146 | particleSpeed: PropTypes.number,
|
147 | onComplete: PropTypes.func
|
148 | }
|
149 |
|
150 | Confetti.defaultProps = {
|
151 | particleCount: 300,
|
152 | particleSpeed: 1,
|
153 | onComplete: () => null
|
154 | }
|
155 |
|
156 |
|
157 | export default Confetti
|