How to Animate Scrolling Backgrounds in Three.js

Animating a scrolling background in Three.js is a powerful technique for creating dynamic environments, such as moving skies, infinite roads, or flowing water effects. This article provides a straightforward guide on how to manipulate texture offsets within your rendering loop to achieve a seamless, continuous scrolling effect on your 3D materials.

To create a scrolling background, you must load a texture, enable texture wrapping, and incrementally update the texture’s offset coordinates within your animation loop.

Step 1: Load the Texture and Enable Repeating

By default, textures in Three.js do not repeat. To make a texture scroll infinitely without stretching or stopping, you must configure its wrapping behavior. Set both wrapS (horizontal wrapping) and wrapT (vertical wrapping) to THREE.RepeatWrapping.

import * as THREE from 'three';

// Initialize texture loader
const textureLoader = new THREE.TextureLoader();

// Load your background texture
const backgroundTexture = textureLoader.load('path/to/your/texture.jpg');

// Enable infinite repeating
backgroundTexture.wrapS = THREE.RepeatWrapping;
backgroundTexture.wrapT = THREE.RepeatWrapping;

Step 2: Apply the Texture to a Material

Apply the configured texture to a material, then map that material onto a 3D object. Typically, a flat plane or a large sphere surrounding the scene is used for backgrounds.

// Create a plane geometry for the background
const geometry = new THREE.PlaneGeometry(10, 10);

// Create a material using the texture
const material = new THREE.MeshBasicMaterial({ map: backgroundTexture });

// Create the mesh and add it to the scene
const backgroundMesh = new THREE.Mesh(geometry, material);
scene.add(backgroundMesh);

Step 3: Manipulate Offsets in the Animation Loop

The texture.offset property controls the starting point of the UV mapping. It is a THREE.Vector2 object with x (horizontal) and y (vertical) properties. Values range from 0 to 1.

To animate the scroll, increment the x or y offset inside your requestAnimationFrame loop.

// Define the scrolling speed
const scrollSpeedX = 0.05; 
const scrollSpeedY = 0.02;

const clock = new THREE.Clock();

function animate() {
    requestAnimationFrame(animate);

    // Get delta time to ensure smooth animation regardless of frame rate
    const delta = clock.getDelta();

    // Update texture offset
    backgroundTexture.offset.x += scrollSpeedX * delta;
    backgroundTexture.offset.y += scrollSpeedY * delta;

    // Optional: Reset offset to keep values small and prevent precision loss
    backgroundTexture.offset.x %= 1;
    backgroundTexture.offset.y %= 1;

    // Render the scene
    renderer.render(scene, camera);
}

animate();

Using clock.getDelta() ensures that the background scrolls at a consistent speed regardless of the user’s monitor refresh rate or system performance. Utilizing the modulo operator (% 1) keeps the offset values between 0 and 1, preventing potential rendering glitches caused by extremely high float values over long periods of execution.