3d model in a page using three.js

Wide transparent graphic asset of a torn paper edge rendered in white, appearing blank against a white background
Thumbnail with a man in a dark jacket on the left over a purple glow with the partly hidden heading Management: Where Talent Meets Opportunity behind him, and a large slanted orange side menu panel listing Home, About Us, Services, Our Work, Careers and Contact Us with Facebook, YouTube, Instagram and LinkedIn links, a yellow CLOSE button at the top right, a magenta circle with a white list icon at the lower left, and model portrait cards visible behind the panel

The Creative Overlay Menu in Elementor — A Full-Screen Menu That Shows Off

Reboot Addons' Creative Overlay Menu: a full-screen Elementor menu with kicker, headline, CTA and eleven animated surface options. No custom ...

Read More
A web-design studio desk with a laptop and monitors showing website layouts and design tools — the kind of monthly managed-build behind the R749 to R1999 plans.

R749 vs R999 vs R1999 — What Each Website Package Actually Gets You

R749, R999, R1499 or R1999 a month — what each Reboot Websites package actually builds, and why the gap between ...

Read More
Tall parallax website demo screenshot for Apple Watch Ultra showing a hero with the heading Apple Watch Ultra: Your Companion for Every Adventure over a close up of the watch face on a wrist, a dark section with the heading A REVOLUTIONARY BLEND OF CUTTING-EDGE TECHNOLOGY AND RUGGED DURABILITY FOR THE MODERN EXPLORER beside a floating watch band image, a section of layered watch photos with translucent highlight text, and a flat lay of the watch with an orange band beside a succulent and a Bullet planner notebook, all inside the WordPress admin bar

Advanced Text FX in Elementor — 58 Text Effects in One Widget

Advanced Text FX gives you 58 text-in effects in one Elementor widget — scramble, typewriter, glitch, neon, and a whole ...

Read More
Thumbnail with a man in a dark shirt on the right, a fan of eight portrait cards of women behind him, large distressed red and orange CARD EFFECT lettering across the bottom, a magenta circle with a white list icon at the lower right, and a blurred purple radial background

Elementor Card Widgets without Custom JS — The Free Tier

Seven content widgets plus all six form stylers that build card effects, carousels, and styled forms without writing JS. Widget ...

Read More
Showing Slide 1 of 5
Thumbnail with a man in a black suit on the left beside a laptop screen with a black humanoid robot climbing out of it, the Elementor logo above and large orange 3 JS text at the bottom right

So if you follow along in the video, I refer to this blog post for you to either enter code at certain points into the HTML widget, so below is everything you will need!

The plugin to allow you to upload the model files into wordpress:

The image I used in the pre loader in the tutorial:

Sketchfab, the website to download your own models:

Models I used in the tutorial:

The Three.js script used to create the whole scene, model, camera, lights … just everything to perfectly handle the object.
This script is without camera controls, better for just displaying objects to the user.

				
					<div id="canvas-container-2" style="width: 100%; height: 100%; position: relative;max-height:100vh;">
    <div id="loading-screen" style="width: 100%; height: 90%; background-color: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; z-index: 1000;">
        <div style="text-align: center; color: white;">
            <img decoding="async" data-src="http://video-on-scroll.local/wp-content/uploads/2024/09/futuristic-tunnel-corridor-with-neon-glowing-light-2023-11-27-04-53-49-utc.jpeg" alt="Loading" style="max-width: 100%; max-height: 100%;" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==" class="lazyload">
            <p id="loading-progress">Loading 3D Model: 0%</p>
        </div>
    </div>
    <canvas id="cubeCanvas2" style="width: 100%; height: 100%;"></canvas>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js"></script>

<script>
// ====== EASY TO MODIFY SETTINGS ======

// Model URL - Change this to your model's URL
const MODEL_URL = 'http://video-on-scroll.local/wp-content/uploads/2024/09/fresh_baked_bread.glb';

// Camera settings
const CAMERA_FOV = 50;
const CAMERA_NEAR = 0.1;
const CAMERA_FAR = 3000;
const CAMERA_DISTANCE_FACTOR_DESKTOP = 0.5; // Control for desktop
const CAMERA_DISTANCE_FACTOR_MOBILE = 0.8; // Control for mobile
const CAMERA_OFFSET_X = 0;
const CAMERA_OFFSET_Y = 0.3;

// Lighting settings
const DIRECTIONAL_LIGHT_COLOR = 0xffffff;
const DIRECTIONAL_LIGHT_INTENSITY = 0.2;
const AMBIENT_LIGHT_COLOR = 0xffffff;
const AMBIENT_LIGHT_INTENSITY = 5;
const ADDITIONAL_LIGHT_COLOR = 0xffffff;
const ADDITIONAL_LIGHT_INTENSITY = 0.5;

// Auto-rotation settings
const AUTO_ROTATE = true;
const AUTO_ROTATE_SPEED = 0.005;

// ====== SCENE SETUP ======

let canvas, renderer, scene, camera, light, ambientLight, additionalLight;
let model, rotationGroup, mixer, clock;

function setupScene() {
    canvas = document.getElementById('cubeCanvas2');
    renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true, antialias: true });
    renderer.setClearColor(0x000000, 0);
    renderer.setPixelRatio(window.devicePixelRatio);

    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera(CAMERA_FOV, canvas.clientWidth / canvas.clientHeight, CAMERA_NEAR, CAMERA_FAR);

    light = new THREE.DirectionalLight(DIRECTIONAL_LIGHT_COLOR, DIRECTIONAL_LIGHT_INTENSITY);
    light.position.set(1, 1, 1).normalize();
    scene.add(light);

    ambientLight = new THREE.AmbientLight(AMBIENT_LIGHT_COLOR, AMBIENT_LIGHT_INTENSITY);
    scene.add(ambientLight);

    additionalLight = new THREE.DirectionalLight(ADDITIONAL_LIGHT_COLOR, ADDITIONAL_LIGHT_INTENSITY);
    additionalLight.position.set(-1, -1, 5).normalize();
    scene.add(additionalLight);

    rotationGroup = new THREE.Group();
    scene.add(rotationGroup);

    clock = new THREE.Clock();

    resizeCanvas();
}

function fitModelToCanvas() {
    if (!model) return;

    const box = new THREE.Box3().setFromObject(model);
    const center = box.getCenter(new THREE.Vector3());

    model.position.sub(center);

    // Set camera distance based on whether it's mobile or desktop
    const isMobile = window.innerWidth <= 768;
    let distance = isMobile ? CAMERA_DISTANCE_FACTOR_MOBILE : CAMERA_DISTANCE_FACTOR_DESKTOP;

    // Set camera position
    camera.position.set(CAMERA_OFFSET_X, CAMERA_OFFSET_Y, distance);

    camera.lookAt(new THREE.Vector3(0, 0, 0));

    // Update camera properties
    camera.updateProjectionMatrix();

    // Ensure the renderer fits the canvas size
    renderer.setSize(canvas.clientWidth, canvas.clientHeight);
}

function animate() {
    requestAnimationFrame(animate);

    if (mixer) {
        mixer.update(clock.getDelta());
    }

    if (AUTO_ROTATE) {
        rotationGroup.rotation.y += AUTO_ROTATE_SPEED;
    }

    renderer.render(scene, camera);
}

function resizeCanvas() {
    if (canvas && renderer && camera) {
        camera.aspect = canvas.clientWidth / canvas.clientHeight;
        camera.updateProjectionMatrix();

        renderer.setSize(canvas.clientWidth, canvas.clientHeight);

        if (model) {
            fitModelToCanvas();
        }
    }
}

function updateLoadingScreen(progress) {
    const progressText = document.getElementById('loading-progress');
    if (progressText) {
        progressText.textContent = `Loading 3D Model: ${Math.round(progress * 100)}%`;
    }
}

function hideLoadingScreen() {
    const loadingScreen = document.getElementById('loading-screen');
    if (loadingScreen) {
        loadingScreen.style.display = 'none';
    }
}

function startModelLoad() {
    const loadingManager = new THREE.LoadingManager();

    loadingManager.onProgress = function (url, itemsLoaded, itemsTotal) {
        updateLoadingScreen(itemsLoaded / itemsTotal);
    };

    loadingManager.onLoad = function () {
        hideLoadingScreen();
    };

    const loader = new THREE.GLTFLoader(loadingManager);

    loader.load(MODEL_URL, 
        function (gltf) {
            model = gltf.scene;
            rotationGroup.add(model);

            fitModelToCanvas();

            if (gltf.animations && gltf.animations.length) {
                mixer = new THREE.AnimationMixer(model);
                const animation = gltf.animations[0];
                const action = mixer.clipAction(animation);
                action.play();
            }

            animate(); // Start the animation loop
        },
        null, // Removed the console log for loading progress
        function (error) {
            console.error('An error occurred while loading the model:', error);
        }
    );
}

function initiateBackgroundLoading() {
    setupScene();
    startModelLoad();
}

// Initialize everything
initiateBackgroundLoading();
window.addEventListener('resize', resizeCanvas);
</script>

				
			

The Three.js script used to create the whole scene, model, camera, lights … just everything to perfectly handle the object.
This script is without camera controls, better for just displaying objects to the user.

				
					<div id="canvas-container-2" style="width: 100%; height: 100%; position: relative;max-height:100vh;">
    <div id="loading-screen" style="width: 100%; height: 90%; background-color: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; z-index: 1000;">
        <div style="text-align: center; color: white;">
            <img decoding="async" data-src="http://video-on-scroll.local/wp-content/uploads/2024/09/futuristic-tunnel-corridor-with-neon-glowing-light-2023-11-27-04-53-49-utc.jpeg" alt="Loading" style="max-width: 100%; max-height: 100%;" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==" class="lazyload">
            <p id="loading-progress">Loading 3D Model: 0%</p>
        </div>
    </div>
    <canvas id="cubeCanvas2" style="width: 100%; height: 100%;"></canvas>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js"></script>

<script>
// ====== EASY TO MODIFY SETTINGS ======

// Model URL - Change this to your model's URL
const MODEL_URL = 'http://video-on-scroll.local/wp-content/uploads/2024/09/fresh_baked_bread.glb';

// Camera settings
const CAMERA_FOV = 50;
const CAMERA_NEAR = 0.1;
const CAMERA_FAR = 3000;
const CAMERA_DISTANCE_FACTOR_DESKTOP = 0.5; // Control for desktop
const CAMERA_DISTANCE_FACTOR_MOBILE = 0.8; // Control for mobile
const CAMERA_OFFSET_X = 0;
const CAMERA_OFFSET_Y = 0.3;

// Lighting settings
const DIRECTIONAL_LIGHT_COLOR = 0xffffff;
const DIRECTIONAL_LIGHT_INTENSITY = 0.2;
const AMBIENT_LIGHT_COLOR = 0xffffff;
const AMBIENT_LIGHT_INTENSITY = 5;
const ADDITIONAL_LIGHT_COLOR = 0xffffff;
const ADDITIONAL_LIGHT_INTENSITY = 0.5;

// Auto-rotation settings
const AUTO_ROTATE = true;
const AUTO_ROTATE_SPEED = 0.005;

// ====== SCENE SETUP ======

let canvas, renderer, scene, camera, light, ambientLight, additionalLight;
let model, rotationGroup, mixer, clock;

// Track initial camera distance to ensure zooming starts from this position
let initialCameraZ = null; 

function setupScene() {
    canvas = document.getElementById('cubeCanvas2');
    renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true, antialias: true });
    renderer.setClearColor(0x000000, 0);
    renderer.setPixelRatio(window.devicePixelRatio);

    // Prevent context menu on right-click
    canvas.addEventListener('contextmenu', (e) => e.preventDefault());

    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera(CAMERA_FOV, canvas.clientWidth / canvas.clientHeight, CAMERA_NEAR, CAMERA_FAR);

    // Setup lighting
    light = new THREE.DirectionalLight(DIRECTIONAL_LIGHT_COLOR, DIRECTIONAL_LIGHT_INTENSITY);
    light.position.set(1, 1, 1).normalize();
    scene.add(light);

    ambientLight = new THREE.AmbientLight(AMBIENT_LIGHT_COLOR, AMBIENT_LIGHT_INTENSITY);
    scene.add(ambientLight);

    additionalLight = new THREE.DirectionalLight(ADDITIONAL_LIGHT_COLOR, ADDITIONAL_LIGHT_INTENSITY);
    additionalLight.position.set(-1, -1, 5).normalize();
    scene.add(additionalLight);

    rotationGroup = new THREE.Group();
    scene.add(rotationGroup);

    clock = new THREE.Clock();

    resizeCanvas();
}


function fitModelToCanvas() {
    if (!model) return;

    const box = new THREE.Box3().setFromObject(model);
    const center = box.getCenter(new THREE.Vector3());

    model.position.sub(center);

    // Set camera distance based on whether it's mobile or desktop
    const isMobile = window.innerWidth <= 768;
    let distance = isMobile ? CAMERA_DISTANCE_FACTOR_MOBILE : CAMERA_DISTANCE_FACTOR_DESKTOP;

    // Set camera position
    camera.position.set(CAMERA_OFFSET_X, CAMERA_OFFSET_Y, distance);

    // Store the initial camera Z position to control zoom properly
    if (initialCameraZ === null) {
        initialCameraZ = camera.position.z;  // Store this once during setup
    }

    camera.lookAt(new THREE.Vector3(0, 0, 0));

    // Update camera properties
    camera.updateProjectionMatrix();

    // Ensure the renderer fits the canvas size
    renderer.setSize(canvas.clientWidth, canvas.clientHeight);
}

function animate() {
    requestAnimationFrame(animate);

    if (mixer) {
        mixer.update(clock.getDelta());
    }

    if (AUTO_ROTATE) {
        rotationGroup.rotation.y += AUTO_ROTATE_SPEED;
    }

    renderer.render(scene, camera);
}

function resizeCanvas() {
    if (canvas && renderer && camera) {
        camera.aspect = canvas.clientWidth / canvas.clientHeight;
        camera.updateProjectionMatrix();

        renderer.setSize(canvas.clientWidth, canvas.clientHeight);

        if (model) {
            fitModelToCanvas();
        }
    }
}

function updateLoadingScreen(progress) {
    const progressText = document.getElementById('loading-progress');
    if (progressText) {
        progressText.textContent = `Loading 3D Model: ${Math.round(progress * 100)}%`;
    }
}

function hideLoadingScreen() {
    const loadingScreen = document.getElementById('loading-screen');
    if (loadingScreen) {
        loadingScreen.style.display = 'none';
    }
}

function startModelLoad() {
    const loadingManager = new THREE.LoadingManager();

    loadingManager.onProgress = function (url, itemsLoaded, itemsTotal) {
        updateLoadingScreen(itemsLoaded / itemsTotal);
    };

    loadingManager.onLoad = function () {
        hideLoadingScreen();
    };

    const loader = new THREE.GLTFLoader(loadingManager);

    loader.load(MODEL_URL, 
        function (gltf) {
            model = gltf.scene;
            rotationGroup.add(model);

            fitModelToCanvas();

            if (gltf.animations && gltf.animations.length) {
                mixer = new THREE.AnimationMixer(model);
                const animation = gltf.animations[0];
                const action = mixer.clipAction(animation);
                action.play();
            }

            animate(); // Start the animation loop
        },
        null, // Removed the console log for loading progress
        function (error) {
            console.error('An error occurred while loading the model:', error);
        }
    );
}

function initiateBackgroundLoading() {
    setupScene();
    startModelLoad();
}

let isDragging = false;
let isRightClick = false; // Flag for right-click movement
let previousMousePosition = { x: 0, y: 0 };

function onPointerDown(event) {
    isDragging = true;
    autoRotate = false; // Stop auto-rotation when dragging starts
    previousMousePosition = { x: event.clientX, y: event.clientY };

    if (event.button === 2) {
        isRightClick = true; // Right-click detected
    }
}

// Handle mouse movement (left-click for rotation, right-click for camera movement)
function onPointerMove(event) {
    if (!isDragging) return;

    const deltaMove = {
        x: event.clientX - previousMousePosition.x,
        y: event.clientY - previousMousePosition.y
    };

    const rotationSpeed = 0.005;
    const cameraMoveSpeed = 0.001;

    if (isRightClick) {
        // Right-click: Move camera horizontally (X axis) and vertically (Y axis)
        camera.position.x -= deltaMove.x * cameraMoveSpeed;
        camera.position.y += deltaMove.y * cameraMoveSpeed;  // Moving on Y-axis as before
    } else {
        // Left-click: Rotate the model
        rotationGroup.rotation.y += deltaMove.x * rotationSpeed;
        rotationGroup.rotation.x += deltaMove.y * rotationSpeed;
        rotationGroup.rotation.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, rotationGroup.rotation.x)); // Limit vertical rotation
    }

    previousMousePosition = { x: event.clientX, y: event.clientY };
}

function onPointerUp() {
    isDragging = false;
    isRightClick = false; // Reset right-click flag
    autoRotate = true; // Resume auto-rotation when dragging stops
}
function zoom(event) {
    event.preventDefault();

    const zoomSpeed = 0.001;  // Slow down zoom for better control

    // Only modify the Z position from the initial distance, keeping the limits
    let newCameraZ = camera.position.z + event.deltaY * zoomSpeed;

    // Set zoom limits based on the initial camera position
    const minZoom = initialCameraZ * 0.01;  // You can tweak this value for the minimum zoom
    const maxZoom = initialCameraZ * 20;    // You can tweak this value for the maximum zoom

    // Apply the limits to the new Z position
    camera.position.z = Math.max(minZoom, Math.min(maxZoom, newCameraZ));

    camera.lookAt(new THREE.Vector3(0, 0, 0));  // Ensure the camera always focuses on the model center
    camera.updateProjectionMatrix();  // Update camera projection after zooming
}

// Initialize everything
initiateBackgroundLoading();
window.addEventListener('resize', resizeCanvas);
canvas.addEventListener('pointerdown', onPointerDown);
window.addEventListener('pointermove', onPointerMove);
window.addEventListener('pointerup', onPointerUp);
canvas.addEventListener('wheel', zoom);  // Add the zoom event listener

</script>