Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div id="canvas-wrapper" aria-label="Bubbles Lamp"></div>

<div class="toggle toggle--anim p-flex-hzt-center" id="toggle-anim">
  <button class="toggle__btn toggle__btn--anim p-text" type="button" data-action="pause" title="pause">||</button>
</div>

<div class="toggle p-flex-hzt-center" id="toggle-color">
	<button class="toggle__btn p-text" type="button" data-mode="monochrome" data-active="true">monochrome</button>
	<button class="toggle__btn p-text" type="button" data-mode="deep-colored">deep-colored</button>
</div>

<p class="credit p-text"><a href="https://www.ilithya.rocks/" target="blank" rel="external noopener" class="credit__link">by ilithya.rocks</a></p>
              
            
!

CSS

              
                $c1: hotpink;
$c2: #fffffd; // white

* {
	user-select: none;
}

.p-text {
	color: $c2;
	font-family: Helvetica, Arial, sans-serif;
	font-size: 0.55rem;
	letter-spacing: 2px;
	line-height: 1.5;
	text-align: center;
	text-transform: lowercase;
	-webkit-tap-highlight-color: rgba(0,0,0,0);
}

.p-flex-hzt-center {
	display: flex;
	justify-content: center;
}

body {
	height: 100vh;
	background-color: $c1;
	margin: 0;
	padding: 0;
	overflow: hidden;
	position: relative;
}

canvas {
	cursor: grab;
}

.toggle {
	width: 100%;
	position: absolute;
	bottom: 36px;
	left: 0;
	z-index: 90;
	
	&--anim {
		bottom: 70px;
	}
	
	&__btn {
		$gap: 5px;
		$gap_anim: 2px;
		
		background-color: $c1;
		border: 1px solid $c2;
		cursor: pointer;
		outline: none;
		padding: $gap round($gap * 2.2) $gap round($gap * 2.5);
		transition: background-color 0.2s ease-in-out;

		&--anim {
			border-color: $c1;
			font-size: 0.8rem;
			padding: $gap_anim round($gap_anim * 3) $gap_anim round($gap_anim * 5.8);
			
			&[data-action="pause"] {
				padding-right: round($gap_anim * 4.3);
			}
		}
		
		&:not(.toggle__btn--anim) {
			&:last-child {
				border-left: 0;
			}
		}
		
		&[data-active],
		&[data-action] {
			background-color: $c2;	
			color: $c1;
			font-weight: bold;
		}
	}
}

.credit {	
	width: 100%;
	position: absolute;
	bottom: 5px;
	left: 0;
	
	&__link {
		color: rgba($c2, 0.6);
		padding: 6px 15px 8px;
		text-decoration: none;
	}
}
              
            
!

JS

              
                /*
 * BUBBLES LAMP
 * 3D code art inspired by a real-life lamp from home.
 *
 * - Rotate the lamp manually by dragging your cursor or dragging with a finger on touch devices.
 * - Zoom in/out inside/outside the lamp with mouse wheel or a two fingers gesture in trackpad and touch devices.
 * - Press the play/stop button to toggle all animations.
 * - Press buttons to change the lamp's mood: monochrome pink vs. deep-colored <3.
 *
 * Some coding techniques are taken after Matt Deslauriers' course:
 * https://frontendmasters.com/teachers/matt-deslauriers/
 *
 * #057 - #100DaysOfCode
 * By ilithya | 2020
 * https://www.ilithya.rocks/
 * https://twitter.com/ilithya_net
 */

// COLOR PALETTE
const defaultColor = "hotpink";
const lightColor = "#ff69b4"; // Light pink
const softDefaultColor = "#ff77bb"; // Lighter pink
const deepLightColor = "orange";
const solidDefaultColor = "#ffc338"; // Matte yellow

// GLOBAL
const nearDist = 1;
const farDist = 100;
const camera = new THREE.PerspectiveCamera(
	45,
	window.innerWidth / window.innerHeight,
	nearDist,
	farDist
);
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({ antialias: true });
const canvasWrapper = document.querySelector("#canvas-wrapper");
const clock = new THREE.Clock();
const controls = new THREE.OrbitControls(camera, renderer.domElement);

const init = () => {
	camera.position.set(2, 2, -5);
	camera.lookAt(new THREE.Vector3()); // Look at the origin of the world
	
	renderer.setClearColor(defaultColor);
	renderer.setSize(window.innerWidth, window.innerHeight);

	canvasWrapper.appendChild(renderer.domElement);
};
init();

function getRandomArbitrary(min, max) {
  return Math.random() * (max - min) + min;
}

const group = new THREE.Group();

// 3D LAMP
const radius = 1;
const widthSeg = radius * 32;
const heightSeg = widthSeg;
const sphereGeom = new THREE.SphereBufferGeometry(radius, widthSeg, heightSeg);
const materialMono = new THREE.MeshPhongMaterial({
	side: THREE.FrontSide,
	transparent: true,
	opacity: .95,
	emissive: softDefaultColor,
	shininess: 30,
});
const materialDeep = new THREE.MeshPhongMaterial({
	color: deepLightColor,
	side: THREE.FrontSide,
	transparent: true,
	opacity: .95,
	emissive: solidDefaultColor,
	shininess: 40,
});
const sphere = new THREE.Mesh(sphereGeom, materialMono);
group.add(sphere);

const icosahedronGeom = new THREE.IcosahedronGeometry(radius, 1);
const points = icosahedronGeom.vertices;

let bubbbles;
points.forEach(point => {
	// I learned this with Matt Deslauriers
	bubbbles = new THREE.Mesh(sphereGeom, materialMono);
	bubbbles.position.copy(point).multiplyScalar(1.1); // multiplyScalar - moves bubbles outside sphere diameter
	bubbbles.scale.setScalar(getRandomArbitrary(0.3, 0.5));
	group.add(bubbbles);
});

scene.add(group);

// LIGHT
const Light = function () {
	return new THREE.PointLight(lightColor, 1, 0);
};
const lightDepth = 1.5;

// Lights to rotate in the x axis
const lightA = new Light();
lightA.position.set(0, 0, lightDepth);
scene.add(lightA);

const lightB = new Light();
lightB.position.set(lightDepth, 0, 0);
scene.add(lightB);

// Lights to rotate in the y axis
const lightZ = new Light();
lightZ.position.set(0, 0, lightDepth);
scene.add(lightZ);

const lightY = new Light();
lightY.position.set(0, lightDepth, 0);
scene.add(lightY);

// LAMP COLOR & ANIMATION TOGGLE
let currentTime;
let IS_ANIMATED = true;
const toggle = {
	btnAnim: document.querySelector("#toggle-anim button"),
	btnColor: document.querySelectorAll("#toggle-color button"),
	updateMaterial(mode) {
		const toggleMaterial =
			mode === "monochrome" ? materialMono : materialDeep;

		const obj = group.children;
		const raycaster = new THREE.Raycaster();
		const intersects = raycaster.intersectObjects(obj, true);

		obj.forEach((el) => {
			el.material = toggleMaterial;
			el.material.needsUpdate = true;
		});
	},
	checkActiveBtnColor() {
		this.btnColor.forEach((el) => {
			el.addEventListener("click", (e) => {
				e.preventDefault();

				const target = e.currentTarget;

				this.btnColor.forEach((l) => delete l.dataset.active);
				target.dataset.active = true;

				this.updateMaterial(target.dataset.mode);
			});
		});
	},
	checkActiveBtnAnim() {
		this.btnAnim.addEventListener("click", (e) => {
			e.preventDefault();

			const target = e.currentTarget;
			const txtPlay = `play`;
			const txtPause = `pause`;
			const iconPlay = `►`;
			const iconPause = `||`;
			
			function setBtnData(btn, icon) {
				target.dataset.action = btn;
				target.title = btn;
				target.textContent = icon;
			}
		
			if (target.dataset.action === 'pause') {
				setBtnData(txtPlay, iconPlay);
				IS_ANIMATED = false;
				clock.stop();
				currentTime = clock.elapsedTime;
			} else {
				setBtnData(txtPause, iconPause);
				IS_ANIMATED = true;
				clock.start();
				clock.elapsedTime = currentTime;
			}
		});
	}
};
toggle.checkActiveBtnColor();
toggle.checkActiveBtnAnim();

// SCREEN RESIZE
const onWindowResize = () => {
	const w = window.innerWidth;
	const h = window.innerHeight;
	
	camera.aspect = w / h;
	camera.updateProjectionMatrix();

	renderer.setSize(w, h);	
	renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
};
window.addEventListener("resize", onWindowResize);

// CONTROLS INTERACTION
const createControls = () => {
	// Make sure to run controls outside render function 	
	// If edit controls update controls while rendering
	controls.autoRotateSpeed = 3.0;
	controls.enableDamping = true;
	controls.dampingFactor = 0.15;
	controls.enableZoom = true;
	controls.minDistance = 1;
	controls.maxDistance = 8;
	controls.keyPanSpeed = 30;
};
createControls();

// CREATE ANIMATIONS
const createAnimLights = () => {
	const orbitAngle = clock.getElapsedTime()/2;
	
	if (IS_ANIMATED) {	
		// Lights rotating in the x axis
		lightA.position.x = Math.cos(orbitAngle) * lightDepth*-1;
		lightA.position.z = Math.sin(orbitAngle) * lightDepth;
		
		lightB.position.x = Math.cos(orbitAngle) * lightDepth;
		lightB.position.z = Math.sin(orbitAngle) * lightDepth*-1;
		
		// Lights rotating in the y axis
		lightZ.position.y = Math.cos(orbitAngle) * lightDepth*-1;
		lightZ.position.z = Math.sin(orbitAngle) * lightDepth;
		
		lightY.position.y = Math.cos(orbitAngle) * lightDepth;
		lightY.position.z = Math.sin(orbitAngle) * lightDepth*-1;
	} 
};

// RENDER 3D LAMP
const render = () => {	
	createAnimLights();
	
	controls.autoRotate = IS_ANIMATED ? true : false;
	controls.update();
	
	renderer.render(scene, camera);

	requestAnimationFrame(render);
};
render();
              
            
!
999px

Console