WebGL animations and 3D effects on website

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Implementing WebGL Animations and 3D Effects on a Website

WebGL is not just "adding beauty." It's a programmable graphics pipeline directly in the browser with GPU access. When you need to render thousands of particles, deform geometry by audio signal, or build interactive 3D scenes without plugins—this is the only tool that delivers without compromises.

WebGL 2.0 is used (95%+ browser support as of 2025), typically via Three.js or directly via WebGL API for non-standard tasks.

Stack and Approaches

Three.js—the de-facto standard for most web projects. Abstracts shaders and buffers, provides scene, camera, lighting. Version r169+ supports WebGPU as an alternative renderer.

Raw WebGL is applied when you need full control: custom geometric primitives, non-standard blend modes, minimal bundle size without unnecessary code.

GLSL shaders are written manually for each effect—universal solutions don't exist here.

// Vertex shader—plane deformation by noise
uniform float uTime;
uniform float uAmplitude;

varying vec2 vUv;

// Simplex noise (embedded as a function)
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }

void main() {
  vUv = uv;

  vec3 pos = position;
  float noise = snoise(vec2(pos.x * 0.5 + uTime * 0.3, pos.y * 0.5));
  pos.z += noise * uAmplitude;

  gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
// Three.js—scene initialization with post-processing
import * as THREE from 'three'
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'

const renderer = new THREE.WebGLRenderer({
  canvas: document.querySelector('#webgl'),
  antialias: true,
  alpha: true,
})
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.toneMapping = THREE.ACESFilmicToneMapping

const composer = new EffectComposer(renderer)
composer.addPass(new RenderPass(scene, camera))
composer.addPass(new UnrealBloomPass(
  new THREE.Vector2(window.innerWidth, window.innerHeight),
  0.8,  // strength
  0.4,  // radius
  0.85  // threshold
))

Typical Effects and Implementation

Shader Background with Noise

One of the most requested effects: animated gradient background that responds to mouse movement. Implemented via PlaneGeometry covering the entire viewport with a fragment shader based on FBM (fractional Brownian motion).

// Fragment shader—color noise
uniform float uTime;
uniform vec2 uMouse;
uniform vec2 uResolution;

varying vec2 vUv;

void main() {
  vec2 uv = vUv;
  vec2 mouse = uMouse / uResolution;

  // UV offset by mouse position
  uv += (mouse - 0.5) * 0.05;

  float noise = fbm(uv * 3.0 + uTime * 0.15);

  vec3 colorA = vec3(0.1, 0.0, 0.4);
  vec3 colorB = vec3(0.0, 0.3, 0.8);
  vec3 colorC = vec3(0.8, 0.1, 0.3);

  vec3 color = mix(colorA, colorB, noise);
  color = mix(color, colorC, smoothstep(0.4, 0.7, noise));

  gl_FragColor = vec4(color, 1.0);
}

Particle System

For 100k+ particles, use BufferGeometry with Float32Array attributes. Animation happens entirely in the vertex shader—CPU is not involved at runtime.

const COUNT = 150000
const positions = new Float32Array(COUNT * 3)
const randoms = new Float32Array(COUNT)

for (let i = 0; i < COUNT; i++) {
  positions[i * 3 + 0] = (Math.random() - 0.5) * 10
  positions[i * 3 + 1] = (Math.random() - 0.5) * 10
  positions[i * 3 + 2] = (Math.random() - 0.5) * 10
  randoms[i] = Math.random()
}

const geometry = new THREE.BufferGeometry()
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
geometry.setAttribute('aRandom', new THREE.BufferAttribute(randoms, 1))

const material = new THREE.ShaderMaterial({
  uniforms: {
    uTime: { value: 0 },
    uSize: { value: 3.0 * renderer.getPixelRatio() },
  },
  vertexShader: particleVertexShader,
  fragmentShader: particleFragmentShader,
  transparent: true,
  depthWrite: false,
  blending: THREE.AdditiveBlending,
})

Image Distortion on Hover

Image texture is loaded as THREE.Texture, deformed via displacement map by cursor position. A "liquid" hover effect.

// Uniforms for passing to shader
const uniforms = {
  uTexture: { value: texture },
  uDisplacement: { value: displacementTexture },
  uMouse: { value: new THREE.Vector2(0, 0) },
  uVelo: { value: 0 },
}

// Track mouse movement velocity
let lastMouse = new THREE.Vector2()
let currentVelo = 0

window.addEventListener('mousemove', (e) => {
  const current = new THREE.Vector2(
    e.clientX / window.innerWidth,
    1.0 - e.clientY / window.innerHeight
  )
  const delta = current.distanceTo(lastMouse)
  currentVelo = Math.min(delta * 10, 1.0)
  lastMouse.copy(current)
  uniforms.uMouse.value.copy(current)
})

React Integration

Via @react-three/fiber (R3F), Three.js integrates into React components declaratively. @react-three/drei provides ready-made helpers: useGLTF, MeshTransmissionMaterial, Float, Environment.

import { Canvas, useFrame } from '@react-three/fiber'
import { useRef } from 'react'
import * as THREE from 'three'

function AnimatedMesh() {
  const meshRef = useRef<THREE.Mesh>(null)

  useFrame(({ clock, pointer }) => {
    if (!meshRef.current) return
    meshRef.current.rotation.y = clock.getElapsedTime() * 0.3
    meshRef.current.position.x = THREE.MathUtils.lerp(
      meshRef.current.position.x,
      pointer.x * 2,
      0.05
    )
  })

  return (
    <mesh ref={meshRef}>
      <icosahedronGeometry args={[1.5, 4]} />
      <meshStandardMaterial
        color="#5500ff"
        wireframe={false}
        roughness={0.1}
        metalness={0.8}
      />
    </mesh>
  )
}

export function Scene() {
  return (
    <Canvas
      camera={{ position: [0, 0, 5], fov: 45 }}
      gl={{ antialias: true, alpha: true }}
      dpr={[1, 2]}
    >
      <ambientLight intensity={0.5} />
      <pointLight position={[10, 10, 10]} intensity={1} />
      <AnimatedMesh />
    </Canvas>
  )
}

Performance

Framerate target—60fps on desktop, 30fps on mobile with automatic quality reduction. Determined via navigator.hardwareConcurrency and benchmark on first render.

Key rules:

  • One drawcall instead of thousands: InstancedMesh for repeating geometry
  • renderer.setPixelRatio(Math.min(devicePixelRatio, 2))—don't render at 3x on Retina without reason
  • Dispose on unmount: geometry.dispose(), material.dispose(), texture.dispose()
  • requestAnimationFrame via Three.js renderer, not custom loop
  • Post-processing only when prefersReducedMotion === false
// Check before initializing heavy effects
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches
const isMobile = /Mobi|Android/i.test(navigator.userAgent)

const config = {
  bloomEnabled: !prefersReduced && !isMobile,
  particleCount: isMobile ? 10000 : 150000,
  pixelRatio: isMobile ? 1 : Math.min(devicePixelRatio, 2),
}

Asset Loading

3D models—.glb format (GLB = binary GLTF). Compression via Draco (geometry) + KTX2 (textures). Loading via GLTFLoader + DRACOLoader.

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js'

const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/')  // wasm in public/

const ktx2Loader = new KTX2Loader()
ktx2Loader.setTranscoderPath('/basis/')
ktx2Loader.detectSupport(renderer)

const loader = new GLTFLoader()
loader.setDRACOLoader(dracoLoader)
loader.setKTX2Loader(ktx2Loader)

loader.load('/models/scene.glb', (gltf) => {
  scene.add(gltf.scene)
}, (progress) => {
  const pct = (progress.loaded / progress.total * 100).toFixed(0)
  onProgress(pct)
})

Timeline and Stages

Prototype with main effect—3–5 days. Full integration into website with responsiveness, fallback for weak devices, bundle optimization—10–20 days depending on scene complexity. Animated hero with shader background and mouse reaction—closer to lower bound. Interactive 3D product model with material configurator—closer to upper bound.