Follow Us On Telegram
Get Full Source Code Zipped File
Telegram link
HTML
<!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <title>CodeAtNow - Gooey Effect </title> <link rel="stylesheet" href="./style.css"> </head> <body> <!-- partial:index.partial.html --> <section class="container"> <article class="tile"> <figure class="tile__figure"> <img src="https://res.cloudinary.com/dz37h4oq1/image/upload/v1683129589/codepen/met-gala/dua-lipa-Met-Gala-2023-04020e7b2705411d9ac2db9e4ad12686_bowy3o.jpg" data-hover="https://res.cloudinary.com/dz37h4oq1/image/upload/v1683051832/codepen/met-gala/dua-lipa_bwoq6z.jpg" class="tile__image" alt="My image" width="400" height="300" /> </figure> </article> </section> <canvas id="stage"></canvas> <!-- partial --> <script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/r124/three.min.js'></script> <script src='https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js'></script> <script src='https://unpkg.co/gsap@3/dist/gsap.min.js'></script><script src="./script.js"></script> </body> </html>
CSS
.container { display: flex; align-items: center; justify-content: center; width: 100%; height: 100vh; z-index: 10; } body{ background-color: #11131e; } body::-webkit-scrollbar { display: none; } body { -ms-overflow-style: none; scrollbar-width: none; } .tile { width: 35vw; flex: 0 0 auto; } .tile__image { width: 100%; height: 100%; object-fit: cover; object-position: center; } canvas { position: fixed; left: 0; top: 0; width: 100%; height: 100vh; z-index: 9; }
JS
const perspective = 800 const vertexShader = `varying vec2 v_uv; void main() { v_uv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }` const fragmentShader = ` uniform vec2 u_mouse; uniform vec2 u_res; uniform sampler2D u_image; uniform sampler2D u_imagehover; uniform float u_time; varying vec2 v_uv; float circle(in vec2 _st, in float _radius, in float blurriness){ vec2 dist = _st; return 1.-smoothstep(_radius-(_radius*blurriness), _radius+(_radius*blurriness), dot(dist,dist)*4.0); } vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); } vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; } float snoise3(vec3 v) { const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); // First corner vec3 i = floor(v + dot(v, C.yyy) ); vec3 x0 = v - i + dot(i, C.xxx) ; // Other corners vec3 g = step(x0.yzx, x0.xyz); vec3 l = 1.0 - g; vec3 i1 = min( g.xyz, l.zxy ); vec3 i2 = max( g.xyz, l.zxy ); // x0 = x0 - 0.0 + 0.0 * C.xxx; // x1 = x0 - i1 + 1.0 * C.xxx; // x2 = x0 - i2 + 2.0 * C.xxx; // x3 = x0 - 1.0 + 3.0 * C.xxx; vec3 x1 = x0 - i1 + C.xxx; vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y // Permutations i = mod289(i); vec4 p = permute( permute( permute( i.z + vec4(0.0, i1.z, i2.z, 1.0 )) + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); // Gradients: 7x7 points over a square, mapped onto an octahedron. // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) float n_ = 0.142857142857; // 1.0/7.0 vec3 ns = n_ * D.wyz - D.xzx; vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) vec4 x_ = floor(j * ns.z); vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) vec4 x = x_ *ns.x + ns.yyyy; vec4 y = y_ *ns.x + ns.yyyy; vec4 h = 1.0 - abs(x) - abs(y); vec4 b0 = vec4( x.xy, y.xy ); vec4 b1 = vec4( x.zw, y.zw ); //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; vec4 s0 = floor(b0)*2.0 + 1.0; vec4 s1 = floor(b1)*2.0 + 1.0; vec4 sh = -step(h, vec4(0.0)); vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; vec3 p0 = vec3(a0.xy,h.x); vec3 p1 = vec3(a0.zw,h.y); vec3 p2 = vec3(a1.xy,h.z); vec3 p3 = vec3(a1.zw,h.w); //Normalise gradients vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w; // Mix final noise value vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); m = m * m; return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3) ) ); } void main() { // We manage the device ratio by passing PR constant vec2 res = u_res * PR; vec2 st = gl_FragCoord.xy / res.xy - vec2(0.5); // tip: use the following formula to keep the good ratio of your coordinates st.y *= u_res.y / u_res.x; // We readjust the mouse coordinates vec2 mouse = u_mouse * -0.5; vec2 circlePos = st + mouse; float c = circle(circlePos, 0.15, 2.) * 2.5; float offx = v_uv.x + sin(v_uv.y + u_time * .1); float offy = v_uv.y - u_time * 0.1 - cos(u_time * .001) * .01; float n = snoise3(vec3(offx, offy, u_time * .1) * 8.) - 1.; float finalMask = smoothstep(0.4, 0.5, n + pow(c, 2.)); vec4 image = texture2D(u_image, v_uv); vec4 hover = texture2D(u_imagehover, v_uv); vec4 finalImage = mix(image, hover, finalMask); gl_FragColor = finalImage; } ` class Scene { constructor() { this.container = document.getElementById('stage') this.scene = new THREE.Scene() this.renderer = new THREE.WebGLRenderer({ canvas: this.container, alpha: true, }) this.renderer.setSize(window.innerWidth, window.innerHeight) this.renderer.setPixelRatio(window.devicePixelRatio) this.initLights() this.initCamera() this.figure = new Figure(this.scene, () => { this.update() }) } initLights() { const ambientlight = new THREE.AmbientLight(0xffffff, 2) this.scene.add(ambientlight) } initCamera() { const fov = (180 * (2 * Math.atan(window.innerHeight / 2 / perspective))) / Math.PI this.camera = new THREE.PerspectiveCamera(fov, window.innerWidth / window.innerHeight, 1, 1000) this.camera.position.set(0, 0, perspective) } update() { requestAnimationFrame(this.update.bind(this)) this.figure.update() this.renderer.render(this.scene, this.camera) } } class Figure { constructor(scene, cb) { this.$image = document.querySelector('.tile__image') this.scene = scene this.callback = cb this.loader = new THREE.TextureLoader() this.image = this.loader.load(this.$image.src, () => { this.start() }) this.hoverImage = this.loader.load(this.$image.dataset.hover) this.$image.style.opacity = 0 this.sizes = new THREE.Vector2(0, 0) this.offset = new THREE.Vector2(0, 0) this.mouse = new THREE.Vector2(0, 0) window.addEventListener('mousemove', (ev) => { this.onMouseMove(ev) }) } start(){ this.getSizes() this.createMesh() this.callback() } getSizes() { const { width, height, top, left } = this.$image.getBoundingClientRect() this.sizes.set(width, height); this.offset.set(left - window.innerWidth / 2 + width / 2, -top + window.innerHeight / 2 - height / 2) } createMesh() { this.uniforms = { u_image: { type: 't', value: this.image }, u_imagehover: { type: 't', value: this.hoverImage }, u_mouse: { value: this.mouse }, u_time: { value: 0 }, u_res: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) } } this.geometry = new THREE.PlaneBufferGeometry(1, 1, 1, 1) this.material = new THREE.ShaderMaterial({ uniforms: this.uniforms, vertexShader: vertexShader, fragmentShader: fragmentShader, defines: { PR: window.devicePixelRatio.toFixed(1) } }) this.mesh = new THREE.Mesh(this.geometry, this.material) this.mesh.position.set(this.offset.x, this.offset.y, 0) this.mesh.scale.set(this.sizes.x, this.sizes.y, 1) this.scene.add(this.mesh) } onMouseMove(event) { TweenMax.to(this.mouse, 0.4, { x: (event.clientX / window.innerWidth) * 2 - 1, y: -(event.clientY / window.innerHeight) * 2 + 1, }) TweenMax.to(this.mesh.rotation, 0.5, { x: -this.mouse.y * 0.3, y: this.mouse.x * (Math.PI / 6) }) } update() { this.uniforms.u_time.value += 0.01 } } new Scene()