<script src="https://unpkg.com/three@0.146.0/build/three.js"></script>
<script src="https://unpkg.com/three@0.146.0/examples/js/controls/OrbitControls.js"></script>

<canvas id="c"></canvas>

<div id="content">
	<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - multiple elements - webgl</div>
</div>
body {
	margin: 0;
	background-color: #000;
	color: #fff;
	font-family: Monospace;
	font-size: 13px;
	line-height: 24px;
	overscroll-behavior: none;
}

* {
	box-sizing: border-box;
	-moz-box-sizing: border-box;
}

body {
	background-color: #fff;
	color: #444;
}

a {
	color: #08f;
}

#content {
	position: absolute;
	top: 0; width: 100%;
	z-index: 1;
	padding: 3em 0 0 0;
}

#c {
	position: absolute;
	left: 0;
	width: 100%;
	height: 100%;
}

.list-item {
	display: inline-block;
	margin: 1em;
	padding: 1em;
	box-shadow: 1px 2px 4px 0px rgba(0,0,0,0.25);
}

.list-item > div:nth-child(1) {
	width: 200px;
	height: 200px;
}

.list-item > div:nth-child(2) {
	color: #888;
	font-family: sans-serif;
	font-size: large;
	width: 200px;
	margin-top: 0.5em;
}
// import * as THREE from 'three';
// import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

let canvas, renderer;

const scenes = [];

init();

animate();

function init() {

	canvas = document.getElementById( 'c' );

	const geometries = [
		new THREE.BoxGeometry( 1, 1, 1 ),
		new THREE.SphereGeometry( 0.5, 12, 8 ),
		new THREE.DodecahedronGeometry( 0.5 ),
		new THREE.CylinderGeometry( 0.5, 0.5, 1, 12 )
	];

	const content = document.getElementById( 'content' );

	for ( let i = 0; i < 8; i ++ ) {

		const scene = new THREE.Scene();

		// make a list item
		const element = document.createElement( 'div' );
		element.className = 'list-item';

		const sceneElement = document.createElement( 'div' );
		element.appendChild( sceneElement );

		const descriptionElement = document.createElement( 'div' );
		descriptionElement.innerText = 'Scene ' + ( i + 1 );
		element.appendChild( descriptionElement );

		// the element that represents the area we want to render the scene
		scene.userData.element = sceneElement;
		content.appendChild( element );

		const camera = new THREE.PerspectiveCamera( 50, 1, 1, 10 );
		camera.position.z = 2;
		scene.userData.camera = camera;

		const controls = new THREE.OrbitControls( scene.userData.camera, scene.userData.element );
		controls.minDistance = 2;
		controls.maxDistance = 5;
		controls.enablePan = false;
		controls.enableZoom = false;
		scene.userData.controls = controls;

		// add one random mesh to each scene
		const geometry = geometries[ geometries.length * Math.random() | 0 ];

		const material = new THREE.MeshStandardMaterial( {

			color: new THREE.Color().setHSL( Math.random(), 1, 0.75 ),
			roughness: 0.5,
			metalness: 0,
			flatShading: true

		} );

		scene.add( new THREE.Mesh( geometry, material ) );

		scene.add( new THREE.HemisphereLight( 0xaaaaaa, 0x444444 ) );

		const light = new THREE.DirectionalLight( 0xffffff, 0.5 );
		light.position.set( 1, 1, 1 );
		scene.add( light );

		scenes.push( scene );

	}


	renderer = new THREE.WebGLRenderer( { canvas: canvas, antialias: true } );
	renderer.setClearColor( 0xffffff, 1 );
	renderer.setPixelRatio( window.devicePixelRatio );

}

function updateSize() {

	const width = canvas.clientWidth;
	const height = canvas.clientHeight;

	if ( canvas.width !== width || canvas.height !== height ) {

		renderer.setSize( width, height, false );

	}

}

function animate() {

	render();
	
	setTimeout( animate, 100 );

}

function render() {

	updateSize();

	canvas.style.transform = `translateY(${window.scrollY}px)`;

	renderer.setClearColor( 0xffffff );
	renderer.setScissorTest( false );
	// renderer.clear();

	renderer.setClearColor( 0xe0e0e0 );
	renderer.setScissorTest( true );

	// emulate updating only some scenes that were updated, not all of them every time.
	const scene1 = scenes[Math.round(Math.random() * (scenes.length-1))]
	const scene2 = scenes[Math.round(Math.random() * (scenes.length-1))]
	const scene3 = scenes[Math.round(Math.random() * (scenes.length-1))]
	const scenesToRender = [scene1, scene2, scene3];
	
	scenesToRender.forEach( function ( scene ) {

		// so something moves
		scene.children[ 0 ].rotation.y = Date.now() * 0.001;

		// get the element that is a place holder for where we want to
		// draw the scene
		const element = scene.userData.element;

		// get its position relative to the page's viewport
		const rect = element.getBoundingClientRect();

		// check if it's offscreen. If so skip it
		if ( rect.bottom < 0 || rect.top > renderer.domElement.clientHeight ||
			rect.right < 0 || rect.left > renderer.domElement.clientWidth ) {

			return; // it's off screen

		}

		// set the viewport
		const width = rect.right - rect.left;
		const height = rect.bottom - rect.top;
		const left = rect.left;
		const bottom = renderer.domElement.clientHeight - rect.bottom;

		renderer.setViewport( left, bottom, width, height );
		renderer.setScissor( left, bottom, width, height );

		const camera = scene.userData.camera;

		//camera.aspect = width / height; // not changing in this example
		//camera.updateProjectionMatrix();

		//scene.userData.controls.update();

		renderer.render( scene, camera );

	} );

}
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.