If you’ve ever wanted to spice up your website with something interactive and eye-catching, a 3D rotating planet with rings might just do the trick! In this post, I’ll walk you through a cool Three.js project that creates a Saturn-like model and show you how to embed it into your Webflow site. Plus, I’ll give a shoutout to AI for making this kind of creative coding accessible to everyone—no PhD in computer graphics required!
This code uses Three.js, a JavaScript library for 3D rendering, to create a simple yet stunning Saturn-like planet. Here’s what you’ll see:
The best part? It’s all rendered in your browser, and you can tweak settings like colours, ring size, or rotation speed to match your vibe.
Webflow makes it easy to add custom code to your site, even if you’re not a coding wizard. Here’s a step-by-step guide to get this Saturn model live on your Webflow page:
Note: the model will only be visible in the published site and unfortunetly not in the Webflow editor
<!-- Load Three.js library to handle 3D rendering -->
<script src="https://cdn.jsdelivr.net/npm/three@0.138.1/build/three.min.js"></script>
<!-- Load OrbitControls for mouse-based interaction (rotate, zoom, pan) -->
<script src="https://cdn.jsdelivr.net/npm/three@0.138.1/examples/js/controls/OrbitControls.js"></script>
<!-- Main script to create and interact with the Saturn-like model -->
<script type="module">
// Select the canvas element where the 3D scene will be drawn
const canvas = document.querySelector('canvas.webgl');
// Create a new 3D scene, a container for all objects
const scene = new THREE.Scene();
// Define the planet's color; change this hex code to alter the planet's color
const planetColor = '#fa6b72';
// Create a sphere for the planet: 1 is radius, 32x32 are segments for smoothness
// Increase radius (e.g., 2) for a bigger planet; fewer segments = less smooth
const planetGeometry = new THREE.SphereGeometry(1, 32, 32);
// Apply a material with the chosen color and standard lighting response
const planetMaterial = new THREE.MeshStandardMaterial({ color: planetColor });
// Combine geometry and material to create the planet and add it to the scene
const planet = new THREE.Mesh(planetGeometry, planetMaterial);
scene.add(planet);
// Parameters object to control the rings' appearance
const parameters = {
count: 120000, // Number of ring particles; more = denser, but slower performance
radiusInner: 1.5, // Inner ring radius; increase to start rings farther out
radiusOuter: 6, // Outer ring radius; increase for wider rings
size: 0.02, // Particle size; larger values = bigger particles
randomness: 0.2, // Vertical scatter of particles; higher = more chaotic
randomnessPower: 3, // Scatter distribution; higher = more particles near plane
rings: 3, // Number of ring bands; increase for more rings
insideColor: '#fa6b72', // Inner ring color; matches planet
outsideColor: '#005164' // Outer ring color; teal by default
};
// Variables for ring geometry, material, and points; initialized as null
let ringGeometry = null;
let ringMaterial = null;
let ringPoints = null;
// Vertex shader: Positions and colors ring particles
const vertexShader = `
varying vec3 vColor; // Pass particle color to fragment shader
void main() {
vColor = color; // Assign each particle its color
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); // Transform position
gl_PointSize = 10.0 / -mvPosition.z; // Size particles based on distance
gl_Position = projectionMatrix * mvPosition; // Set screen position
}
`;
// Fragment shader: Makes particles circular with a fade-out effect
const fragmentShader = `
varying vec3 vColor; // Receive color from vertex shader
void main() {
float dist = length(gl_PointCoord - vec2(0.5, 0.5)); // Distance from center
if (dist > 0.5) discard; // Discard outside circle for round shape
gl_FragColor = vec4(vColor, 1.0 - dist); // Color with fade
}
`;
// Function to generate the rings based on parameters
const generateRings = () => {
// Clean up existing rings to avoid memory leaks
if (ringPoints !== null) {
ringGeometry.dispose();
ringMaterial.dispose();
scene.remove(ringPoints);
}
// Create new geometry for ring particles
ringGeometry = new THREE.BufferGeometry();
// Arrays for particle positions (x, y, z) and colors (r, g, b)
const positions = new Float32Array(parameters.count * 3);
const colors = new Float32Array(parameters.count * 3);
// Define colors for gradient effect
const colorInside = new THREE.Color(parameters.insideColor);
const colorOutside = new THREE.Color(parameters.outsideColor);
// Loop to position and color each particle
for (let i = 0; i < parameters.count; i++) {
const i3 = i * 3; // Index for 3D coordinates
const ringIndex = Math.floor(i / (parameters.count / parameters.rings)); // Split into bands
const randomRingOffset = Math.random() * 0.5; // Random variation
const ringRadiusInner = parameters.radiusInner + ringIndex * (1.2 + randomRingOffset); // Inner edge
const ringRadiusOuter = ringRadiusInner + 1 + randomRingOffset; // Outer edge
const radius = ringRadiusInner + Math.pow(Math.random() * (ringRadiusOuter - ringRadiusInner), 1.5); // Random radius
const angle = Math.random() * Math.PI * 2; // Random angle
// Set particle position
positions[i3] = Math.cos(angle) * radius; // X (horizontal)
positions[i3 + 1] = Math.pow(Math.random(), parameters.randomnessPower) * (Math.random() < 0.5 ? 1 : -1); // Y (vertical scatter)
positions[i3 + 2] = Math.sin(angle) * radius; // Z (depth)
// Blend colors from inner to outer
const mixedColor = colorInside.clone();
mixedColor.lerp(colorOutside, (radius - parameters.radiusInner) / (parameters.radiusOuter - parameters.radiusInner));
colors[i3] = mixedColor.r; // Red
colors[i3 + 1] = mixedColor.g; // Green
colors[i3 + 2] = mixedColor.b; // Blue
}
// Attach positions and colors to geometry
ringGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
ringGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
// Create custom material with shaders
ringMaterial = new THREE.ShaderMaterial({
vertexColors: true, // Use per-particle colors
transparent: true, // Allow transparency
vertexShader: vertexShader,// Position shader
fragmentShader: fragmentShader // Appearance shader
});
// Create ring points and add to scene
ringPoints = new THREE.Points(ringGeometry, ringMaterial);
scene.add(ringPoints);
};
generateRings(); // Generate rings
// Set initial window size for rendering
const sizes = {
width: window.innerWidth,
height: window.innerHeight
};
// Update sizes and camera on window resize
window.addEventListener('resize', () => {
sizes.width = window.innerWidth; // Match canvas to window width
sizes.height = window.innerHeight; // Match canvas to window height
camera.aspect = sizes.width / sizes.height; // Update aspect ratio
camera.updateProjectionMatrix(); // Apply new aspect
renderer.setSize(sizes.width, sizes.height); // Resize renderer
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // Optimize resolution
});
// Create camera: 52 is field of view, followed by aspect ratio, near/far planes
// Smaller FOV = zoomed in; larger FOV = wider view
const camera = new THREE.PerspectiveCamera(52, sizes.width / sizes.height, 0.1, 100);
// Position camera above the planet
const elevationDegrees = 15; // Angle above rings; increase to view more from top
const elevationRadians = THREE.MathUtils.degToRad(elevationDegrees);
const distance = 10; // Distance from planet; increase to zoom out
const x = distance * Math.cos(elevationRadians);
const y = distance * Math.sin(elevationRadians);
const z = 0;
camera.position.set(x, y, z); // Set camera position
camera.lookAt(0, 0, 0); // Point at planet center
scene.add(camera);
// Add OrbitControls for interactivity
const controls = new THREE.OrbitControls(camera, canvas);
controls.enableDamping = true; // Smooths out movement; set to false for instant response
controls.dampingFactor = 0.05; // Controls smoothness; higher = slower damping (0.01 to 0.5)
controls.enableZoom = true; // Enable zooming with scroll wheel; set to false to disable
controls.zoomSpeed = 1.0; // Zoom speed; increase (e.g., 2.0) for faster zoom
controls.minDistance = 2; // Minimum zoom distance; prevents zooming too close
controls.maxDistance = 50; // Maximum zoom distance; limits how far you can zoom out
controls.enablePan = true; // Enable panning with right-click drag; set to false to disable
controls.panSpeed = 0.5; // Panning speed; increase (e.g., 1.0) for faster dragging
controls.enableRotate = true; // Enable rotation with left-click drag; set to false to disable
controls.rotateSpeed = 1.0; // Rotation speed; increase (e.g., 2.0) for faster rotation
// Add ambient light for soft illumination
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // White light, 0.5 intensity
scene.add(ambientLight);
// Add point light for directional lighting
const pointLight = new THREE.PointLight(0xffffff, 1); // White light, full intensity
pointLight.position.set(10, 10, 10); // Position above and to side
scene.add(pointLight);
// Set up renderer to draw the scene
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
alpha: true // Transparent background; set to false for solid color
});
renderer.setSize(sizes.width, sizes.height); // Match window size
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // Optimize resolution
renderer.setClearColor(0x000000, 0); // Black background, fully transparent
// Clock for animation timing
const clock = new THREE.Clock();
// Animation loop to update scene
const tick = () => {
const elapsedTime = clock.getElapsedTime(); // Time since start
planet.rotation.y = elapsedTime * 0.1; // Rotate planet; increase 0.1 for faster spin
ringPoints.rotation.y = elapsedTime * 0.05; // Rotate rings; adjust 0.05 for speed
controls.update(); // Update controls for smooth interaction
renderer.render(scene, camera); // Render scene
window.requestAnimationFrame(tick); // Loop animation
};
tick(); // Start animation
// Expose objects for debugging
window.planet = planet;
window.ringPoints = ringPoints;
// Tilt planet and rings for Saturn-like appearance
planet.rotation.x = THREE.MathUtils.degToRad(-15);
ringPoints.rotation.x = THREE.MathUtils.degToRad(-15);
</script>
*Remember to remove // comments if short on character limit
Here’s where it gets exciting: I didn’t spend weeks learning Three.js from scratch. Using AI (shoutout to Grok from xAI), I got this code explained, tweaked, and ready in hours. AI broke down complex concepts—like shaders and buffers—into plain English and helped me iterate fast. What once felt like a tedious slog is now a creative sandbox. Anyone can experiment with 3D graphics like this, no PhD required!
Adding a 3D element like this to your site isn’t just eye-catching—it’s a statement. It shows you’re willing to push boundaries. With AI smoothing out the learning curve, tools like Three.js are more accessible than ever. So, grab this code, tweak the colours or sizes, and make it your own. Your visitors will love exploring your little corner of the cosmos.
Happy creating!