HOW TO CREATE AN INTERACTIVE 3D WEB HERO SECTION WITH THREE.JS

An advanced 3D interactive hero section that combines real-time animations, particle systems, and dynamic connections using Three.js. It delivers a modern and immersive UI experience with smooth transitions and user-responsive effects.

Web Development Moderate HTML CSS JS

Technologies Used

  • HTML – structure of the hero section
  • Three.js – rendering 3D elements and animations
  • JavaScript – controlling interactions and dynamic behavior

HTML Structure Explanation

The HTML structure of this interactive 3D hero section is designed to create a full-screen canvas where animations are rendered. A container element is used to hold the Three.js canvas, which acts as the main visual layer of the hero section. Additional UI elements such as headings, buttons, and statistics are placed above the canvas using proper positioning. This layered structure allows the animation to run in the background while keeping the content readable and interactive. The simplicity of HTML ensures that the focus remains on the animation and user experience.

HTML Code




<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <title>Interactive 3D Web Hero Section | Educrush</title>
    
<link rel="stylesheet" href="style.css">
  </head>
    
  <body>
<div id="hero-container">
    <div class="hero-content">
        <div class="header">
            <h1>Educrush</h1>
            <div class="stats">
                <div class="stat-item">
                    <span id="nodes">0</span>
                    <small>Users</small>
                </div>
                <div class="stat-item">
                    <span id="links">0</span>
                    <small>Projects</small>
                </div>
                <div class="stat-item">
                    <span id="teraflops">0</span>
                    <small>Links</small>
                </div>
            </div>
        </div>
        
        <div class="main-content">
            <div class="main-content-inner">
                <h2>Educrush</h2>
                <p>Transform your learning experience with our cutting-edge educational platform</p>
            </div>
        </div>
        
        <div class="cta-container">
            <a href="#" class="cta">Join the Revolution</a>
            <a href="#" class="secondary-cta">Learn More</a>
        </div>
    </div>
</div>

<script type="importmap">
{
    "imports": {
        "three": "https://unpkg.com/three@0.169.0/build/three.module.js",
        "three/addons/": "https://unpkg.com/three@0.169.0/examples/jsm/"
    }
}
</script>

    <script type="module" src="script.js"></script>
  </body>
  
</html>

CSS Styling Explanation

CSS plays an important role in designing the hero section layout and enhancing the visual appeal. We use full-screen height, flexbox alignment, and absolute positioning to properly place the content over the animated background. Gradients, shadows, and glowing effects are applied to create a modern and futuristic UI. Smooth transitions and hover effects on buttons improve user interaction and make the design more engaging. The combination of dark backgrounds with neon-like particles gives a premium and immersive feel similar to modern tech websites.

CSS Code


 * {
        margin: 0;
        padding: 0;
        border: none;
        box-sizing: border-box;
    }

    #hero-container {
        width: 100vw;
        height: 100vh;
        overflow: hidden;
        background: radial-gradient(circle at center, #3a1cbd, #090418);
        position: fixed;
        top: 0;
        left: 0;
        will-change: transform;
    }

    canvas {
        display: block;
        width: 100vw;
        height: 100vh;
        transition: opacity 0.5s ease;
        opacity: 0;
        animation: canvasFadeIn 2s ease-out forwards;
    }

    .hero-content {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        z-index: 1;
        color: #ffffff;
        font-family: 'Arial', sans-serif;
        pointer-events: none;
        opacity: 0;
        animation: fadeIn 1.5s ease-out 0.5s forwards;
    }

    .header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        backdrop-filter: blur(10px);
        padding: 0.75rem 1.5rem;
        border-radius: 30px;
        background: rgba(30, 20, 60, 0.2);
        border: 1px solid rgba(0, 255, 213, 0.3);
        box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
        transition: all 0.3s ease;
        position: absolute;
        top: 2rem;
        left: 2rem;
        right: 2rem;
        z-index: 10;
    }

    .header:hover {
        background: rgba(58, 28, 189, 0.2);
        box-shadow: 0 8px 25px rgba(0, 255, 213, 0.2);
    }

    .header h1 {
        font-size: 2.5rem;
        font-weight: 900;
        background: linear-gradient(90deg, #00ffd5, #ff2cc4, #00ffd5);
        background-size: 200% 100%;
        -webkit-background-clip: text;
        background-clip: text;
        color: transparent;
        text-shadow: 0 0 30px rgba(0, 234, 255, 0.9);
        animation: titleGlow 2.5s infinite alternate, gradientFlow 5s infinite linear;
        letter-spacing: -0.05em;
    }

    .stats {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 1.5rem;
        background: rgba(25, 15, 60, 0.85);
        padding: 1.5rem;
        border-radius: 30px;
        backdrop-filter: blur(15px);
        border: 1px solid rgba(0, 255, 213, 0.4);
        box-shadow: 0 15px 45px rgba(0, 0, 0, 0.3);
        transition: all 0.3s ease;
    }

    .stat-item {
        text-align: center;
        transition: transform 0.3s ease, opacity 0.3s ease;
        opacity: 0;
        animation: statFade 0.6s ease-out forwards;
        position: relative;
        overflow: hidden;
    }

    .stat-item::after {
        content: '';
        position: absolute;
        top: -50%;
        left: -50%;
        width: 200%;
        height: 200%;
        background: radial-gradient(circle, rgba(0, 255, 213, 0.2) 0%, transparent 70%);
        opacity: 0;
        transition: opacity 0.3s ease;
    }

    .stat-item:hover::after {
        opacity: 1;
    }

    .stat-item:nth-child(1) { animation-delay: 0.3s; }
    .stat-item:nth-child(2) { animation-delay: 0.5s; }
    .stat-item:nth-child(3) { animation-delay: 0.7s; }

    .stat-item:hover {
        transform: scale(1.1) translateY(-5px);
    }

    .stat-item span {
        display: block;
        font-size: 2.2rem;
        color: #00ffd5;
        font-weight: 800;
        text-shadow: 0 0 20px rgba(0, 234, 255, 0.8);
        transition: all 0.3s ease;
    }

    .stat-item:hover span {
        color: #ff2cc4;
        text-shadow: 0 0 25px rgba(255, 51, 204, 0.8);
    }

    .stat-item small {
        font-size: 1rem;
        opacity: 0.9;
        color: #f0f0ff;
        letter-spacing: 0.5px;
        text-transform: uppercase;
        font-weight: 600;
    }

    .main-content {
        position: absolute;
        top: 50%;
        left: 0;
        width: 100%;
        transform: translateY(-50%);
        display: flex;
        align-items: center;
        justify-content: center;
        text-align: center;
        padding: 0 2rem;
        margin-top: 2rem;
    }

    .main-content-inner {
        max-width: 1200px;
        width: 100%;
        opacity: 0;
        animation: fadeInUp 1.2s ease-out 0.7s forwards;
    }

    .main-content h2 {
        font-size: 6rem;
        font-weight: 900;
        background: linear-gradient(45deg, #00ffd5, #ff2cc4, #00ffd5);
        background-size: 200% 100%;
        -webkit-background-clip: text;
        background-clip: text;
        color: transparent;
        text-shadow: 0 0 60px rgba(0, 234, 255, 0.8);
        animation: textPulse 3.5s infinite, gradientFlow 7s infinite linear;
        position: relative;
        letter-spacing: -0.05em;
    }

    .main-content h2::after {
        content: '';
        position: absolute;
        bottom: -10px;
        left: 50%;
        width: 0;
        height: 3px;
        background: linear-gradient(90deg, #00ffd5, #ff2cc4);
        transform: translateX(-50%);
        animation: underlineExpand 4s infinite;
    }

    .main-content p {
        font-size: 1.8rem;
        color: #f0f0ff;
        margin: 2rem auto;
        opacity: 0.95;
        text-shadow: 0 0 25px rgba(255, 255, 255, 0.5);
        line-height: 1.5;
        max-width: 800px;
    }

    .cta-container {
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        display: flex;
        justify-content: center;
        gap: 1.5rem;
        padding-bottom: 3rem;
        z-index: 5;
    }

    .cta {
        pointer-events: auto;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        padding: 1rem 2.5rem;
        font-size: 1.6rem;
        font-weight: 700;
        color: #ffffff;
        background: linear-gradient(45deg, #00ffd5, #ff2cc4);
        background-size: 200% 100%;
        border-radius: 50px;
        text-decoration: none;
        transition: all 0.4s ease;
        box-shadow: 0 8px 25px rgba(0, 234, 255, 0.6);
        text-transform: uppercase;
        letter-spacing: 1.5px;
        position: relative;
        overflow: hidden;
        animation: gradientFlow 6s infinite linear;
    }

    .cta::before {
        content: '';
        position: absolute;
        top: 0;
        left: -100%;
        width: 100%;
        height: 100%;
        background: linear-gradient(120deg, transparent, rgba(255, 255, 255, 0.4), transparent);
        transition: all 0.6s ease;
    }

    .cta:hover {
        transform: translateY(-6px) scale(1.05);
        box-shadow: 0 15px 40px rgba(0, 234, 255, 0.8);
        background-position: 100% 0;
    }

    .cta:hover::before {
        left: 100%;
    }

    .cta:active {
        transform: translateY(-2px) scale(0.98);
        box-shadow: 0 5px 20px rgba(0, 234, 255, 0.5);
    }

    .secondary-cta {
        pointer-events: auto;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        padding: 1rem 2.5rem;
        font-size: 1.6rem;
        font-weight: 700;
        color: #ffffff;
        background: transparent;
        border: 2px solid rgba(0, 255, 213, 0.6);
        border-radius: 50px;
        text-decoration: none;
        transition: all 0.4s ease;
        box-shadow: 0 4px 15px rgba(0, 255, 213, 0.3);
        text-transform: uppercase;
        letter-spacing: 1.5px;
    }

    .secondary-cta:hover {
        transform: translateY(-6px);
        box-shadow: 0 8px 20px rgba(0, 255, 213, 0.4);
        border-color: rgba(0, 255, 213, 0.9);
        background: rgba(0, 255, 213, 0.1);
    }

    .secondary-cta:active {
        transform: translateY(-2px) scale(0.98);
    }

    @keyframes canvasFadeIn {
        from { opacity: 0; }
        to { opacity: 1; }
    }

    @keyframes fadeIn {
        from { opacity: 0; }
        to { opacity: 1; }
    }

    @keyframes fadeInUp {
        from { opacity: 0; transform: translateY(30px); }
        to { opacity: 1; transform: translateY(0); }
    }

    @keyframes statFade {
        from { opacity: 0; transform: translateY(20px); }
        to { opacity: 1; transform: translateY(0); }
    }

    @keyframes titleGlow {
        from { text-shadow: 0 0 20px rgba(0, 234, 255, 0.7); }
        to { text-shadow: 0 0 40px rgba(0, 234, 255, 1); }
    }

    @keyframes textPulse {
        0% { transform: scale(1); }
        50% { transform: scale(1.04); }
        100% { transform: scale(1); }
    }

    @keyframes gradientFlow {
        0% { background-position: 0% 50%; }
        50% { background-position: 100% 50%; }
        100% { background-position: 0% 50%; }
    }

    @keyframes underlineExpand {
        0% { width: 0; }
        50% { width: 60%; }
        100% { width: 0; }
    }

    @media (max-width: 1024px) {
        .header h1 { font-size: 2.2rem; }
        .main-content h2 { font-size: 4.5rem; }
        .main-content p { font-size: 1.5rem; }
        .cta, .secondary-cta { font-size: 1.4rem; padding: 0.9rem 2.2rem; }
        .cta-container { padding-bottom: 4rem; }
    }

    @media (max-width: 768px) {
        .header { 
            flex-direction: column; 
            padding: 0.8rem 1rem; 
            gap: 0.8rem; 
            top: 1.5rem;
            left: 1.5rem;
            right: 1.5rem;
        }
        .header h1 { font-size: 1.8rem; }
        .stats { padding: 1rem; gap: 0.8rem; }
        .main-content { transform: translateY(-50%); margin-top: 2.5rem; }
        .main-content h2 { font-size: 3.2rem; }
        .main-content p { font-size: 1.2rem; margin: 1.5rem auto; }
        .cta-container { 
            flex-direction: column; 
            gap: 1rem; 
            align-items: center; 
            padding-bottom: 5rem;
        }
        .cta, .secondary-cta { 
            padding: 0.8rem 2rem; 
            font-size: 1.3rem; 
            width: 90%; 
            max-width: 280px; 
        }
        .stat-item span { font-size: 1.8rem; }
    }

    @media (max-width: 480px) {
        .header { 
            top: 1rem;
            left: 1rem;
            right: 1rem;
        }
        .header h1 { font-size: 1.6rem; }
        .main-content { transform: translateY(-50%); margin-top: 3rem; }
        .main-content h2 { font-size: 2.5rem; }
        .main-content p { font-size: 1.1rem; }
        .cta, .secondary-cta { 
            padding: 0.7rem 1.5rem; 
            font-size: 1.1rem; 
        }
        .cta-container { 
            padding-bottom: 6rem;
        }
        .stat-item small { font-size: 0.9rem; }
    }

Three.js Animation Explanation

The core of this project is built using Three.js, a powerful JavaScript library for rendering 3D graphics in the browser. A scene, camera, and renderer are created to initialize the 3D environment. Thousands of particles are generated using buffer geometry and displayed as points in 3D space. Dynamic connections between particles are created to simulate a network or galaxy-like structure. Mouse movement and animations are used to make the hero section interactive and responsive. This creates a real-time animated background that reacts smoothly, giving users a visually rich experience.

JavaScript Code




import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'

const container = document.getElementById('hero-container')
let scene, camera, renderer, controls, nodes = [], connections, raycaster, mouse, particleSystem

function init() {
    scene = new THREE.Scene()
    
    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
    camera.position.set(0, 12, 25)

    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
    renderer.setSize(window.innerWidth, window.innerHeight)
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
    container.appendChild(renderer.domElement)

    controls = new OrbitControls(camera, renderer.domElement)
    controls.enableDamping = true
    controls.dampingFactor = 0.2
    controls.maxDistance = 50
    controls.minDistance = 8
    controls.enabled = false

    raycaster = new THREE.Raycaster()
    mouse = new THREE.Vector2()

    const nodeGeometry = new THREE.IcosahedronGeometry(0.35, 3)
    const nodeMaterial = new THREE.MeshBasicMaterial({
        color: 0x00ffd5,
        transparent: true,
        opacity: 0.8
    })

    for (let i = 0; i < 300; i++) {
        const node = new THREE.Mesh(nodeGeometry, nodeMaterial.clone())
        node.position.set(
            (Math.random() - 0.5) * 50,
            (Math.random() - 0.5) * 50,
            (Math.random() - 0.5) * 50
        )
        node.velocity = new THREE.Vector3(
            (Math.random() - 0.5) * 0.04,
            (Math.random() - 0.5) * 0.04,
            (Math.random() - 0.5) * 0.04
        )
        node.rotationSpeed = (Math.random() - 0.5) * 0.02
        node.baseColor = new THREE.Color(0x00ffd5)
        node.hoverColor = new THREE.Color(0xff2cc4)
        node.hoverState = 0
        nodes.push(node)
        scene.add(node)
    }

    const lineMaterial = new THREE.LineBasicMaterial({
        color: 0x00ffd5,
        transparent: true,
        opacity: 0.5
    })
    connections = new THREE.LineSegments(new THREE.BufferGeometry(), lineMaterial)
    scene.add(connections)

    const particleCount = 250
    const particleGeometry = new THREE.BufferGeometry()
    const particlePositions = new Float32Array(particleCount * 3)
    const particleColors = new Float32Array(particleCount * 3)
    
    for (let i = 0; i < particleCount * 3; i += 3) {
        particlePositions[i] = (Math.random() - 0.5) * 80
        particlePositions[i + 1] = (Math.random() - 0.5) * 80
        particlePositions[i + 2] = (Math.random() - 0.5) * 80
        
        const color = Math.random() > 0.5 ? new THREE.Color(0x00ffd5) : new THREE.Color(0xff2cc4)
        particleColors[i] = color.r
        particleColors[i + 1] = color.g
        particleColors[i + 2] = color.b
    }
    
    particleGeometry.setAttribute('position', new THREE.BufferAttribute(particlePositions, 3))
    particleGeometry.setAttribute('color', new THREE.BufferAttribute(particleColors, 3))
    
    const particleMaterial = new THREE.PointsMaterial({
        size: 0.4,
        vertexColors: true,
        transparent: true,
        opacity: 0.7
    })
    
    particleSystem = new THREE.Points(particleGeometry, particleMaterial)
    scene.add(particleSystem)

    const light1 = new THREE.PointLight(0x00ffd5, 2.5, 120)
    light1.position.set(30, 30, 30)
    scene.add(light1)

    const light2 = new THREE.PointLight(0xff2cc4, 2.5, 120)
    light2.position.set(-30, -30, 30)
    scene.add(light2)
    
    const light3 = new THREE.PointLight(0x3a1cbd, 1.5, 150)
    light3.position.set(0, -40, -20)
    scene.add(light3)

    const ambient = new THREE.AmbientLight(0x404040, 0.5)
    scene.add(ambient)

    window.addEventListener('resize', onResize)
    container.addEventListener('mousemove', onMouseMove)
    container.addEventListener('click', onMouseClick)
    
    animateCounter('nodes', 0, nodes.length, 3000)
    setTimeout(() => {
        animateCounter('teraflops', 0, 4200, 2500)
    }, 500)
}

function animateCounter(id, start, end, duration) {
    const element = document.getElementById(id)
    const range = end - start
    const increment = end > start ? 1 : -1
    const stepTime = Math.abs(Math.floor(duration / range))
    let current = start
    
    const timer = setInterval(() => {
        current += increment
        element.textContent = current
        if (current === end) {
            clearInterval(timer)
        }
    }, stepTime)
}

function onResize() {
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()
    renderer.setSize(window.innerWidth, window.innerHeight)
}

function onMouseMove(event) {
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1

    const rotationSpeed = 0.6
    controls.target.x += mouse.x * rotationSpeed
    controls.target.y += mouse.y * rotationSpeed
    controls.target.clamp(new THREE.Vector3(-12, -12, 0), new THREE.Vector3(12, 12, 0))
    camera.position.x += (mouse.x * 18 - camera.position.x) * 0.06
    camera.position.y += (-mouse.y * 18 - camera.position.y) * 0.06
    camera.lookAt(controls.target)
}

function onMouseClick(event) {
    raycaster.setFromCamera(mouse, camera)
    const intersects = raycaster.intersectObjects(nodes)
    if (intersects.length > 0) {
        const node = intersects[0].object
        node.velocity.add(new THREE.Vector3(
            (Math.random() - 0.5) * 0.2,
            (Math.random() - 0.5) * 0.2,
            (Math.random() - 0.5) * 0.2
        ))
    }
}

function animate() {
    requestAnimationFrame(animate)

    raycaster.setFromCamera(mouse, camera)
    const intersects = raycaster.intersectObjects(nodes)

    nodes.forEach(node => {
        node.hoverState *= 0.9
        node.position.add(node.velocity)
        node.rotation.x += node.rotationSpeed
        node.rotation.y += node.rotationSpeed
        
        node.material.color.copy(node.baseColor).lerp(node.hoverColor, node.hoverState)
        
        if (node.position.length() > 35) {
            node.velocity.multiplyScalar(-0.95)
        }
    })

    if (intersects.length > 0) {
        const closest = intersects[0].object
        closest.hoverState = 1
        nodes.forEach(node => {
            const distance = node.position.distanceTo(closest.position)
            if (distance < 6 && node !== closest) {
                node.hoverState = Math.max(node.hoverState, 1 - (distance / 6))
            }
        })
    }

    const positions = []
    let linkCount = 0
    for (let i = 0; i < nodes.length; i++) {
        for (let j = i + 1; j < nodes.length; j++) {
            const dist = nodes[i].position.distanceTo(nodes[j].position)
            if (dist < 8) {
                positions.push(
                    nodes[i].position.x,
                    nodes[i].position.y,
                    nodes[i].position.z,
                    nodes[j].position.x,
                    nodes[j].position.y,
                    nodes[j].position.z
                )
                linkCount++
            }
        }
    }
    connections.geometry.dispose()
    connections.geometry = new THREE.BufferGeometry()
    connections.geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3))

    let currentLinks = parseInt(document.getElementById('links').textContent) || 0
    if (currentLinks !== linkCount) {
        const newValue = currentLinks < linkCount ? 
            Math.min(currentLinks + Math.ceil((linkCount - currentLinks) / 20), linkCount) : 
            Math.max(currentLinks - Math.ceil((currentLinks - linkCount) / 20), linkCount);
        document.getElementById('links').textContent = newValue;
    }
    
    let teraflops = parseInt(document.getElementById('teraflops').textContent) || 0
    const targetTeraflops = Math.floor(Math.sin(Date.now() * 0.0005) * 200 + 4200)
    if (Math.abs(teraflops - targetTeraflops) > 50) {
        const newValue = teraflops < targetTeraflops ? 
            teraflops + Math.ceil((targetTeraflops - teraflops) / 20) : 
            teraflops - Math.ceil((teraflops - targetTeraflops) / 20);
        document.getElementById('teraflops').textContent = newValue;
    }

    controls.update()
    renderer.render(scene, camera)
}

init()
animate()

Conclusion

IIn conclusion, this 3D interactive hero section demonstrates the power of modern web technologies in creating immersive user experiences. By combining Three.js with JavaScript, the project delivers real-time animations, dynamic interactions, and visually rich effects. It showcases advanced concepts such as 3D rendering, particle systems, raycasting, and user interaction handling, making it an excellent example of creative front-end development. This project not only enhances UI/UX design skills but also serves as a strong foundation for building complex and interactive web applications in the future.

Real World Applications

Interactive 3D hero sections are widely used in modern websites, especially in tech startups, portfolios, and SaaS platforms. They help in grabbing user attention instantly and creating a strong first impression. Companies use such designs to showcase innovation, creativity, and technical expertise. This type of hero section is ideal for landing pages, product showcases, and personal portfolios where visual impact matters the most.

For more programming projects, source codes and notes join the EduCrush Telegram channel.

Join Telegram

Join the EduCrush Community

Be the first to access new notes, coding resources, and academic support tools.