Template Showcase: 3D Crystal Field
A 3D Animated, modern ART with Graphics for Fun.
Complete Project Code
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Interactive Crystal Field</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #000511; /* Deep dark blue */
cursor: none;
}
canvas {
display: block;
}
#intro-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #000511;
z-index: 100;
cursor: default;
}
#intro-screen h1 {
font-family: 'Helvetica Neue', sans-serif;
font-weight: 200;
font-size: 4rem;
color: #fff;
text-shadow: 0 0 10px #00ffff, 0 0 20px #00ffff;
margin: 0;
}
#start-btn {
font-family: 'Helvetica Neue', sans-serif;
font-size: 1.5rem;
color: #00ffff;
background-color: transparent;
border: 2px solid #00ffff;
padding: 10px 30px;
border-radius: 5px;
cursor: pointer;
margin-top: 2.5rem;
transition: all 0.3s ease;
box-shadow: 0 0 15px rgba(0, 255, 255, 0.5);
}
#start-btn:hover {
background-color: #00ffff;
color: #000511;
box-shadow: 0 0 25px #00ffff, 0 0 50px #00ffff;
}
#info {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: rgba(255, 255, 255, 0.7);
font-family: 'Helvetica Neue', sans-serif;
font-size: 1.5rem;
text-align: center;
pointer-events: none;
display: none; /* Hidden by default */
animation: fadeInOut 6s ease-in-out forwards;
}
@keyframes fadeInOut {
0% { opacity: 0; }
20% { opacity: 1; }
80% { opacity: 1; }
100% { opacity: 0; }
}
</style>
</head>
<body>
<div id="intro-screen">
<h1>Crystal Field</h1>
<button id="start-btn">Enter</button>
</div>
<div id="info">Move your cursor to illuminate the scene</div>
<!-- three.js library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// --- Global Variables ---
let scene, camera, renderer;
let crystals = [];
let mouseLight;
let introScreen, startButton, infoDiv, canvasElement;
// Mouse coordinates
const mouse = { x: 0, y: 0 };
function init() {
// Scene
scene = new THREE.Scene();
// Camera
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 50;
// Renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
canvasElement = renderer.domElement;
canvasElement.style.display = 'none'; // Hide canvas initially
document.body.appendChild(canvasElement);
// Get DOM elements
introScreen = document.getElementById('intro-screen');
startButton = document.getElementById('start-btn');
infoDiv = document.getElementById('info');
// --- Lighting ---
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambientLight);
mouseLight = new THREE.PointLight(0x00ffff, 2, 100, 2);
mouseLight.position.set(0, 0, 20);
scene.add(mouseLight);
// --- Object Creation (Crystals) ---
const crystalGeometry = [
new THREE.IcosahedronGeometry(1, 0),
new THREE.OctahedronGeometry(1, 0),
new THREE.DodecahedronGeometry(1, 0)
];
for (let i = 0; i < 150; i++) {
const material = new THREE.MeshStandardMaterial({
color: 0xffffff,
metalness: 1.0,
roughness: 0.2,
flatShading: true
});
const geometry = crystalGeometry[Math.floor(Math.random() * crystalGeometry.length)];
const crystal = new THREE.Mesh(geometry, material);
crystal.position.set((Math.random() - 0.5) * 100, (Math.random() - 0.5) * 100, (Math.random() - 0.5) * 100);
crystal.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, Math.random() * Math.PI);
const scale = Math.random() * 2 + 1;
crystal.scale.set(scale, scale, scale);
crystal.userData.rotationSpeed = { x: (Math.random() - 0.5) * 0.01, y: (Math.random() - 0.5) * 0.01 };
crystals.push(crystal);
scene.add(crystal);
}
// --- Event Listeners ---
startButton.addEventListener('click', startExperience);
canvasElement.addEventListener('click', endExperience);
window.addEventListener('resize', onWindowResize, false);
window.addEventListener('mousemove', onMouseMove, false);
animate();
}
// --- Experience Control Functions ---
function startExperience() {
introScreen.style.display = 'none';
canvasElement.style.display = 'block';
infoDiv.style.display = 'block';
// Reset the fade-out animation for the info text
infoDiv.style.animation = 'none';
void infoDiv.offsetWidth; // Trigger reflow to restart animation
infoDiv.style.animation = 'fadeInOut 6s ease-in-out forwards';
}
function endExperience() {
introScreen.style.display = 'flex';
canvasElement.style.display = 'none';
infoDiv.style.display = 'none';
}
// --- Event Handlers ---
function onWindowResize() {
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;
}
// --- Animation Loop ---
function animate() {
requestAnimationFrame(animate);
crystals.forEach(crystal => {
crystal.rotation.x += crystal.userData.rotationSpeed.x;
crystal.rotation.y += crystal.userData.rotationSpeed.y;
});
const vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
vector.unproject(camera);
const dir = vector.sub(camera.position).normalize();
const distance = -camera.position.z / dir.z;
const pos = camera.position.clone().add(dir.multiplyScalar(distance));
mouseLight.position.copy(pos);
renderer.render(scene, camera);
}
init();
</script>
</body>
</html>