From 50b88dec67bddd3a49b917568cbcbd4aa2f7d25d Mon Sep 17 00:00:00 2001 From: arne Date: Fri, 26 Jan 2024 17:10:51 +0100 Subject: [PATCH] Create canvas with ripple effect on click --- bun.lockb | Bin 18812 -> 19229 bytes index.html | 5 +-- package.json | 1 + src/counter.ts | 9 ----- src/main.ts | 91 +++++++++++++++++++++++++++++++++---------- src/style.css | 95 +++------------------------------------------ src/typescript.svg | 1 - 7 files changed, 78 insertions(+), 124 deletions(-) delete mode 100644 src/counter.ts delete mode 100644 src/typescript.svg diff --git a/bun.lockb b/bun.lockb index f137ba7e7fa03d27dfab996c57752228f29b7e05..b7bf71afeb8e720d08b7ee8236c48d0ba6f7fb04 100755 GIT binary patch delta 2753 zcmc&$YfMx}6rS0KyR5RXfWl=#L|N1#`@Xm<3rlU)Vl^O5YYRR>5a?32tWZE?*HkRj z7^ElF##*deLu+cIZv3ICjariWXd5Z9X=+SuT3?A$wN*oOvFE6(WCfd6Yxwws@UV+HvD-!zoAvY2(KqeyRXh0l`7|{v| zE+caKpx8f#$OXW0kmT@nx|(Wl&iP}}guNFcP^~Pc7C5Bz(zjt)=~WM)j}wllJ=|oL zLY^vDeI41$Je#GIY}=8Q@V1-Cmho(XijYM-+ly=k&kSlpmhi0pA^QfI;5T)Yq`rkr z$ng`hm$)Y=3x_mb>W5+c{{q}vuYzcyRhv)e!Xd3!-Gy)Ef=HA{h3_>BBHCQ_8Dw*K zmN=S_LO7!HsE`QaZr;SX4IvZU*fEmYimZ@-^h;#(c@~Qwfna+DSrKph7@45nMkYk@ z=q0}nWJ0!64{h-=+!NeTkO&X-A$y(=cMF-2A~#kF+=)!E4IsmC(Vj5|oHATNoL8cR z^vo>Sr_|Ce7}n7xG6QON%< z1{6m2cLzqe{7-TTf{$9;|8WU|Pi*RiB?^y^yBsqwCZjB=yUfvEH~X`}@w?WQ6i(~a z-|c#Tb$CNs)8Sn^+B>`b3m4W7-hH>_t2%O~ds49}7(M^WzF!pgd@+`bTUcB~N=9Nv z@Z`6^aC*HWgMdCG8Mm1#s-fQJYxEOxJlr#JB^~R*a|icV2v0>)87Ob8@vR{3;pmJ( z%6Z4~hb+qDaLeMB423V4;-jD-&Zcu&g->gq6(VLY792A2KxmxZg1=_b!wGKdWN5P_ z&{5E1DKq4t#w{}zHLj2mJwz-HJr`k_4gaut>@dSR5l`~P)^j?)T`-Pv!A#pzd%j&2s*U}}d*=|O=G*+n^pyD(;%r*$w*2M=f2=@X#meP%x2lLzsbQvVt(`h@n z?Q0ck3ZL8UiZ}{4>`AOtK@UE-c1xFh%A}%Bv)jxKTdJ`Owhj#yd^j*7E7g$aV2n~t z4x3bqtyenYWqmii?qFy?Y;Z6sQboV?v`KsWnP>ZDFO>q0I+9rF3gFc&sA}juxk7f3 zE(xivMf76Z*^sQ1LbQ{iO)%N%pnXv0WQ<~=4`rR*)iChtfkg_s#Adc~Gj$BQoeV1# z)DxQGEwiuG9amAS+2%Ie>_jTLCHZHvQc?|lvJ!#w7+EUOy+-Td!K)RevYrVBmZ5IQ zVi}`Y;X}vC(45SxiUQfI0+zB2E8Pgry;(Kew)w|nvM${jc7D=cJES#zCo9`vFGiN` z7y~Cywa*Cy3&+}bHC1+XTEF-n)D6L&&sZ6Cb|vR(!Ia~RqJr*jxxu}k=JH@RRarvR+e zt;uU}A7Moft#h$o(`Nn1QPHJiF0`V&s>NR!sA#D1ugdl9=A>`)bN96}}pCU2OmiGmUwV_qSwQ9xrAK`{KRle*F`5`vNTh delta 2486 zcmc&$drVtZ7{8~z9c{N!Xc^mGhvhLkT$A>;rLVv!z8KXp=HgI+4K~mSn}9+Lb5lAp z;^KmepV<^&LrHK`qO(z)i)L}tNQ6k8WSSXc!kpq5@sTlgJp9hR_w2@GiT|LUr1|~M z_xsLwPR^sh`{DsI{WN*XdA)X~uBqUK>=Pqp)t?+ppV`}Def>|8-Z6a!MvFiErawMW z%t_7Fb*D}9o3pDmr>#=MacLYEEks;_n5FcMhywP1A{>{FID~i|;t|A5#Qlgi#2pH* zS9F)sw;p zw*+wvkxnp-NcCe%zYozIyF_*oXw_~YOJGnNCQV@A!(;#=d|0;=H?SN^_&nWdWOqr- zp=GfSWE-W}8D!NG3-BCQi7cN-QY(cnBikaeO*+P6$Y@%g&a{BV?nfq1@iDS8suC*< zCWJ{D81!M%3lV)-b3Oqc*B1~G1~JqK218i)0PgE8u-K5Ndl%VSiOnF3NUUg~tmgAb zD9-PqfZFmkGE}z~*sZqol^qxxX zU&gk-O@zlzACT0pg?M{8zoO3%w#|3Jf38Q@Pj_9~d7S_M+Inf{>HHG;@3&ssse9@C z^L(b)s2AI5o-n}&nKi@+8Ck3Jui>P4%cHUQ$~y>s2Zn~N4UKiR_&*LuS6cM7_)mtf zA{zU}K15<~*ewQQ&?POETjzqLUF~@U7QlWxjGeCDpw7$>-X6lIsjP=|i!ABUi98AG*DJaE=_a&b%_P zsz2ztGE$@}M!14&V&0nea4GqI_h?kr3&G_RNF_vkUeXDV_ynusk5R|ys}IJvzg?*z zTij03>EXCOIEuN);4{o(uC%dW#IYhRxsNBJ(;afUJ=_?~`2-(xv6rmk`=`fi%2l2D zoOok*)xZLiMpbN3;uoxnF-AY)CfC}>HHE4kfjxeKRKW9?#ccJJ!;jWa>a)L4v)bWv zzu;pw9Pdd=JC!qDs_M+BSDSOZWdB%?s+@$hfZ%7wzAbgyV6vr5sXDX%9}cwq+7q5S zt}1f|*b!J}W#0;rH<4A}*}YFqu*1s%!Nw_Lv=0pIP06C QD`brsLqkch=GO220#c)&s{jB1 diff --git a/index.html b/index.html index 44a9335..3504586 100644 --- a/index.html +++ b/index.html @@ -2,12 +2,11 @@ - - Vite + TS + ripples | compost.party -
+ diff --git a/package.json b/package.json index 335a73b..801587e 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "devDependencies": { "typescript": "^5.2.2", + "typescript-language-server": "^4.3.1", "vite": "^5.0.8" } } diff --git a/src/counter.ts b/src/counter.ts deleted file mode 100644 index 09e5afd..0000000 --- a/src/counter.ts +++ /dev/null @@ -1,9 +0,0 @@ -export function setupCounter(element: HTMLButtonElement) { - let counter = 0 - const setCounter = (count: number) => { - counter = count - element.innerHTML = `count is ${counter}` - } - element.addEventListener('click', () => setCounter(counter + 1)) - setCounter(0) -} diff --git a/src/main.ts b/src/main.ts index 791547b..ec0d437 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,24 +1,73 @@ import './style.css' -import typescriptLogo from './typescript.svg' -import viteLogo from '/vite.svg' -import { setupCounter } from './counter.ts' -document.querySelector('#app')!.innerHTML = ` -
- - - - - - -

Vite + TypeScript

-
- -
-

- Click on the Vite and TypeScript logos to learn more -

-
-` +const canvas = document.querySelector('#canvas')! -setupCounter(document.querySelector('#counter')!) +const ctx = canvas.getContext('2d')! + +const MIN_RADIUS = 20 +const MAX_RADIUS = 200 +const MAX_AGE = 5000 // in milliseconds + +type Particle = { + position: [number, number], + age: number +} + +const createParticle = (x: number, y: number): Particle => ({ + position: [x, y], + age: 0, +}) + +let state = { + particles: [] +} + +type State = typeof state + +canvas.addEventListener('click', (e) => { + // TODO Normalize x and y coords + const particle = createParticle(e.clientX, e.clientY) + state.particles.push(particle) +}) + +const update = (state: State, deltaTime: number): State => ({ + particles: state.particles + .map(p => ({ + ...p, + age: p.age + deltaTime + })) + .filter(p => p.age < MAX_AGE) +}) + +const render = (state: State, ctx: CanvasRenderingContext2D) => { + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height) + + for (const particle of state.particles) { + const progress = (particle.age / MAX_AGE) + const opacity = Math.min(1, (1-progress)*(1-progress)) + + ctx.beginPath() + ctx.strokeStyle = `rgba(40, 40, 40, ${opacity})` + const [x, y] = particle.position + const radius = (progress * (MAX_RADIUS - MIN_RADIUS)) + MIN_RADIUS + ctx.ellipse(x, y, radius, radius, 0, 0, Math.PI * 2) + ctx.stroke() + } +} + +let before = Date.now() +const loop = () => { + const now = Date.now() + const dt = now - before + state = update(state, dt) + render(state, ctx) + before = now + requestAnimationFrame(loop) +} + +document.addEventListener('DOMContentLoaded', () => { + canvas.width = canvas.parentElement!.clientWidth + canvas.height = canvas.parentElement!.clientHeight + ctx.translate(0.5, 0.5) + loop() +}) diff --git a/src/style.css b/src/style.css index f9c7350..92912df 100644 --- a/src/style.css +++ b/src/style.css @@ -1,96 +1,11 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - +html, body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; + min-width: 100wh; min-height: 100vh; + padding: 0; + margin: 0; } -h1 { - font-size: 3.2em; - line-height: 1.1; -} +canvas { -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.vanilla:hover { - filter: drop-shadow(0 0 2em #3178c6aa); -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } } diff --git a/src/typescript.svg b/src/typescript.svg deleted file mode 100644 index d91c910..0000000 --- a/src/typescript.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file