mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 22:49:31 -07:00
1 line
9.4 KiB
JSON
1 line
9.4 KiB
JSON
{"name":"ShapeBlur","title":"ShapeBlur","description":"Morphing blurred geometric shape. The effect occurs on hover.","type":"registry:component","add":"when-added","files":[{"type":"registry:component","role":"file","content":"<template>\n <div ref=\"shapeBlurContainer\" class=\"shape-blur-container\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, watch, useTemplateRef } from 'vue';\nimport * as THREE from 'three';\n\ninterface ShapeBlurProps {\n className?: string;\n variation?: number;\n pixelRatioProp?: number;\n shapeSize?: number;\n roundness?: number;\n borderSize?: number;\n circleSize?: number;\n circleEdge?: number;\n}\n\nconst props = withDefaults(defineProps<ShapeBlurProps>(), {\n className: '',\n variation: 0,\n pixelRatioProp: 2,\n shapeSize: 1.2,\n roundness: 0.4,\n borderSize: 0.05,\n circleSize: 0.3,\n circleEdge: 0.5\n});\n\nconst shapeBlurContainer = useTemplateRef<HTMLDivElement>('shapeBlurContainer');\n\nlet animationFrameId: number;\nlet time = 0;\nlet lastTime = 0;\nlet scene: THREE.Scene;\nlet camera: THREE.OrthographicCamera;\nlet renderer: THREE.WebGLRenderer;\nlet material: THREE.ShaderMaterial;\nlet quad: THREE.Mesh;\nlet resizeObserver: ResizeObserver | null = null;\n\nconst vMouse = new THREE.Vector2();\nconst vMouseDamp = new THREE.Vector2();\nconst vResolution = new THREE.Vector2();\n\nlet w = 1;\nlet h = 1;\n\nconst vertexShader = /* glsl */ `\nvarying vec2 v_texcoord;\nvoid main() {\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n v_texcoord = uv;\n}\n`;\n\nconst fragmentShader = /* glsl */ `\nvarying vec2 v_texcoord;\n\nuniform vec2 u_mouse;\nuniform vec2 u_resolution;\nuniform float u_pixelRatio;\n\nuniform float u_shapeSize;\nuniform float u_roundness;\nuniform float u_borderSize;\nuniform float u_circleSize;\nuniform float u_circleEdge;\n\n#ifndef PI\n#define PI 3.1415926535897932384626433832795\n#endif\n#ifndef TWO_PI\n#define TWO_PI 6.2831853071795864769252867665590\n#endif\n\n#ifndef VAR\n#define VAR 0\n#endif\n\n#ifndef FNC_COORD\n#define FNC_COORD\nvec2 coord(in vec2 p) {\n p = p / u_resolution.xy;\n if (u_resolution.x > u_resolution.y) {\n p.x *= u_resolution.x / u_resolution.y;\n p.x += (u_resolution.y - u_resolution.x) / u_resolution.y / 2.0;\n } else {\n p.y *= u_resolution.y / u_resolution.x;\n p.y += (u_resolution.x - u_resolution.y) / u_resolution.x / 2.0;\n }\n p -= 0.5;\n p *= vec2(-1.0, 1.0);\n return p;\n}\n#endif\n\n#define st0 coord(gl_FragCoord.xy)\n#define mx coord(u_mouse * u_pixelRatio)\n\nfloat sdRoundRect(vec2 p, vec2 b, float r) {\n vec2 d = abs(p - 0.5) * 4.2 - b + vec2(r);\n return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;\n}\nfloat sdCircle(in vec2 st, in vec2 center) {\n return length(st - center) * 2.0;\n}\nfloat sdPoly(in vec2 p, in float w, in int sides) {\n float a = atan(p.x, p.y) + PI;\n float r = TWO_PI / float(sides);\n float d = cos(floor(0.5 + a / r) * r - a) * length(max(abs(p) * 1.0, 0.0));\n return d * 2.0 - w;\n}\n\nfloat aastep(float threshold, float value) {\n float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757;\n return smoothstep(threshold - afwidth, threshold + afwidth, value);\n}\nfloat fill(in float x) { return 1.0 - aastep(0.0, x); }\nfloat fill(float x, float size, float edge) {\n return 1.0 - smoothstep(size - edge, size + edge, x);\n}\nfloat stroke(in float d, in float t) { return (1.0 - aastep(t, abs(d))); }\nfloat stroke(float x, float size, float w, float edge) {\n float d = smoothstep(size - edge, size + edge, x + w * 0.5) - smoothstep(size - edge, size + edge, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nfloat strokeAA(float x, float size, float w, float edge) {\n float afwidth = length(vec2(dFdx(x), dFdy(x))) * 0.70710678;\n float d = smoothstep(size - edge - afwidth, size + edge + afwidth, x + w * 0.5)\n - smoothstep(size - edge - afwidth, size + edge + afwidth, x - w * 0.5);\n return clamp(d, 0.0, 1.0);\n}\n\nvoid main() {\n vec2 st = st0 + 0.5;\n vec2 posMouse = mx * vec2(1., -1.) + 0.5;\n\n float size = u_shapeSize;\n float roundness = u_roundness;\n float borderSize = u_borderSize;\n float circleSize = u_circleSize;\n float circleEdge = u_circleEdge;\n\n float sdfCircle = fill(\n sdCircle(st, posMouse),\n circleSize,\n circleEdge\n );\n\n float sdf;\n if (VAR == 0) {\n sdf = sdRoundRect(st, vec2(size), roundness);\n sdf = strokeAA(sdf, 0.0, borderSize, sdfCircle) * 4.0;\n } else if (VAR == 1) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = fill(sdf, 0.6, sdfCircle) * 1.2;\n } else if (VAR == 2) {\n sdf = sdCircle(st, vec2(0.5));\n sdf = strokeAA(sdf, 0.58, 0.02, sdfCircle) * 4.0;\n } else if (VAR == 3) {\n sdf = sdPoly(st - vec2(0.5, 0.45), 0.3, 3);\n sdf = fill(sdf, 0.05, sdfCircle) * 1.4;\n }\n\n vec3 color = vec3(1.0);\n float alpha = sdf;\n gl_FragColor = vec4(color.rgb, alpha);\n}\n`;\n\nconst onPointerMove = (e: PointerEvent | MouseEvent) => {\n const mount = shapeBlurContainer.value;\n if (!mount) return;\n const rect = mount.getBoundingClientRect();\n vMouse.set(e.clientX - rect.left, e.clientY - rect.top);\n};\n\nconst resize = () => {\n const container = shapeBlurContainer.value;\n if (!container) return;\n\n w = container.clientWidth;\n h = container.clientHeight;\n const dpr = Math.min(window.devicePixelRatio || 1, 2);\n\n renderer.setSize(w, h, false);\n renderer.setPixelRatio(dpr);\n\n camera.left = -w / 2;\n camera.right = w / 2;\n camera.top = h / 2;\n camera.bottom = -h / 2;\n camera.updateProjectionMatrix();\n\n quad.scale.set(w, h, 1);\n vResolution.set(w, h).multiplyScalar(dpr);\n material.uniforms.u_pixelRatio.value = dpr;\n};\n\nconst update = () => {\n time = performance.now() * 0.001;\n const dt = time - lastTime;\n lastTime = time;\n\n vMouseDamp.x = THREE.MathUtils.damp(vMouseDamp.x, vMouse.x, 8, dt);\n vMouseDamp.y = THREE.MathUtils.damp(vMouseDamp.y, vMouse.y, 8, dt);\n\n renderer.render(scene, camera);\n animationFrameId = requestAnimationFrame(update);\n};\n\nconst initShapeBlur = () => {\n const mount = shapeBlurContainer.value;\n if (!mount) return;\n\n scene = new THREE.Scene();\n camera = new THREE.OrthographicCamera();\n camera.position.z = 1;\n\n renderer = new THREE.WebGLRenderer({ alpha: true });\n renderer.setClearColor(0x000000, 0);\n renderer.domElement.style.width = '100%';\n renderer.domElement.style.height = '100%';\n renderer.domElement.style.display = 'block';\n mount.appendChild(renderer.domElement);\n\n const geo = new THREE.PlaneGeometry(1, 1);\n material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n uniforms: {\n u_mouse: { value: vMouseDamp },\n u_resolution: { value: vResolution },\n u_pixelRatio: { value: props.pixelRatioProp },\n u_shapeSize: { value: props.shapeSize },\n u_roundness: { value: props.roundness },\n u_borderSize: { value: props.borderSize },\n u_circleSize: { value: props.circleSize },\n u_circleEdge: { value: props.circleEdge }\n },\n defines: { VAR: props.variation },\n transparent: true\n });\n\n quad = new THREE.Mesh(geo, material);\n scene.add(quad);\n\n document.addEventListener('mousemove', onPointerMove);\n document.addEventListener('pointermove', onPointerMove);\n\n resize();\n\n if (typeof ResizeObserver !== 'undefined') {\n resizeObserver = new ResizeObserver(resize);\n resizeObserver.observe(mount);\n } else {\n window.addEventListener('resize', resize);\n }\n\n update();\n};\n\nconst cleanup = () => {\n if (animationFrameId) {\n cancelAnimationFrame(animationFrameId);\n }\n\n if (resizeObserver) {\n resizeObserver.disconnect();\n } else {\n window.removeEventListener('resize', resize);\n }\n\n document.removeEventListener('mousemove', onPointerMove);\n document.removeEventListener('pointermove', onPointerMove);\n\n const mount = shapeBlurContainer.value;\n if (mount && renderer) {\n mount.removeChild(renderer.domElement);\n renderer.dispose();\n }\n};\n\nwatch(\n () => [\n props.variation,\n props.pixelRatioProp,\n props.shapeSize,\n props.roundness,\n props.borderSize,\n props.circleSize,\n props.circleEdge\n ],\n () => {\n if (material) {\n material.uniforms.u_pixelRatio.value = props.pixelRatioProp;\n material.uniforms.u_shapeSize.value = props.shapeSize;\n material.uniforms.u_roundness.value = props.roundness;\n material.uniforms.u_borderSize.value = props.borderSize;\n material.uniforms.u_circleSize.value = props.circleSize;\n material.uniforms.u_circleEdge.value = props.circleEdge;\n material.defines.VAR = props.variation;\n material.needsUpdate = true;\n }\n },\n { deep: true }\n);\n\nonMounted(() => {\n initShapeBlur();\n});\n\nonUnmounted(() => {\n cleanup();\n});\n</script>\n\n<style scoped>\n.shape-blur-container {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n}\n\n.shape-blur-container :deep(canvas) {\n width: 100% !important;\n height: 100% !important;\n display: block;\n}\n</style>\n","path":"ShapeBlur/ShapeBlur.vue","_imports_":[],"registryDependencies":[],"dependencies":[],"devDependencies":[]}],"registryDependencies":[],"dependencies":[{"ecosystem":"js","name":"three","version":"^0.178.0"}],"devDependencies":[],"categories":["Animations"]} |