Frontend Forever App
We have a mobile app for you to download and use. And you can unlock many features in the app.
Get it now
Intall Later
Run
HTML
CSS
Javascript
Output
Document
@charset "UTF-8"; @import url(https://fonts.googleapis.com/css?family=Nunito+Sans:300,400,600,700,800); *, :after, :before { box-sizing: border-box; padding: 0; margin: 0; } body{ overflow: hidden; margin: 0; background-color: gray; } canvas { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); box-sizing: border-box; }
console.log("Event Fired") import {Vector2, Clock, MathUtils} from "https://unpkg.com/three@0.165.0/build/three.module.js"; console.clear(); let mu = MathUtils; // load fonts await (async function () { async function loadFont(fontface) { await fontface.load(); document.fonts.add(fontface); } let fonts = [ new FontFace( "Ultra", "url(https://fonts.gstatic.com/s/ultra/v23/zOLy4prXmrtY-uT9wrI.woff2) format('woff2')" ) ]; for (let font in fonts) { await loadFont(fonts[font]); } })(); let beerPalette = ["#F4400C", "#FFAC33", "#FFCC4D", "#E1E8ED"]; class StencilCanvas { constructor(){ let c = document.createElement("canvas"); c.width = c.height = 1024; let ctx = c.getContext("2d"); let u = val => c.height * 0.01 * val; ctx.fillStyle = "rgba(0, 0, 0, 1)"; ctx.fillRect(0, 0, c.width, c.height); ctx.fillStyle = beerPalette[2]; ctx.fillRect(0, 0, c.width, c.height); ctx.font = `${u(49)}px Ultra`; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.translate(u(50), u(53)); let str = [..."BEER"]; let charIdx = 0; let baseGCO = ctx.globalCompositeOperation; for(let y = -1; y <= 1; y += 2){ for(let x = -1; x <= 1; x += 2){ let text = str[charIdx]; let posX = u(x * 22); let posY = u(y * 22); ctx.fillStyle = `rgba(0, 0, 0, 1)`; ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = beerPalette[3]; ctx.lineWidth = u(5); ctx.strokeText(text, posX, posY); ctx.strokeStyle = beerPalette[0]; ctx.lineWidth = u(3.5); ctx.strokeText(text, posX, posY); ctx.globalCompositeOperation = "destination-out"; ctx.fillText(text, posX, posY); charIdx++; } } this.canvas = c; this.data = ctx.getImageData(0, 0, c.width, c.height); } } class Bubble extends Vector2{ constructor(x, y){ super(x, y); this.actualPosition = new Vector2(x, y); this.speed = 0; this.size = 0; this.connections = 0; this.isActive = true; this.interactive = false; this.shift = new Vector2(); } } class Bubbles { constructor(amount){ this.items = []; this.fill = beerPalette[2]; this.stroke = "#ffa"; this.border = 0.5; this.line = 0.25; this.maxConnections = 10; this.maxDistance = 20; this.setItems(amount); this.shockwave = { totalTime: 0, startTime: 0, inAction: false, position: new Vector2(-100, -100), maxRadius: 50, maxAmp: 10, waveWidth: 10, duration: 1 //seconds } } setItems(amount){ this.items = Array.from({length: amount + 1}, () => { let bubble = new Bubble().random().multiplyScalar(100); bubble.actualPosition.copy(bubble); bubble.speed = Math.random() * 15 + 5; bubble.size = Math.random() * 1 + 0.5; return bubble; }); this.items[0].isActive = false; this.items[0].interactive = true; this.items[0].size = 3; this.items[0].initSize = 3; } update(t){ let sw = this.shockwave; sw.totalTime += t; let waveTimeRatio = mu.clamp((sw.totalTime - sw.startTime) / sw.duration, 0, 1); let waveDist = sw.maxRadius * waveTimeRatio; let waveAmp = 1 - waveTimeRatio; this.items.forEach(item => { if(item.interactive) return; item.actualPosition.y -= item.speed * t; if(item.y < 0) { item.actualPosition.y = (100 - item.y) % 100 + 100; item.actualPosition.x = Math.random() * 100; item.speed = Math.random() * 15 + 5; item.size = Math.random() * 1 + 0.5; } // shockwave shift item.shift.subVectors(item.actualPosition, this.shockwave.position); let itemDist = item.shift.length(); item.shift.normalize(); let shiftAmp = mu.smoothstep(itemDist, waveDist - sw.waveWidth, waveDist) - mu.smoothstep(itemDist, waveDist, waveDist + sw.waveWidth); item.shift.multiplyScalar(sw.maxAmp * shiftAmp * waveAmp); item.copy(item.actualPosition).add(item.shift); }); this.drawLines(); this.drawBubbles(); // } drawLines(){ for(let i = 0; i < this.items.length; i++){this.items[i].connections = 0;} for(let i = 0; i < this.items.length; i++){ let bubble = this.items[i]; if (!bubble.isActive) continue; if (bubble.connections >= this.maxConnections) continue; for(let j = i + 1; j < this.items.length; j++){ let bubbleB = this.items[j]; if (bubbleB.connections >= this.maxConnections) continue; let dist = bubble.distanceTo(bubbleB); if(dist <= this.maxDistance){ bubble.connections++; bubbleB.connections++; let a = 1 - (mu.clamp(dist / this.maxDistance, 0, 1) ** 1.9); let lw = u(this.line * a); ctx.strokeStyle = `rgba(255, 255, 127, ${a})`; ctx.lineWidth = lw; ctx.beginPath(); ctx.moveTo(u(bubble.x), u(bubble.y)); ctx.lineTo(u(bubbleB.x), u(bubbleB.y)); ctx.stroke(); } } } } drawBubbles(){ ctx.fillStyle = this.fill; ctx.strokeStyle = this.stroke; ctx.lineWidth = u(this.border); ctx.beginPath(); this.items.forEach(item => { if (!item.isActive) return; ctx.moveTo(u(item.x + item.size), u(item.y)); ctx.arc(u(item.x), u(item.y), u(item.size), 0, Math.PI * 2); }) ctx.fill(); ctx.stroke(); } } class StampJS { constructor(){ this.text = "JS"; } draw(t){ ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.font = `${u(50)}px Ultra`; ctx.fillStyle = `rgba(255, 255, 200, 0.25)`; let shift = Math.sin(t * 0.25 + Math.sin(t * 0.1 + Math.cos(t * 0.031)) * Math.PI); ctx.fillText(this.text, u(50), u(53 + shift * 10)); } } let c = cnv; let ctx = cnv.getContext("2d"); let unit = 0; let u = (val) => unit * val; let resize = event => { let minVal = Math.min(innerWidth, innerHeight); c.width = c.height = minVal * 0.95; unit = c.height * 0.01; c.style.borderRadius = `${u(20)}px`; c.style.border = `${u(2)}px solid #444`; c.style.backgroundColor = beerPalette[1]; } window.addEventListener("resize", resize); resize(); let stencil = new StencilCanvas(); let stampJS = new StampJS(); let bubbles = new Bubbles(100); let zeroItem = bubbles.items[0]; let wavePos = new Vector2(); c.addEventListener("pointerdown", event => { if(zeroItem.isActive){ zeroItem.size = zeroItem.initSize * 1.25; bubbles.shockwave.startTime = bubbles.shockwave.totalTime; bubbles.shockwave.position.copy(zeroItem); } else { zeroItem.size = zeroItem.initSize; } }); c.addEventListener("pointerup", event => { zeroItem.size = zeroItem.initSize; }) c.addEventListener("pointermove", event => { let rect = event.target.getBoundingClientRect(); let x = event.clientX - rect.left - u(2); let y = event.clientY - rect.top - u(2); let w = rect.right - rect.left - u(4); let h = rect.bottom - rect.top - u(4); //console.log(x, y, w, h); let xRatio = x / w; let yRatio = y / h; let stencilX = Math.floor(xRatio * stencil.canvas.width); let stencilY = Math.floor(yRatio * stencil.canvas.height); let stencilIndex = stencilY * stencil.canvas.width + stencilX; let alphaData = stencil.data.data[stencilIndex * 4 + 3]; if (alphaData == 0) { zeroItem.set(xRatio, yRatio).multiplyScalar(100); zeroItem.isActive = true; } else { zeroItem.isActive = false; } }) let clock = new Clock(); let t = 0; let tMin = 1 / 30; animate(); function animate(){ let dt = Math.min(tMin, clock.getDelta()); t += dt; requestAnimationFrame(animate); ctx.clearRect(0, 0, c.width, c.height); stampJS.draw(t); bubbles.update(dt); ctx.drawImage(stencil.canvas, 0, 0, c.width, c.height); }