@@ -6,6 +6,10 @@ import "prism-themes/themes/prism-nord.min.css";
6
6
Prism . manual = true ;
7
7
Prism . highlightAll ( ) ;
8
8
9
+ function lerp ( a : number , b : number , t : number ) {
10
+ return a * ( 1 - t ) + b * t ;
11
+ }
12
+
9
13
const shaders : Record < string , string > = {
10
14
logo : `
11
15
precision highp float;
@@ -142,6 +146,32 @@ const shaders: Record<string, string> = {
142
146
img.a *= 0.5;
143
147
gl_FragColor = img;
144
148
}
149
+ ` ,
150
+ canvas : `
151
+ precision highp float;
152
+ uniform vec2 resolution;
153
+ uniform vec2 offset;
154
+ uniform float time;
155
+ uniform sampler2D src;
156
+
157
+ #define ZOOM(uv, x) ((uv - .5) / x + .5)
158
+
159
+ void main (void) {
160
+ vec2 uv = (gl_FragCoord.xy - offset) / resolution;
161
+
162
+ float r = sin(time) * 0.5 + 0.5;
163
+
164
+ float l = pow(length(uv - .5), 2.);
165
+ uv = (uv - .5) * (1. - l * 0.3 * r) + .5;
166
+
167
+
168
+ float n = 0.02 + r * 0.03;
169
+ vec4 cr = texture2D(src, ZOOM(uv, 1.00));
170
+ vec4 cg = texture2D(src, ZOOM(uv, (1. + n)));
171
+ vec4 cb = texture2D(src, ZOOM(uv, (1. + n * 2.)));
172
+
173
+ gl_FragColor = vec4(cr.r, cg.g, cb.b, 1);
174
+ }
145
175
` ,
146
176
custom : `
147
177
precision highp float;
@@ -218,6 +248,85 @@ class App {
218
248
} ) ;
219
249
}
220
250
251
+ initCanvas ( ) {
252
+ const canvas = document . getElementById ( "canvas" ) as HTMLCanvasElement ;
253
+ const ctx = canvas . getContext ( "2d" ) ! ;
254
+ const { width, height } = canvas . getBoundingClientRect ( ) ;
255
+ const ratio = window . devicePixelRatio ?? 1 ;
256
+ canvas . width = width * ratio ;
257
+ canvas . height = height * ratio ;
258
+ ctx . scale ( ratio , ratio ) ;
259
+
260
+ let target = [ width / 2 , height / 2 ] ;
261
+ let p = target ;
262
+ const ps = [ p ] ;
263
+ let isMouseOn = false ;
264
+ const startTime = Date . now ( ) ;
265
+
266
+ canvas . addEventListener ( "mousemove" , ( e ) => {
267
+ isMouseOn = true ;
268
+ target = [ e . offsetX , e . offsetY ] ;
269
+ } ) ;
270
+ canvas . addEventListener ( "mouseleave" , ( e ) => {
271
+ isMouseOn = false ;
272
+ } ) ;
273
+
274
+ let isInside = false ;
275
+ const io = new IntersectionObserver (
276
+ ( changes ) => {
277
+ for ( const c of changes ) {
278
+ isInside = c . intersectionRatio > 0.1 ;
279
+ }
280
+ } ,
281
+ { threshold : [ 0 , 1 , 0.2 , 0.8 ] } ,
282
+ ) ;
283
+ io . observe ( canvas ) ;
284
+
285
+ const drawMouseStalker = ( ) => {
286
+ requestAnimationFrame ( drawMouseStalker ) ;
287
+
288
+ if ( ! isInside ) {
289
+ return ;
290
+ }
291
+
292
+ if ( ! isMouseOn ) {
293
+ const t = Date . now ( ) / 1000 - startTime ;
294
+ target = [
295
+ width * 0.5 + Math . sin ( t * 1.3 ) * width * 0.3 ,
296
+ height * 0.5 + Math . sin ( t * 1.7 ) * height * 0.3 ,
297
+ ] ;
298
+ }
299
+ p = [ lerp ( p [ 0 ] , target [ 0 ] , 0.1 ) , lerp ( p [ 1 ] , target [ 1 ] , 0.1 ) ] ;
300
+
301
+ ps . push ( p ) ;
302
+ ps . splice ( 0 , ps . length - 30 ) ;
303
+
304
+ ctx . clearRect ( 0 , 0 , width , height ) ;
305
+ ctx . fillStyle = "black" ;
306
+ ctx . fillRect ( 0 , 0 , width , height ) ;
307
+
308
+ ctx . fillStyle = "white" ;
309
+ ctx . font = `bold ${ width * 0.14 } px sans-serif` ;
310
+ ctx . fillText ( "HOVER ME" , width / 2 , height / 2 ) ;
311
+ ctx . textBaseline = "middle" ;
312
+ ctx . textAlign = "center" ;
313
+
314
+ for ( let i = 0 ; i < ps . length ; i ++ ) {
315
+ const [ x , y ] = ps [ i ] ;
316
+ const t = ( i / ps . length ) * 255 ;
317
+ ctx . fillStyle = `rgba(${ 255 - t } , 255, ${ t } , ${ ( i / ps . length ) * 0.5 + 0.5 } )` ;
318
+ ctx . beginPath ( ) ;
319
+ ctx . arc ( x , y , i + 20 , 0 , 2 * Math . PI ) ;
320
+ ctx . fill ( ) ;
321
+ }
322
+
323
+ this . vfx . update ( canvas ) ;
324
+ } ;
325
+ drawMouseStalker ( ) ;
326
+
327
+ this . vfx . add ( canvas , { shader : shaders . canvas } ) ;
328
+ }
329
+
221
330
initCustomShader ( ) {
222
331
const e = document . getElementById ( "custom" ) ! ;
223
332
this . vfx . add ( e , {
@@ -265,6 +374,7 @@ window.addEventListener("load", () => {
265
374
app . initBG ( ) ;
266
375
app . initVFX ( ) ;
267
376
app . initDiv ( ) ;
377
+ app . initCanvas ( ) ;
268
378
app . initCustomShader ( ) ;
269
379
app . hideMask ( ) ;
270
380
setTimeout ( ( ) => {
0 commit comments