UNPKG

@chencu/liquid-glass-vue

Version:

Performance-optimized Apple's Liquid Glass effect for Vue 3. Fork with 70% better performance.

155 lines (154 loc) 6.48 kB
(function() { "use strict"; function smoothStep(a, b, t) { t = Math.max(0, Math.min(1, (t - a) / (b - a))); return t * t * (3 - 2 * t); } function length(x, y) { return Math.sqrt(x * x + y * y); } function roundedRectSDF(x, y, width, height, radius) { const qx = Math.abs(x) - width + radius; const qy = Math.abs(y) - height + radius; return Math.min(Math.max(qx, qy), 0) + length(Math.max(qx, 0), Math.max(qy, 0)) - radius; } function texture(x, y) { return { x, y }; } function noise(x, y) { const n = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453; return (n - Math.floor(n)) * 2 - 1; } function fbm(x, y, octaves = 4) { let value = 0; let amplitude = 0.5; let frequency = 1; for (let i = 0; i < octaves; i++) { value += amplitude * noise(x * frequency, y * frequency); amplitude *= 0.5; frequency *= 2; } return value; } const fragmentShaders = { liquidGlass: (uv) => { const ix = uv.x - 0.5; const iy = uv.y - 0.5; const distanceToEdge = roundedRectSDF(ix, iy, 0.3, 0.2, 0.6); const displacement = smoothStep(0.8, 0, distanceToEdge - 0.15); const scaled = smoothStep(0, 1, displacement); return texture(ix * scaled + 0.5, iy * scaled + 0.5); }, flowingLiquid: (uv) => { const ix = uv.x - 0.5; const iy = uv.y - 0.5; const distanceToEdge = roundedRectSDF(ix, iy, 0.3, 0.2, 1); const displacement = smoothStep(0.8, 0, distanceToEdge - 0.15); const scaled = smoothStep(0, 1, displacement); return texture(ix * scaled + 0.5, iy * scaled + 0.5); }, transparentIce: (uv) => { const ix = uv.x - 0.5; const iy = uv.y - 0.5; const crystalNoise = fbm(uv.x * 8, uv.y * 8, 3) * 0.05; const fineDetail = noise(uv.x * 20, uv.y * 20) * 0.02; const distanceToEdge = roundedRectSDF(ix, iy, 0.35, 0.25, 0.3); const iceMask = smoothStep(0.5, -0.2, distanceToEdge); const crackPattern = Math.sin(uv.x * 15 + crystalNoise * 10) * Math.cos(uv.y * 12) * 0.03; const distortionX = ix + (crystalNoise + fineDetail + crackPattern) * iceMask; const distortionY = iy + (crystalNoise * 0.8 + fineDetail) * iceMask; const edgeEffect = smoothStep(0, 0.1, distanceToEdge) * 0.1; return texture(distortionX * (1 - edgeEffect) + 0.5, distortionY * (1 - edgeEffect) + 0.5); }, unevenGlass: (uv) => { const ix = uv.x - 0.5; const iy = uv.y - 0.5; const surfaceWarp = fbm(uv.x * 3, uv.y * 3, 2) * 0.08; const surfaceDetail = noise(uv.x * 6 + surfaceWarp, uv.y * 6) * 0.04; const warpedX = ix + surfaceWarp * 0.3; const warpedY = iy + surfaceWarp * 0.3; const distanceToEdge = roundedRectSDF(warpedX, warpedY, 0.32, 0.22, 0.4); const glassMask = smoothStep(0.6, -0.1, distanceToEdge); const ripples = Math.sin(uv.x * 8 + surfaceWarp * 5) * Math.sin(uv.y * 8) * 0.02; const distortionStrength = glassMask * (1 + surfaceDetail * 0.5); const finalX = ix + (surfaceWarp + ripples) * distortionStrength; const finalY = iy + (surfaceWarp * 0.9 + surfaceDetail + ripples * 0.7) * distortionStrength; return texture(finalX + 0.5, finalY + 0.5); }, mosaicGlass: (uv) => { const ix = uv.x - 0.5; const iy = uv.y - 0.5; const tileSize = 0.05; const tileX = Math.floor(uv.x / tileSize); const tileY = Math.floor(uv.y / tileSize); const tileSeed = Math.sin(tileX * 12.9898 + tileY * 78.233) * 43758.5453; const tileRandom = tileSeed - Math.floor(tileSeed); const tileAngle = tileRandom * Math.PI * 2; const refractionX = Math.cos(tileAngle) * 0.03; const refractionY = Math.sin(tileAngle) * 0.03; const distanceToEdge = roundedRectSDF(ix, iy, 0.35, 0.25, 0.05); const glassMask = smoothStep(0.4, -0.1, distanceToEdge); const localX = uv.x % tileSize / tileSize; const localY = uv.y % tileSize / tileSize; const groutThickness = 0.1; const isGroutLine = localX < groutThickness || localX > 1 - groutThickness || localY < groutThickness || localY > 1 - groutThickness; const surfaceNoise = noise(tileX * 3.7, tileY * 3.7) * 0.02; const tileVariation = (tileRandom - 0.5) * 0.04; let finalDistortionX, finalDistortionY; if (isGroutLine) { finalDistortionX = surfaceNoise * 0.2; finalDistortionY = surfaceNoise * 0.2; } else { const tileDistortion = 1 + tileVariation; finalDistortionX = (refractionX + surfaceNoise) * tileDistortion; finalDistortionY = (refractionY + surfaceNoise) * tileDistortion; } const maskedDistortionX = finalDistortionX * glassMask; const maskedDistortionY = finalDistortionY * glassMask; return texture(ix + maskedDistortionX + 0.5, iy + maskedDistortionY + 0.5); } }; self.onmessage = (e) => { const { width, height, effect, mousePosition, time } = e.data; const w = width; const h = height; let maxScale = 0; const rawValues = []; for (let y = 0; y < h; y++) { for (let x = 0; x < w; x++) { const uv = { x: x / w, y: y / h }; const pos = fragmentShaders[effect](uv, mousePosition, time); const dx = pos.x * w - x; const dy = pos.y * h - y; maxScale = Math.max(maxScale, Math.abs(dx), Math.abs(dy)); rawValues.push(dx, dy); } } if (maxScale > 0) { maxScale = Math.max(maxScale, 1); } else { maxScale = 1; } const imageData = new ImageData(w, h); const data = imageData.data; let rawIndex = 0; for (let y = 0; y < h; y++) { for (let x = 0; x < w; x++) { const dx = rawValues[rawIndex++]; const dy = rawValues[rawIndex++]; const edgeDistance = Math.min(x, y, w - x - 1, h - y - 1); const edgeFactor = Math.min(1, edgeDistance / 2); const smoothedDx = dx * edgeFactor; const smoothedDy = dy * edgeFactor; const r = smoothedDx / maxScale + 0.5; const g = smoothedDy / maxScale + 0.5; const pixelIndex = (y * w + x) * 4; data[pixelIndex] = Math.max(0, Math.min(255, r * 255)); data[pixelIndex + 1] = Math.max(0, Math.min(255, g * 255)); data[pixelIndex + 2] = Math.max(0, Math.min(255, g * 255)); data[pixelIndex + 3] = 255; } } self.postMessage({ imageData }, [imageData.data.buffer]); }; })();