mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 06:29:30 -07:00
1 line
13 KiB
JSON
1 line
13 KiB
JSON
{"name":"Prism","title":"Prism","description":"Rotating prism with configurable intensity, size, and colors.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<script setup lang=\"ts\">\nimport { Mesh, Program, Renderer, Triangle } from 'ogl';\nimport { onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue';\n\ntype PrismProps = {\n height?: number;\n baseWidth?: number;\n animationType?: 'rotate' | 'hover' | '3drotate';\n glow?: number;\n offset?: { x?: number; y?: number };\n noise?: number;\n transparent?: boolean;\n scale?: number;\n hueShift?: number;\n colorFrequency?: number;\n hoverStrength?: number;\n inertia?: number;\n bloom?: number;\n suspendWhenOffscreen?: boolean;\n timeScale?: number;\n};\n\nconst props = withDefaults(defineProps<PrismProps>(), {\n height: 3.5,\n baseWidth: 5.5,\n animationType: 'rotate',\n glow: 1,\n offset: () => ({ x: 0, y: 0 }),\n noise: 0.5,\n transparent: true,\n scale: 3.6,\n hueShift: 0,\n colorFrequency: 1,\n hoverStrength: 2,\n inertia: 0.05,\n bloom: 1,\n suspendWhenOffscreen: false,\n timeScale: 0.5\n});\n\nconst containerRef = useTemplateRef('containerRef');\n\nlet cleanup: (() => void) | null = null;\n\nconst setup = () => {\n const container = containerRef.value;\n if (!container) return;\n\n const H = Math.max(0.001, props.height);\n const BW = Math.max(0.001, props.baseWidth);\n const BASE_HALF = BW * 0.5;\n const GLOW = Math.max(0.0, props.glow);\n const NOISE = Math.max(0.0, props.noise);\n const offX = props.offset?.x ?? 0;\n const offY = props.offset?.y ?? 0;\n const SAT = props.transparent ? 1.5 : 1;\n const SCALE = Math.max(0.001, props.scale);\n const HUE = props.hueShift || 0;\n const CFREQ = Math.max(0.0, props.colorFrequency || 1);\n const BLOOM = Math.max(0.0, props.bloom || 1);\n const RSX = 1;\n const RSY = 1;\n const RSZ = 1;\n const TS = Math.max(0, props.timeScale || 1);\n const HOVSTR = Math.max(0, props.hoverStrength || 1);\n const INERT = Math.max(0, Math.min(1, props.inertia || 0.12));\n\n const dpr = Math.min(2, window.devicePixelRatio || 1);\n const renderer = new Renderer({\n dpr,\n alpha: props.transparent,\n antialias: false\n });\n const gl = renderer.gl;\n gl.disable(gl.DEPTH_TEST);\n gl.disable(gl.CULL_FACE);\n gl.disable(gl.BLEND);\n\n Object.assign(gl.canvas.style, {\n position: 'absolute',\n inset: '0',\n width: '100%',\n height: '100%',\n display: 'block'\n } as Partial<CSSStyleDeclaration>);\n container.appendChild(gl.canvas);\n\n const vertex = /* glsl */ `\n attribute vec2 position;\n void main() {\n gl_Position = vec4(position, 0.0, 1.0);\n }\n `;\n\n const fragment = /* glsl */ `\n precision highp float;\n\n uniform vec2 iResolution;\n uniform float iTime;\n\n uniform float uHeight;\n uniform float uBaseHalf;\n uniform mat3 uRot;\n uniform int uUseBaseWobble;\n uniform float uGlow;\n uniform vec2 uOffsetPx;\n uniform float uNoise;\n uniform float uSaturation;\n uniform float uScale;\n uniform float uHueShift;\n uniform float uColorFreq;\n uniform float uBloom;\n uniform float uCenterShift;\n uniform float uInvBaseHalf;\n uniform float uInvHeight;\n uniform float uMinAxis;\n uniform float uPxScale;\n uniform float uTimeScale;\n\n vec4 tanh4(vec4 x){\n vec4 e2x = exp(2.0*x);\n return (e2x - 1.0) / (e2x + 1.0);\n }\n\n float rand(vec2 co){\n return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453123);\n }\n\n float sdOctaAnisoInv(vec3 p){\n vec3 q = vec3(abs(p.x) * uInvBaseHalf, abs(p.y) * uInvHeight, abs(p.z) * uInvBaseHalf);\n float m = q.x + q.y + q.z - 1.0;\n return m * uMinAxis * 0.5773502691896258;\n }\n\n float sdPyramidUpInv(vec3 p){\n float oct = sdOctaAnisoInv(p);\n float halfSpace = -p.y;\n return max(oct, halfSpace);\n }\n\n mat3 hueRotation(float a){\n float c = cos(a), s = sin(a);\n mat3 W = mat3(\n 0.299, 0.587, 0.114,\n 0.299, 0.587, 0.114,\n 0.299, 0.587, 0.114\n );\n mat3 U = mat3(\n 0.701, -0.587, -0.114,\n -0.299, 0.413, -0.114,\n -0.300, -0.588, 0.886\n );\n mat3 V = mat3(\n 0.168, -0.331, 0.500,\n 0.328, 0.035, -0.500,\n -0.497, 0.296, 0.201\n );\n return W + U * c + V * s;\n }\n\n void main(){\n vec2 f = (gl_FragCoord.xy - 0.5 * iResolution.xy - uOffsetPx) * uPxScale;\n\n float z = 5.0;\n float d = 0.0;\n\n vec3 p;\n vec4 o = vec4(0.0);\n\n float centerShift = uCenterShift;\n float cf = uColorFreq;\n\n mat2 wob = mat2(1.0);\n if (uUseBaseWobble == 1) {\n float t = iTime * uTimeScale;\n float c0 = cos(t + 0.0);\n float c1 = cos(t + 33.0);\n float c2 = cos(t + 11.0);\n wob = mat2(c0, c1, c2, c0);\n }\n\n const int STEPS = 100;\n for (int i = 0; i < STEPS; i++) {\n p = vec3(f, z);\n p.xz = p.xz * wob;\n p = uRot * p;\n vec3 q = p;\n q.y += centerShift;\n d = 0.1 + 0.2 * abs(sdPyramidUpInv(q));\n z -= d;\n o += (sin((p.y + z) * cf + vec4(0.0, 1.0, 2.0, 3.0)) + 1.0) / d;\n }\n\n o = tanh4(o * o * (uGlow * uBloom) / 1e5);\n\n vec3 col = o.rgb;\n float n = rand(gl_FragCoord.xy + vec2(iTime));\n col += (n - 0.5) * uNoise;\n col = clamp(col, 0.0, 1.0);\n\n float L = dot(col, vec3(0.2126, 0.7152, 0.0722));\n col = clamp(mix(vec3(L), col, uSaturation), 0.0, 1.0);\n\n if(abs(uHueShift) > 0.0001){\n col = clamp(hueRotation(uHueShift) * col, 0.0, 1.0);\n }\n\n gl_FragColor = vec4(col, o.a);\n }\n `;\n\n const geometry = new Triangle(gl);\n const iResBuf = new Float32Array(2);\n const offsetPxBuf = new Float32Array(2);\n\n const program = new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n iResolution: { value: iResBuf },\n iTime: { value: 0 },\n uHeight: { value: H },\n uBaseHalf: { value: BASE_HALF },\n uUseBaseWobble: { value: 1 },\n uRot: { value: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]) },\n uGlow: { value: GLOW },\n uOffsetPx: { value: offsetPxBuf },\n uNoise: { value: NOISE },\n uSaturation: { value: SAT },\n uScale: { value: SCALE },\n uHueShift: { value: HUE },\n uColorFreq: { value: CFREQ },\n uBloom: { value: BLOOM },\n uCenterShift: { value: H * 0.25 },\n uInvBaseHalf: { value: 1 / BASE_HALF },\n uInvHeight: { value: 1 / H },\n uMinAxis: { value: Math.min(BASE_HALF, H) },\n uPxScale: {\n value: 1 / ((gl.drawingBufferHeight || 1) * 0.1 * SCALE)\n },\n uTimeScale: { value: TS }\n }\n });\n const mesh = new Mesh(gl, { geometry, program });\n\n const resize = () => {\n const w = container.clientWidth || 1;\n const h = container.clientHeight || 1;\n renderer.setSize(w, h);\n iResBuf[0] = gl.drawingBufferWidth;\n iResBuf[1] = gl.drawingBufferHeight;\n offsetPxBuf[0] = offX * dpr;\n offsetPxBuf[1] = offY * dpr;\n program.uniforms.uPxScale.value = 1 / ((gl.drawingBufferHeight || 1) * 0.1 * SCALE);\n };\n const ro = new ResizeObserver(resize);\n ro.observe(container);\n resize();\n\n const rotBuf = new Float32Array(9);\n const setMat3FromEuler = (yawY: number, pitchX: number, rollZ: number, out: Float32Array) => {\n const cy = Math.cos(yawY),\n sy = Math.sin(yawY);\n const cx = Math.cos(pitchX),\n sx = Math.sin(pitchX);\n const cz = Math.cos(rollZ),\n sz = Math.sin(rollZ);\n const r00 = cy * cz + sy * sx * sz;\n const r01 = -cy * sz + sy * sx * cz;\n const r02 = sy * cx;\n\n const r10 = cx * sz;\n const r11 = cx * cz;\n const r12 = -sx;\n\n const r20 = -sy * cz + cy * sx * sz;\n const r21 = sy * sz + cy * sx * cz;\n const r22 = cy * cx;\n\n out[0] = r00;\n out[1] = r10;\n out[2] = r20;\n out[3] = r01;\n out[4] = r11;\n out[5] = r21;\n out[6] = r02;\n out[7] = r12;\n out[8] = r22;\n return out;\n };\n\n const NOISE_IS_ZERO = NOISE < 1e-6;\n let raf = 0;\n const t0 = performance.now();\n const startRAF = () => {\n if (raf) return;\n raf = requestAnimationFrame(render);\n };\n const stopRAF = () => {\n if (!raf) return;\n cancelAnimationFrame(raf);\n raf = 0;\n };\n\n const rnd = () => Math.random();\n const wX = (0.3 + rnd() * 0.6) * RSX;\n const wY = (0.2 + rnd() * 0.7) * RSY;\n const wZ = (0.1 + rnd() * 0.5) * RSZ;\n const phX = rnd() * Math.PI * 2;\n const phZ = rnd() * Math.PI * 2;\n\n let yaw = 0,\n pitch = 0,\n roll = 0;\n let targetYaw = 0,\n targetPitch = 0;\n const lerp = (a: number, b: number, t: number) => a + (b - a) * t;\n\n const pointer = { x: 0, y: 0, inside: true };\n const onMove = (e: PointerEvent) => {\n const ww = Math.max(1, window.innerWidth);\n const wh = Math.max(1, window.innerHeight);\n const cx = ww * 0.5;\n const cy = wh * 0.5;\n const nx = (e.clientX - cx) / (ww * 0.5);\n const ny = (e.clientY - cy) / (wh * 0.5);\n pointer.x = Math.max(-1, Math.min(1, nx));\n pointer.y = Math.max(-1, Math.min(1, ny));\n pointer.inside = true;\n };\n const onLeave = () => {\n pointer.inside = false;\n };\n const onBlur = () => {\n pointer.inside = false;\n };\n\n let onPointerMove: ((e: PointerEvent) => void) | null = null;\n if (props.animationType === 'hover') {\n onPointerMove = (e: PointerEvent) => {\n onMove(e);\n startRAF();\n };\n window.addEventListener('pointermove', onPointerMove, { passive: true });\n window.addEventListener('mouseleave', onLeave);\n window.addEventListener('blur', onBlur);\n program.uniforms.uUseBaseWobble.value = 0;\n } else if (props.animationType === '3drotate') {\n program.uniforms.uUseBaseWobble.value = 0;\n } else {\n program.uniforms.uUseBaseWobble.value = 1;\n }\n\n const render = (t: number) => {\n const time = (t - t0) * 0.001;\n program.uniforms.iTime.value = time;\n\n let continueRAF = true;\n\n if (props.animationType === 'hover') {\n const maxPitch = 0.6 * HOVSTR;\n const maxYaw = 0.6 * HOVSTR;\n targetYaw = (pointer.inside ? -pointer.x : 0) * maxYaw;\n targetPitch = (pointer.inside ? pointer.y : 0) * maxPitch;\n const prevYaw = yaw;\n const prevPitch = pitch;\n const prevRoll = roll;\n yaw = lerp(prevYaw, targetYaw, INERT);\n pitch = lerp(prevPitch, targetPitch, INERT);\n roll = lerp(prevRoll, 0, 0.1);\n program.uniforms.uRot.value = setMat3FromEuler(yaw, pitch, roll, rotBuf);\n\n if (NOISE_IS_ZERO) {\n const settled =\n Math.abs(yaw - targetYaw) < 1e-4 && Math.abs(pitch - targetPitch) < 1e-4 && Math.abs(roll) < 1e-4;\n if (settled) continueRAF = false;\n }\n } else if (props.animationType === '3drotate') {\n const tScaled = time * TS;\n yaw = tScaled * wY;\n pitch = Math.sin(tScaled * wX + phX) * 0.6;\n roll = Math.sin(tScaled * wZ + phZ) * 0.5;\n program.uniforms.uRot.value = setMat3FromEuler(yaw, pitch, roll, rotBuf);\n if (TS < 1e-6) continueRAF = false;\n } else {\n rotBuf[0] = 1;\n rotBuf[1] = 0;\n rotBuf[2] = 0;\n rotBuf[3] = 0;\n rotBuf[4] = 1;\n rotBuf[5] = 0;\n rotBuf[6] = 0;\n rotBuf[7] = 0;\n rotBuf[8] = 1;\n program.uniforms.uRot.value = rotBuf;\n if (TS < 1e-6) continueRAF = false;\n }\n\n renderer.render({ scene: mesh });\n if (continueRAF) {\n raf = requestAnimationFrame(render);\n } else {\n raf = 0;\n }\n };\n\n if (props.suspendWhenOffscreen) {\n const io = new IntersectionObserver(entries => {\n const vis = entries.some(e => e.isIntersecting);\n if (vis) startRAF();\n else stopRAF();\n });\n io.observe(container);\n startRAF();\n (container as HTMLElement & { __prismIO?: IntersectionObserver }).__prismIO = io;\n } else {\n startRAF();\n }\n\n cleanup = () => {\n stopRAF();\n ro.disconnect();\n if (props.animationType === 'hover') {\n if (onPointerMove) window.removeEventListener('pointermove', onPointerMove as EventListener);\n window.removeEventListener('mouseleave', onLeave);\n window.removeEventListener('blur', onBlur);\n }\n if (props.suspendWhenOffscreen) {\n const io = (container as HTMLElement & { __prismIO?: IntersectionObserver }).__prismIO as\n | IntersectionObserver\n | undefined;\n if (io) io.disconnect();\n delete (container as HTMLElement & { __prismIO?: IntersectionObserver }).__prismIO;\n }\n if (gl.canvas.parentElement === container) container.removeChild(gl.canvas);\n };\n};\n\nonMounted(() => {\n setup();\n});\n\nonBeforeUnmount(() => {\n cleanup?.();\n});\n\nwatch(\n props,\n () => {\n cleanup?.();\n setup();\n },\n { deep: true }\n);\n</script>\n\n<template>\n <div class=\"relative w-full h-full\" ref=\"containerRef\" />\n</template>\n","path":"Prism/Prism.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"ogl","version":"^1.0.11"}],"devDependencies":[],"categories":["Backgrounds"]} |