@@ -3,14 +3,14 @@ import { createNoise3D } from 'simplex-noise'
3
3
import { cn } from ' @/lib/utils'
4
4
5
5
const props = withDefaults (defineProps <VortexProps >(), {
6
- particleCount: 700 ,
7
- rangeY: 100 ,
8
- baseSpeed: 0.0 ,
9
- rangeSpeed: 1.5 ,
10
- baseRadius: 1 ,
11
- rangeRadius: 2 ,
12
- baseHue: 220 ,
13
- backgroundColor: ' transparent' ,
6
+ particleCount: 700 ,
7
+ rangeY: 100 ,
8
+ baseSpeed: 0.0 ,
9
+ rangeSpeed: 1.5 ,
10
+ baseRadius: 1 ,
11
+ rangeRadius: 2 ,
12
+ baseHue: 220 ,
13
+ backgroundColor: ' transparent' ,
14
14
})
15
15
// All constants
16
16
const TAU = 2 * Math .PI
@@ -25,16 +25,16 @@ const zOff = 0.0005
25
25
let tick = 0
26
26
27
27
interface VortexProps {
28
- class? : string
29
- containerClass? : string
30
- particleCount? : number
31
- rangeY? : number
32
- baseHue? : number
33
- baseSpeed? : number
34
- rangeSpeed? : number
35
- baseRadius? : number
36
- rangeRadius? : number
37
- backgroundColor? : string
28
+ class? : string
29
+ containerClass? : string
30
+ particleCount? : number
31
+ rangeY? : number
32
+ baseHue? : number
33
+ baseSpeed? : number
34
+ rangeSpeed? : number
35
+ baseRadius? : number
36
+ rangeRadius? : number
37
+ backgroundColor? : string
38
38
}
39
39
40
40
const canvasRef = useTemplateRef <HTMLCanvasElement | null >(' canvasRef' )
@@ -47,196 +47,198 @@ let particleProps = new Float32Array(particlePropsLength)
47
47
const center: [number , number ] = [0 , 0 ]
48
48
49
49
function rand(n : number ): number {
50
- return n * Math .random ()
50
+ return n * Math .random ()
51
51
}
52
52
53
53
function randRange(n : number ): number {
54
- return n - rand (2 * n )
54
+ return n - rand (2 * n )
55
55
}
56
56
57
57
function fadeInOut(t : number , m : number ): number {
58
- const hm = 0.5 * m
59
- return Math .abs (((t + hm ) % m ) - hm ) / hm
58
+ const hm = 0.5 * m
59
+ return Math .abs (((t + hm ) % m ) - hm ) / hm
60
60
}
61
61
62
62
function lerp(n1 : number , n2 : number , speed : number ): number {
63
- return (1 - speed ) * n1 + speed * n2
63
+ return (1 - speed ) * n1 + speed * n2
64
64
}
65
65
66
66
function setup() {
67
- const canvas = canvasRef .value
68
- const container = containerRef .value
69
- if (canvas && container ) {
70
- const ctx = canvas .getContext (' 2d' )
71
- if (ctx ) {
72
- resize (canvas , ctx )
73
- initParticles ()
74
- draw (canvas , ctx )
75
- }
67
+ const canvas = canvasRef .value
68
+ const container = containerRef .value
69
+ if (canvas && container ) {
70
+ const ctx = canvas .getContext (' 2d' )
71
+ if (ctx ) {
72
+ resize (canvas , ctx )
73
+ initParticles ()
74
+ draw (canvas , ctx )
76
75
}
76
+ }
77
77
}
78
78
79
79
function initParticles() {
80
- tick = 0
81
- particleProps = new Float32Array (particlePropsLength )
82
- for (let i = 0 ; i < particlePropsLength ; i += particlePropCount ) {
83
- initParticle (i )
84
- }
80
+ tick = 0
81
+ particleProps = new Float32Array (particlePropsLength )
82
+ for (let i = 0 ; i < particlePropsLength ; i += particlePropCount ) {
83
+ initParticle (i )
84
+ }
85
85
}
86
86
87
87
function initParticle(i : number ) {
88
- const canvas = canvasRef .value
89
- if (! canvas )
90
- return
88
+ const canvas = canvasRef .value
89
+ if (! canvas )
90
+ return
91
91
92
- const x = rand (canvas .width )
93
- const y = center [1 ] + randRange (props .rangeY )
94
- const vx = 0
95
- const vy = 0
96
- const life = 0
97
- const ttl = baseTTL + rand (rangeTTL )
98
- const speed = props .baseSpeed + rand (props .rangeSpeed )
99
- const radius = props .baseRadius + rand (props .rangeRadius )
100
- const hue = props .baseHue + rand (rangeHue )
92
+ const x = rand (canvas .width )
93
+ const y = center [1 ] + randRange (props .rangeY )
94
+ const vx = 0
95
+ const vy = 0
96
+ const life = 0
97
+ const ttl = baseTTL + rand (rangeTTL )
98
+ const speed = props .baseSpeed + rand (props .rangeSpeed )
99
+ const radius = props .baseRadius + rand (props .rangeRadius )
100
+ const hue = props .baseHue + rand (rangeHue )
101
101
102
- particleProps .set ([x , y , vx , vy , life , ttl , speed , radius , hue ], i )
102
+ particleProps .set ([x , y , vx , vy , life , ttl , speed , radius , hue ], i )
103
103
}
104
104
105
105
function draw(canvas : HTMLCanvasElement , ctx : CanvasRenderingContext2D ) {
106
- tick ++
107
- ctx .clearRect (0 , 0 , canvas .width , canvas .height )
106
+ tick ++
107
+ ctx .clearRect (0 , 0 , canvas .width , canvas .height )
108
108
109
- ctx .fillStyle = props .backgroundColor
110
- ctx .fillRect (0 , 0 , canvas .width , canvas .height )
109
+ ctx .fillStyle = props .backgroundColor
110
+ ctx .fillRect (0 , 0 , canvas .width , canvas .height )
111
111
112
- drawParticles (ctx )
113
- renderGlow (canvas , ctx )
114
- renderToScreen (canvas , ctx )
112
+ drawParticles (ctx )
113
+ renderGlow (canvas , ctx )
114
+ renderToScreen (canvas , ctx )
115
115
116
- requestAnimationFrame (() => draw (canvas , ctx ))
116
+ requestAnimationFrame (() => draw (canvas , ctx ))
117
117
}
118
118
119
119
function drawParticles(ctx : CanvasRenderingContext2D ) {
120
- for (let i = 0 ; i < particlePropsLength ; i += particlePropCount ) {
121
- updateParticle (i , ctx )
122
- }
120
+ for (let i = 0 ; i < particlePropsLength ; i += particlePropCount ) {
121
+ updateParticle (i , ctx )
122
+ }
123
123
}
124
124
125
125
function updateParticle(i : number , ctx : CanvasRenderingContext2D ) {
126
- const canvas = canvasRef .value
127
- if (! canvas )
128
- return
129
-
130
- const [x, y, vx, vy, life, ttl, speed, radius, hue] = [
131
- particleProps [i ],
132
- particleProps [i + 1 ],
133
- particleProps [i + 2 ],
134
- particleProps [i + 3 ],
135
- particleProps [i + 4 ],
136
- particleProps [i + 5 ],
137
- particleProps [i + 6 ],
138
- particleProps [i + 7 ],
139
- particleProps [i + 8 ],
140
- ]
141
-
142
- const n = noise3D (x * xOff , y * yOff , tick * zOff ) * noiseSteps * TAU
143
- const nextVx = lerp (vx , Math .cos (n ), 0.5 )
144
- const nextVy = lerp (vy , Math .sin (n ), 0.5 )
145
-
146
- drawParticle (x , y , x + nextVx * speed , y + nextVy * speed , life , ttl , radius , hue , ctx )
147
-
148
- particleProps [i ] = x + nextVx * speed
149
- particleProps [i + 1 ] = y + nextVy * speed
150
- particleProps [i + 2 ] = nextVx
151
- particleProps [i + 3 ] = nextVy
152
- particleProps [i + 4 ] = life + 1
153
-
154
- if (checkBounds (x , y , canvas ) || life > ttl ) {
155
- initParticle (i )
156
- }
126
+ const canvas = canvasRef .value
127
+ if (! canvas )
128
+ return
129
+
130
+ const [x, y, vx, vy, life, ttl, speed, radius, hue] = [
131
+ particleProps [i ],
132
+ particleProps [i + 1 ],
133
+ particleProps [i + 2 ],
134
+ particleProps [i + 3 ],
135
+ particleProps [i + 4 ],
136
+ particleProps [i + 5 ],
137
+ particleProps [i + 6 ],
138
+ particleProps [i + 7 ],
139
+ particleProps [i + 8 ],
140
+ ]
141
+
142
+ const n = noise3D (x * xOff , y * yOff , tick * zOff ) * noiseSteps * TAU
143
+ const nextVx = lerp (vx , Math .cos (n ), 0.5 )
144
+ const nextVy = lerp (vy , Math .sin (n ), 0.5 )
145
+
146
+ drawParticle (x , y , x + nextVx * speed , y + nextVy * speed , life , ttl , radius , hue , ctx )
147
+
148
+ particleProps [i ] = x + nextVx * speed
149
+ particleProps [i + 1 ] = y + nextVy * speed
150
+ particleProps [i + 2 ] = nextVx
151
+ particleProps [i + 3 ] = nextVy
152
+ particleProps [i + 4 ] = life + 1
153
+
154
+ if (checkBounds (x , y , canvas ) || life > ttl ) {
155
+ initParticle (i )
156
+ }
157
157
}
158
158
159
159
function drawParticle(
160
- x : number ,
161
- y : number ,
162
- x2 : number ,
163
- y2 : number ,
164
- life : number ,
165
- ttl : number ,
166
- radius : number ,
167
- hue : number ,
168
- ctx : CanvasRenderingContext2D ,
160
+ x : number ,
161
+ y : number ,
162
+ x2 : number ,
163
+ y2 : number ,
164
+ life : number ,
165
+ ttl : number ,
166
+ radius : number ,
167
+ hue : number ,
168
+ ctx : CanvasRenderingContext2D ,
169
169
) {
170
- ctx .save ()
171
- ctx .lineCap = ' round'
172
- ctx .lineWidth = radius
173
- ctx .strokeStyle = ` hsla(${hue },100%,60%,${fadeInOut (life , ttl )}) `
174
- ctx .beginPath ()
175
- ctx .moveTo (x , y )
176
- ctx .lineTo (x2 , y2 )
177
- ctx .stroke ()
178
- ctx .closePath ()
179
- ctx .restore ()
170
+ ctx .save ()
171
+ ctx .lineCap = ' round'
172
+ ctx .lineWidth = radius
173
+ ctx .strokeStyle = ` hsla(${hue },100%,60%,${fadeInOut (life , ttl )}) `
174
+ ctx .beginPath ()
175
+ ctx .moveTo (x , y )
176
+ ctx .lineTo (x2 , y2 )
177
+ ctx .stroke ()
178
+ ctx .closePath ()
179
+ ctx .restore ()
180
180
}
181
181
182
182
function checkBounds(x : number , y : number , canvas : HTMLCanvasElement ) {
183
- return x > canvas .width || x < 0 || y > canvas .height || y < 0
183
+ return x > canvas .width || x < 0 || y > canvas .height || y < 0
184
184
}
185
185
186
186
function resize(canvas : HTMLCanvasElement , ctx ? : CanvasRenderingContext2D ) {
187
- const { innerWidth, innerHeight } = window
188
- canvas .width = innerWidth
189
- canvas .height = innerHeight
190
- center [0 ] = 0.5 * canvas .width
191
- center [1 ] = 0.5 * canvas .height
187
+ const { innerWidth, innerHeight } = window
188
+ canvas .width = innerWidth
189
+ canvas .height = innerHeight
190
+ center [0 ] = 0.5 * canvas .width
191
+ center [1 ] = 0.5 * canvas .height
192
192
}
193
193
194
194
function renderGlow(canvas : HTMLCanvasElement , ctx : CanvasRenderingContext2D ) {
195
- ctx .save ()
196
- ctx .filter = ' blur(8px) brightness(200%)'
197
- ctx .globalCompositeOperation = ' lighter'
198
- ctx .drawImage (canvas , 0 , 0 )
199
- ctx .restore ()
195
+ ctx .save ()
196
+ ctx .filter = ' blur(8px) brightness(200%)'
197
+ ctx .globalCompositeOperation = ' lighter'
198
+ ctx .drawImage (canvas , 0 , 0 )
199
+ ctx .restore ()
200
200
201
- ctx .save ()
202
- ctx .filter = ' blur(4px) brightness(200%)'
203
- ctx .globalCompositeOperation = ' lighter'
204
- ctx .drawImage (canvas , 0 , 0 )
205
- ctx .restore ()
201
+ ctx .save ()
202
+ ctx .filter = ' blur(4px) brightness(200%)'
203
+ ctx .globalCompositeOperation = ' lighter'
204
+ ctx .drawImage (canvas , 0 , 0 )
205
+ ctx .restore ()
206
206
}
207
207
208
208
function renderToScreen(canvas : HTMLCanvasElement , ctx : CanvasRenderingContext2D ) {
209
- ctx .save ()
210
- ctx .globalCompositeOperation = ' lighter'
211
- ctx .drawImage (canvas , 0 , 0 )
212
- ctx .restore ()
209
+ ctx .save ()
210
+ ctx .globalCompositeOperation = ' lighter'
211
+ ctx .drawImage (canvas , 0 , 0 )
212
+ ctx .restore ()
213
213
}
214
214
215
215
onMounted (() => {
216
- setup ()
217
- window .addEventListener (' resize' , () => {
218
- const canvas = canvasRef .value
219
- const ctx = canvas ?.getContext (' 2d' )
220
- if (canvas && ctx ) {
221
- resize (canvas , ctx )
222
- }
223
- })
216
+ setup ()
217
+ window .addEventListener (' resize' , () => {
218
+ const canvas = canvasRef .value
219
+ const ctx = canvas ?.getContext (' 2d' )
220
+ if (canvas && ctx ) {
221
+ resize (canvas , ctx )
222
+ }
223
+ })
224
224
})
225
225
226
226
onUnmounted (() => {
227
- window .removeEventListener (' resize' , () => { })
227
+ window .removeEventListener (' resize' , () => { })
228
228
})
229
229
</script >
230
230
231
231
<template >
232
- <div :class =" cn('relative h-full w-full', props.containerClass)" >
233
- <div ref =" containerRef" v-motion :initial =" { opacity: 0 }" :enter =" { opacity: 1 }"
234
- class =" absolute inset-0 z-0 flex size-full items-center justify-center bg-transparent" >
235
- <canvas ref =" canvasRef" />
236
- </div >
237
-
238
- <div :class =" cn('relative z-10', props.class)" >
239
- <slot />
240
- </div >
232
+ <div :class =" cn('relative h-full w-full', props.containerClass)" >
233
+ <div
234
+ ref =" containerRef" v-motion :initial =" { opacity: 0 }" :enter =" { opacity: 1 }"
235
+ class =" absolute inset-0 z-0 flex size-full items-center justify-center bg-transparent"
236
+ >
237
+ <canvas ref =" canvasRef" />
238
+ </div >
239
+
240
+ <div :class =" cn('relative z-10', props.class)" >
241
+ <slot />
241
242
</div >
243
+ </div >
242
244
</template >
0 commit comments