Elemental
Code Collection
Wavy 3D Blob Animation Kabarza Rich text edit

Wavy 3D Blob Animation Kabarza Rich text edit

Step-by-Step Setup Guide

This tutorial will walk you through creating a mesmerizing 3D animated blob that responds to scroll events. The blob uses custom WebGL shaders to create smooth distortion effects.

What You'll Learn

  • How to implement Three.js in Webflow
  • Creating custom shader materials
  • Scroll-triggered animations
  • WebGL rendering optimization

Prerequisites

  • Basic understanding of Webflow
  • Familiarity with custom code embedding

Step 1: Add the Required Libraries

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>

First, you need to add the Three.js library to your project. In your Webflow project settings:

  1. Go to Project Settings > Custom Code
  2. Add this to the Head Code section:
‍<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>

Step 2: Create the HTML Structure

copy paste this elemnet into your webflow designer

[c-gsap-dots]

Step 3: Add the CSS Styles

<style>
  :root {
    --blob-bg: transparent; /* set a background if you want */
  }

  .blob-component {
    position: relative;
    width: 100%;
    height: 100vh;
    overflow: hidden;
    background: var(--blob-bg);
  }

  .blob {
    position: relative;
    z-index: 1;           /* content above the canvas */
    width: 100%;
    height: 100%;
  }

  .blob-component canvas {
    position: absolute;
    inset: 0;             /* top:0 right:0 bottom:0 left:0 */
    width: 100%;
    height: 100%;
    display: block;       /* avoid inline-canvas gaps */
    z-index: 0;           /* sits behind .blob */
  }

  /* Optional: quick spacing utilities for content inside .blob */
  .blob [data-pad="sm"] { padding: 8px; }
  .blob [data-pad="md"] { padding: 16px; }
  .blob [data-pad="lg"] { padding: clamp(16px, 3vw, 48px); }
</style>

Add this CSS to your Project Settings > Custom Code > Head Code:

<style>
   .blob-component {
       width: 100%;
       height: 100vh;
       position: relative;
       overflow: hidden;
   }
   .blob {
       width: 100%;
       height: 100%;
       position: relative;
       z-index: 1;
   }
   canvas {
       position: absolute;
       top: 0;
       left: 0;
       width: 100%;
       height: 100%;
   }
</style>

Step 4: Add the JavaScript Code

&const vertexShader = `  
varying vec2 vUv;  
varying float vDistortion;  
uniform float uTime;  
uniform float uFrequency;  
uniform float uAmplitude;  
uniform float uSpeed;  

void main() {    
   vUv = uv;    
   vec3 pos = position;    
   float distortion = sin(pos.x * uFrequency + uTime * uSpeed) *                      
                     sin(pos.y * uFrequency + uTime * uSpeed) *                      
                     sin(pos.z * uFrequency + uTime * uSpeed) *                      
                     uAmplitude;    
   pos += normal * distortion;    
   vDistortion = distortion;    
   gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);  
}`;

const fragmentShader = `  
varying vec2 vUv;  
varying float vDistortion;  
uniform vec3 uLowColor;  
uniform vec3 uHighColor;  

void main() {    
   vec3 color = mix(uLowColor, uHighColor, vDistortion * 0.5);    
   gl_FragColor = vec4(color, 1.0);  
}`;

let scene, camera, renderer, blob, blobMaterial;

function handleScroll() {  
   if (!blob || !blobMaterial) return;    
   const scrollTrigger = document.querySelector('.blob-component');  
   if (!scrollTrigger) return;  
   
   const rect = scrollTrigger.getBoundingClientRect();  
   const windowHeight = window.innerHeight;    
   let scrollProgress = 0;    
   
   if (rect.top <= windowHeight && rect.bottom >= 0) {    
       scrollProgress = (windowHeight - rect.top) / (windowHeight + rect.height);    
       scrollProgress = Math.min(Math.max(scrollProgress, 0), 1);  
   } else if (rect.top > windowHeight) {    
       scrollProgress = 0;  
   } else if (rect.bottom < 0) {    
       scrollProgress = 1;  
   }    
   
   blobMaterial.uniforms.uFrequency.value = 0.3 + (scrollProgress * 0.7);  
   blobMaterial.uniforms.uAmplitude.value = 0.5 + (scrollProgress * 2.5);  
   blobMaterial.uniforms.uSpeed.value = 0.5 + (scrollProgress * 2.0);
}

function handleResize() {  
   if (!camera || !renderer) return;    
   const container = document.querySelector('.blob');  
   if (!container) return;  
   
   camera.aspect = container.clientWidth / container.clientHeight;  
   camera.updateProjectionMatrix();  
   renderer.setSize(container.clientWidth, container.clientHeight);
}

function animate() {  
   if (!scene || !camera || !renderer || !blobMaterial || !blob) return;    
   requestAnimationFrame(animate);  
   
   blobMaterial.uniforms.uTime.value += 0.01;  
   blob.rotation.y = window.scrollY * 0.001;  
   renderer.render(scene, camera);
}

function cleanup() {  
   if (scene) {    
       window.removeEventListener('scroll', handleScroll);    
       window.removeEventListener('resize', handleResize);    
       scene.remove(blob);    
       blob.geometry.dispose();    
       blobMaterial.dispose();    
       renderer.dispose();    
       scene = null;    
       camera = null;    
       renderer = null;    
       blob = null;    
       blobMaterial = null;  
   }
}

function initBlob() {  
   if (scene) return;    
   const container = document.querySelector('.blob');  
   if (!container) return;  
   
   container.style.position = 'relative';  
   container.style.zIndex = '1';  
   container.style.overflow = 'hidden';  
   
   scene = new THREE.Scene();  
   camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);  
   renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });    
   renderer.setSize(container.clientWidth, container.clientHeight);  
   container.appendChild(renderer.domElement);  
   
   blobMaterial = new THREE.ShaderMaterial({    
       uniforms: {      
           uTime: { value: 0 },      
           uFrequency: { value: 0.3 },      
           uAmplitude: { value: 0.5 },      
           uSpeed: { value: 0.5 },      
           uLowColor: { value: new THREE.Color('#AC70F3') },      
           uHighColor: { value: new THREE.Color('#CDA4FF') }    
       },    
       vertexShader,    
       fragmentShader  
   });  
   
   const geometry = new THREE.SphereGeometry(4, 128, 128);  
   blob = new THREE.Mesh(geometry, blobMaterial);  
   scene.add(blob);  
   camera.position.z = 10;  
   
   window.addEventListener('scroll', handleScroll);  
   window.addEventListener('resize', handleResize);  
   window.addEventListener('beforeunload', cleanup);  
   animate();
}

document.addEventListener('DOMContentLoaded', initBlob);
&

Add this JavaScript code to your Project Settings > Custom Code > Before </body> tag:

```js

const vertexShader = `  
varying vec2 vUv;  
varying float vDistortion;  
uniform float uTime;  
uniform float uFrequency;  
uniform float uAmplitude;  
uniform float uSpeed;  

void main() {    
   vUv = uv;    
   vec3 pos = position;    
   float distortion = sin(pos.x * uFrequency + uTime * uSpeed) *                      
                     sin(pos.y * uFrequency + uTime * uSpeed) *                      
                     sin(pos.z * uFrequency + uTime * uSpeed) *                      
                     uAmplitude;    
   pos += normal * distortion;    
   vDistortion = distortion;    
   gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);  
}`;

const fragmentShader = `  
varying vec2 vUv;  
varying float vDistortion;  
uniform vec3 uLowColor;  
uniform vec3 uHighColor;  

void main() {    
   vec3 color = mix(uLowColor, uHighColor, vDistortion * 0.5);    
   gl_FragColor = vec4(color, 1.0);  
}`;

let scene, camera, renderer, blob, blobMaterial;

function handleScroll() {  
   if (!blob || !blobMaterial) return;    
   const scrollTrigger = document.querySelector('.blob-component');  
   if (!scrollTrigger) return;  
   
   const rect = scrollTrigger.getBoundingClientRect();  
   const windowHeight = window.innerHeight;    
   let scrollProgress = 0;    
   
   if (rect.top <= windowHeight && rect.bottom >= 0) {    
       scrollProgress = (windowHeight - rect.top) / (windowHeight + rect.height);    
       scrollProgress = Math.min(Math.max(scrollProgress, 0), 1);  
   } else if (rect.top > windowHeight) {    
       scrollProgress = 0;  
   } else if (rect.bottom < 0) {    
       scrollProgress = 1;  
   }    
   
   blobMaterial.uniforms.uFrequency.value = 0.3 + (scrollProgress * 0.7);  
   blobMaterial.uniforms.uAmplitude.value = 0.5 + (scrollProgress * 2.5);  
   blobMaterial.uniforms.uSpeed.value = 0.5 + (scrollProgress * 2.0);
}

function handleResize() {  
   if (!camera || !renderer) return;    
   const container = document.querySelector('.blob');  
   if (!container) return;  
   
   camera.aspect = container.clientWidth / container.clientHeight;  
   camera.updateProjectionMatrix();  
   renderer.setSize(container.clientWidth, container.clientHeight);
}

function animate() {  
   if (!scene || !camera || !renderer || !blobMaterial || !blob) return;    
   requestAnimationFrame(animate);  
   
   blobMaterial.uniforms.uTime.value += 0.01;  
   blob.rotation.y = window.scrollY * 0.001;  
   renderer.render(scene, camera);
}

function cleanup() {  
   if (scene) {    
       window.removeEventListener('scroll', handleScroll);    
       window.removeEventListener('resize', handleResize);    
       scene.remove(blob);    
       blob.geometry.dispose();    
       blobMaterial.dispose();    
       renderer.dispose();    
       scene = null;    
       camera = null;    
       renderer = null;    
       blob = null;    
       blobMaterial = null;  
   }
}

function initBlob() {  
   if (scene) return;    
   const container = document.querySelector('.blob');  
   if (!container) return;  
   
   container.style.position = 'relative';  
   container.style.zIndex = '1';  
   container.style.overflow = 'hidden';  
   
   scene = new THREE.Scene();  
   camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);  
   renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });    
   renderer.setSize(container.clientWidth, container.clientHeight);  
   container.appendChild(renderer.domElement);  
   
   blobMaterial = new THREE.ShaderMaterial({    
       uniforms: {      
           uTime: { value: 0 },      
           uFrequency: { value: 0.3 },      
           uAmplitude: { value: 0.5 },      
           uSpeed: { value: 0.5 },      
           uLowColor: { value: new THREE.Color('#AC70F3') },      
           uHighColor: { value: new THREE.Color('#CDA4FF') }    
       },    
       vertexShader,    
       fragmentShader  
   });  
   
   const geometry = new THREE.SphereGeometry(4, 128, 128);  
   blob = new THREE.Mesh(geometry, blobMaterial);  
   scene.add(blob);  
   camera.position.z = 10;  
   
   window.addEventListener('scroll', handleScroll);  
   window.addEventListener('resize', handleResize);  
   window.addEventListener('beforeunload', cleanup);  
   animate();
}

document.addEventListener('DOMContentLoaded', initBlob);

```

Related Components:

blur

No items found.

aykut

ay

CSS
HTML
JS
GSAP
Resource Details:
Fast Access:
Menu
[[Profile embed]]