Skip to content

Instantly share code, notes, and snippets.

@bbauska
Created July 5, 2025 07:53
Show Gist options
  • Save bbauska/a9151b96f81822bb448bd92863351f4f to your computer and use it in GitHub Desktop.
Save bbauska/a9151b96f81822bb448bd92863351f4f to your computer and use it in GitHub Desktop.
Space Globe 2
<div class="banner">You can zoom in and click and drag to explore the scene.</div>
<a href="https://www.isladjan.com/about/" class="btn btn-left" target="_blank">My Works</a>
<button class="btn btn-right" id="fullscreenBtn">Go Fullscreen</button>
<div class="webgl"></div>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
}
}
</script><div class="banner">You can zoom in and click and drag to explore the scene.</div>
<a href="https://www.isladjan.com/about/" class="btn btn-left" target="_blank">My Works</a>
<button class="btn btn-right" id="fullscreenBtn">Go Fullscreen</button>
<div class="webgl"></div>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
}
}
</script>
//Author: isladjan - https://isladjan.com/
import * as THREE from 'three';
import { createNoise2D } from "https://esm.sh/simplex-noise";
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
let theta,
phi,
dx,
dy,
dz,
// Point Star Contraction variables
timeSinceStartContraction,
easingContraction,
positionsContraction,
// Initial Animation variables
newYInitial,
// Expansion Animation variables
positionsExpansion,
xExpansion,
yExpansion,
zExpansion,
currentDistanceExpansion,
distanceRatioExpansion,
expansionFactor,
// Contraction Animation variables
timeSinceStartContr,
easingContr,
positionsContr,
xContr,
yContr,
zContr,
currentDistanceContr,
originalRadiusContr,
normalizedXContr,
normalizedYContr,
normalizedZContr,
contractionSpeedContr,
// Centering Animation variables
timeSinceStartCenter,
easingCenter,
positionsCenter,
xCenter,
yCenter,
zCenter,
centeringSpeed,
// Point Contraction variables
xPointContr,
yPointContr,
zPointContr,
currentDistancePointContr,
nxPointContr,
nyPointContr,
nzPointContr,
targetXPointContr,
targetYPointContr,
targetZPointContr,
contractionSpeedPointContr;
// Schedule various animations and durations
let animationStartTime3 = Date.now() + 6000; // 6 seconds delay for nucleus animation
let animationEasing1 = 0;
let animationStartTime6 = Date.now() + 15000; // 15 seconds delay for moving-star animation
let movingStarsEasing = 0;
let expansionStartTime = Date.now() + 9000; // 9 seconds delay before expansion starts
const MAX_EXPANSION_DISTANCE = 95; // Maximum distance from center
let contractionStartTime = Date.now() + 20000; // 20 seconds delay before contraction
let contractionDuration = 8000; // Contraction lasts 8 seconds
let centeringStartTime = Date.now() + 25000; // 25 seconds delay before centering
let centeringDuration = 8000; // Centering lasts 8 seconds
let pointStarsContractStartTime = Date.now() + 25000; // Start point-star contraction after 25 seconds
let pointStarsContractDuration = 6000; // Point-star contraction lasts 6 seconds
const TARGET_RADIUS = 95; // Target distance from center for final positioning
class Effect {
constructor() {
this.nucleusPosition = null;
this.originalY = null;
this.time = null;
this.positionsStar = null;
this.velocitiesStar = null;
this.startPositions = null;
this.delta = 0;
this.textures = {};
}
//MARK: -init
async init() {
this.threeInit();
this.texturePromise = this.textureLoader();
this.createElements();
this.createMovingStars();
this.createPointElement();
this.initFullscreenButton();
this.bannerInit();
await this.texturePromise;
this.pointStars.material.map = this.textures.flare1;
this.pointStars2.material.map = this.textures.flare2;
this.pointComet1.material.map = this.textures.flare3;
this.planet1.material.map = this.textures.planet1;
this.planet2.material.map = this.textures.planet2;
this.planet3.material.map = this.textures.planet3;
this.nucleus.material.map = this.textures.star;
this.sphereBg.material.map = this.textures.sky;
this.stars.material.map = this.textures.flare2;
// Uses ResizeObserver to handle responsive resizing
const container = document.querySelector(".webgl");
this.resizeObserver = new ResizeObserver(() => this.onResize());
this.resizeObserver.observe(container);
// Limit rendering to a certain frame interval
this.limitFPS(1 / 60);
}
initFullscreenButton() {
const fullscreenBtn = document.getElementById("fullscreenBtn");
fullscreenBtn.addEventListener("click", () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch((err) => {
console.log(`Error attempting to enable fullscreen: ${err.message}`);
});
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
});
}
//MARK: -threeInit
// Sets up the core Three.js components: renderer, scene, camera, lighting, controls
threeInit() {
const container = document.querySelector(".webgl");
this.renderer = new THREE.WebGLRenderer({
powerPreference: "high-performance",
alpha: true,
antialias: true,
stencil: false,
});
this.renderer.setSize(container.clientWidth, container.clientHeight);
this.renderer.setPixelRatio(window.devicePixelRatio);
container.appendChild(this.renderer.domElement);
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(
55,
window.innerWidth / window.innerHeight,
0.01,
1000
);
this.camera.position.set(0, 0, 150);
this.clock = new THREE.Clock();
const directionalLight = new THREE.DirectionalLight("#fff", 3);
directionalLight.position.set(0, 50, -20);
this.scene.add(directionalLight);
let ambientLight = new THREE.AmbientLight("#ffffff", 1);
ambientLight.position.set(0, -20, -40);
this.scene.add(ambientLight);
// OrbitControls automatically rotates the camera around the scene
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.autoRotate = true;
this.controls.autoRotateSpeed = 5;
this.controls.maxDistance = 350;
this.controls.minDistance = 150;
this.controls.enablePan = false;
}
//MARK: -textureLoader
// Loads all textures asynchronously
textureLoader() {
const textureLoader = new THREE.TextureLoader();
textureLoader.crossOrigin = 'anonymous';
const textureMap = {
sky: "https://i.ibb.co/HC0vxMw/sky2.jpg",
star: "https://i.ibb.co/NpJzwns/star.jpg",
flare1: "https://i.ibb.co/TRsJ1tm/p1.png",
flare2: "https://i.ibb.co/YQcTCRG/p2.png",
flare3: "https://i.ibb.co/v1S8YW7/p7.png",
planet1: "https://i.ibb.co/s1cZDnM/planet1.webp",
planet2: "https://i.ibb.co/Lt5Kn7y/planet2.webp",
planet3: "https://i.ibb.co/T8V57p4/planet3.webp",
};
return Promise.all(
Object.entries(textureMap).map(([key, path]) => {
return new Promise((resolve, reject) => {
textureLoader.load(
path,
(texture) => {
texture.colorSpace = THREE.SRGBColorSpace;
texture.anisotropy = 16;
this.textures[key] = texture;
resolve(true);
},
undefined,
(error) => reject(`Error loading texture ${path}: ${error}`)
);
});
})
);
}
//MARK: -createElements
// Builds the main nucleus and background spheres
createElements() {
/* Nucleus */
//MARK: ---Nucleus
// Increasing the second parameter drastically reduces FPS, so use carefully
let icosahedronGeometry = new THREE.IcosahedronGeometry(20, 28);
// Convert geometry to store original positions for animating
this.originalPositions = new Float32Array(
icosahedronGeometry.attributes.position.array
);
let lambertMaterial = new THREE.MeshPhongMaterial({});
this.nucleus = new THREE.Mesh(icosahedronGeometry, lambertMaterial);
this.nucleus.position.set(0, 0, 0);
this.scene.add(this.nucleus);
// Noise generator initialization
this.noise = createNoise2D();
this.blobScale = 2; // Higher values amplify the displacement effect
/* Sphere Background 1 */
//MARK: ---sphere Background1
let geometrySphereBg = new THREE.SphereGeometry(90, 50, 50);
let materialSphereBg = new THREE.MeshBasicMaterial({
side: THREE.BackSide,
});
this.sphereBg = new THREE.Mesh(geometrySphereBg, materialSphereBg);
this.sphereBg.position.set(0, 0, 0);
this.scene.add(this.sphereBg);
}
//MARK: -createPointElement
// Generates various point-based particles (stars, comets, planets)
createPointElement() {
const self = this;
// Distant small white stars
this.pointStars = createPointParticles({
size: 0.5,
total: 200,
transparent: true,
max: 130,
min: 130,
});
this.scene.add(this.pointStars);
// Smaller orange stars in the middle
this.pointStars2 = createPointParticles({
size: 3,
total: 600,
transparent: true,
max: 33,
min: 25,
pointY: 0,
});
this.scene.add(this.pointStars2);
// Orange comet
this.pointComet1 = createPointParticles({
size: 12,
total: 1,
transparent: true,
max: 25,
min: 25,
});
this.scene.add(this.pointComet1);
// Blue planet
this.planet1 = createPointParticles({
size: 9,
total: 1,
transparent: false,
max: 60,
min: 40,
});
// Red planet
this.planet2 = createPointParticles({
size: 12,
total: 1,
transparent: false,
max: 60,
min: 40,
});
// Moon-like planet
this.planet3 = createPointParticles({
size: 12,
total: 1,
transparent: false,
max: 60,
min: 40,
});
this.scene.add(this.planet1);
this.scene.add(this.planet2);
this.scene.add(this.planet3);
// Helper function for creating point particles
function createPointParticles({
texture,
size,
total,
transparent = true,
max = 150,
min = 70,
pointY,
}) {
const positions = new Float32Array(total * 3);
const originalY = new Float32Array(total);
let point, idx;
// Generate random positions on a sphere
for (let i = 0; i < total; i++) {
point = self.randomPointSphere(THREE.MathUtils.randInt(max, min));
idx = i * 3;
positions[idx] = point.x;
positions[idx + 2] = point.z;
if (pointY !== undefined) {
// If an explicit y-value was provided
positions[idx + 1] = pointY;
originalY[i] = point.y;
} else positions[idx + 1] = point.y;
}
const pointGeometry = new THREE.BufferGeometry();
pointGeometry.setAttribute(
"position",
new THREE.BufferAttribute(positions, 3)
);
pointGeometry.setAttribute(
"originalY",
new THREE.BufferAttribute(originalY, 1)
);
let blending = transparent
? THREE.AdditiveBlending
: THREE.NormalBlending;
const pointMaterial = new THREE.PointsMaterial({
size: size,
blending: blending,
transparent: true,
depthWrite: false,
});
return new THREE.Points(pointGeometry, pointMaterial);
}
}
//MARK: -createMovingStars
// Builds a set of stars that move from the outer boundary of the scene toward the center
createMovingStars() {
const totalStars = 5;
const positions = new Float32Array(totalStars * 3);
const velocities = new Float32Array(totalStars);
const startPositions = new Float32Array(totalStars * 3);
let point, radius, idx;
for (let i = 0; i < totalStars; i++) {
// Creates random points far from the center
radius = THREE.MathUtils.randFloat(200, 300);
point = this.randomPointSphere(radius);
idx = i * 3;
positions[idx] = point.x;
positions[idx + 1] = point.y;
positions[idx + 2] = point.z;
startPositions[idx] = point.x;
startPositions[idx + 1] = point.y;
startPositions[idx + 2] = point.z;
velocities[i] = THREE.MathUtils.randInt(50, 400);
}
const starsGeometry = new THREE.BufferGeometry();
starsGeometry.setAttribute(
"position",
new THREE.BufferAttribute(positions, 3)
);
starsGeometry.setAttribute(
"velocity",
new THREE.BufferAttribute(velocities, 1)
);
starsGeometry.setAttribute(
"startPosition",
new THREE.BufferAttribute(startPositions, 3)
);
const starsMaterial = new THREE.PointsMaterial({
size: 14,
transparent: true,
opacity: 0.8,
blending: THREE.AdditiveBlending,
depthWrite: false,
});
this.stars = new THREE.Points(starsGeometry, starsMaterial);
this.stars.name = "moving_stars";
this.stars.visible = false;
this.scene.add(this.stars);
}
//MARK: -randomPointSphere
// Generates random coordinates on a sphere using the provided radius
randomPointSphere(radius) {
theta = 2 * Math.PI * Math.random();
phi = Math.acos(2 * Math.random() - 1);
dx = 0 + radius * Math.sin(phi) * Math.cos(theta);
dy = 0 + radius * Math.sin(phi) * Math.sin(theta);
dz = 0 + radius * Math.cos(phi);
return new THREE.Vector3(dx, dy, dz);
}
//MARK: -onResize
// Keeps camera and renderer in sync with the window size
onResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
//MARK: -limitFPS
// Caps the rendering loop to a specified frame interval
limitFPS(interval) {
this.rafAnimate = requestAnimationFrame(this.limitFPS.bind(this, interval));
this.delta += this.clock.getDelta();
if (this.delta > interval) {
this.loop();
this.delta = this.delta % interval;
}
}
//MARK: -updateNucleus
// Expands and displaces the nucleus based on noise after a delay
updateNucleus() {
if (Date.now() < animationStartTime3) return;
const animationEasing1 = Math.min(
1,
(Date.now() - animationStartTime3) / 2000
);
for (let i = 0; i < this.nucleusPosition.count; i++) {
const xNucleus = this.originalPositions[i * 3];
const yNucleus = this.originalPositions[i * 3 + 1];
const zNucleus = this.originalPositions[i * 3 + 2];
// Normalize the position
const lengthNucleus = Math.sqrt(
xNucleus * xNucleus + yNucleus * yNucleus + zNucleus * zNucleus
);
const nxNucleus = xNucleus / lengthNucleus;
const nyNucleus = yNucleus / lengthNucleus;
const nzNucleus = zNucleus / lengthNucleus;
// Displace vertices using Perlin-like noise
const distanceNucleus =
20 +
this.noise(
nxNucleus + this.time * 0.0004,
nyNucleus + this.time * 0.0004
) *
this.blobScale *
animationEasing1;
// Apply the updated coordinates
this.nucleusPosition.array[i * 3] = nxNucleus * distanceNucleus;
this.nucleusPosition.array[i * 3 + 1] = nyNucleus * distanceNucleus;
this.nucleusPosition.array[i * 3 + 2] = nzNucleus * distanceNucleus;
}
this.nucleusPosition.needsUpdate = true;
this.nucleus.geometry.computeVertexNormals();
}
//MARK: -updateMovingStars
// Animates stars flying toward the center after a delay
updateMovingStars() {
if (Date.now() < animationStartTime6) return;
const movingStarsEasing = Math.min(
1,
(Date.now() - animationStartTime6) / 2000
);
for (let i = 0; i < this.positionsStar.count; i++) {
const idx = i * 2;
this.updateSingleMovingStar(i, idx, movingStarsEasing);
}
this.positionsStar.needsUpdate = true;
this.velocitiesStar.needsUpdate = true;
}
// Fine-tunes individual star movement
updateSingleMovingStar(i, idx, easing) {
const moveAmount =
easing *
((0 - this.positionsStar.array[idx]) / this.velocitiesStar.array[i]);
this.positionsStar.array[idx] += moveAmount;
this.positionsStar.array[idx + 1] +=
easing *
((0 - this.positionsStar.array[idx + 1]) / this.velocitiesStar.array[i]);
this.positionsStar.array[idx + 2] +=
easing *
((0 - this.positionsStar.array[idx + 2]) / this.velocitiesStar.array[i]);
this.velocitiesStar.array[i] -= 0.1 * easing;
this.resetStarIfAtCenter(i, idx);
}
// Resets a star to its start position once it reaches the center
resetStarIfAtCenter(i, idx) {
if (
this.positionsStar.array[idx] <= 2 &&
this.positionsStar.array[idx] >= -2 &&
this.positionsStar.array[idx + 2] <= 2 &&
this.positionsStar.array[idx + 2] >= -2
) {
this.positionsStar.array[idx] = this.startPositions.array[idx];
this.positionsStar.array[idx + 1] = this.startPositions.array[idx + 1];
this.positionsStar.array[idx + 2] = this.startPositions.array[idx + 2];
this.velocitiesStar.array[i] = 120;
}
}
//MARK: -updatePointStars2
// Manages positioning for a secondary star group, handling multiple animation phases
updatePointStars2() {
for (let i = 0; i < this.originalY.count; i++) {
this.updateSinglePointStar2(i);
}
this.pointStars2.geometry.attributes.position.needsUpdate = true;
}
// Helper function for updating a single star in the secondary group
updateSinglePointStar2(i) {
const positions = this.pointStars2.geometry.attributes.position;
const currentY = positions.array[i * 3 + 1];
const targetY = this.originalY.array[i];
this.handleInitialYAnimation(i, currentY, targetY);
this.handleExpansionAnimation(i);
this.handleContractionAnimation(i);
this.handleCenteringAnimation(i);
}
//MARK: -updateRotations
// Subtle rotation updates for backgrounds, stars, comets, and planets
updateRotations() {
// Background spheres
//this.sphereBg.rotation.x -= 0.006;
//this.sphereBg.rotation.y += 0.001;
// Star fields / comets
this.pointStars.rotation.y -= 0.0007;
this.pointComet1.rotation.z -= 0.01;
this.pointComet1.rotation.y += 0.001;
//this.pointStars2.rotation.z -= 0.01;
this.pointStars2.rotation.x -= 0.001;
// Planets
this.planet1.rotation.y += 0.001;
this.planet2.rotation.z += 0.003;
this.planet3.rotation.x += 0.0005;
}
//MARK: -loop
// Core animation loop called at each frame or limited interval
loop() {
this.nucleusPosition = this.nucleus.geometry.attributes.position;
this.originalY = this.pointStars2.geometry.attributes.originalY;
this.time = Date.now();
this.updateNucleus();
this.positionsStar = this.stars.geometry.attributes.position;
this.velocitiesStar = this.stars.geometry.attributes.velocity;
this.startPositions = this.stars.geometry.attributes.startPosition;
this.updateMovingStars();
this.updatePointStars2();
this.updateRotations();
// Start the point-star contraction if it’s time
if (Date.now() >= pointStarsContractStartTime) {
this.updatePointStarsContraction();
}
this.controls.update();
this.renderer.render(this.scene, this.camera);
}
//MARK: -updatePointStarsContraction
// Pulls distant point stars toward a target radius
updatePointStarsContraction() {
timeSinceStartContraction = Date.now() - pointStarsContractStartTime;
easingContraction = Math.min(
1,
timeSinceStartContraction / pointStarsContractDuration
);
positionsContraction = this.pointStars.geometry.attributes.position;
this.pointStars.material.size = 0.4 + 0.7 * easingContraction;
for (let i = 0; i < positionsContraction.count; i++) {
this.updateSinglePointStarContraction(
positionsContraction,
i,
easingContraction
);
}
positionsContraction.needsUpdate = true;
}
// Phase 1: Move secondary stars along Y-axis after the nucleus movement starts
handleInitialYAnimation(i, currentY, targetY) {
if (Date.now() >= animationStartTime3) {
newYInitial = currentY + (targetY - currentY) * 0.02;
this.pointStars2.geometry.attributes.position.array[i * 3 + 1] =
newYInitial;
}
}
// Phase 2: Gradual expansion, pushing stars outward
handleExpansionAnimation(i) {
if (Date.now() >= expansionStartTime && Date.now() < contractionStartTime) {
positionsExpansion = this.pointStars2.geometry.attributes.position;
xExpansion = positionsExpansion.array[i * 3];
yExpansion = positionsExpansion.array[i * 3 + 1];
zExpansion = positionsExpansion.array[i * 3 + 2];
currentDistanceExpansion = Math.sqrt(
xExpansion * xExpansion +
yExpansion * yExpansion +
zExpansion * zExpansion
);
if (currentDistanceExpansion < MAX_EXPANSION_DISTANCE) {
distanceRatioExpansion =
currentDistanceExpansion / MAX_EXPANSION_DISTANCE;
expansionFactor = 1 + 0.008 * (1 - distanceRatioExpansion);
positionsExpansion.array[i * 3] = xExpansion * expansionFactor;
positionsExpansion.array[i * 3 + 1] = yExpansion * expansionFactor;
positionsExpansion.array[i * 3 + 2] = zExpansion * expansionFactor;
}
}
}
// Phase 3: Contraction, pulling stars closer to a tighter radius
handleContractionAnimation(i) {
if (Date.now() >= contractionStartTime) {
timeSinceStartContr = Date.now() - contractionStartTime;
easingContr = Math.min(1, timeSinceStartContr / contractionDuration);
positionsContr = this.pointStars2.geometry.attributes.position;
xContr = positionsContr.array[i * 3];
yContr = positionsContr.array[i * 3 + 1];
zContr = positionsContr.array[i * 3 + 2];
currentDistanceContr = Math.sqrt(
xContr * xContr + yContr * yContr + zContr * zContr
);
originalRadiusContr = THREE.MathUtils.randFloat(25, 33);
normalizedXContr = (xContr / currentDistanceContr) * originalRadiusContr;
normalizedYContr = (yContr / currentDistanceContr) * originalRadiusContr;
normalizedZContr = (zContr / currentDistanceContr) * originalRadiusContr;
contractionSpeedContr = 0.02 * easingContr;
positionsContr.array[i * 3] =
xContr + (normalizedXContr - xContr) * contractionSpeedContr;
positionsContr.array[i * 3 + 1] =
yContr + (normalizedYContr - yContr) * contractionSpeedContr;
positionsContr.array[i * 3 + 2] =
zContr + (normalizedZContr - zContr) * contractionSpeedContr;
}
}
// Phase 4: Centering, gathering stars at the scene’s origin
handleCenteringAnimation(i) {
if (Date.now() >= centeringStartTime) {
timeSinceStartCenter = Date.now() - centeringStartTime;
easingCenter = Math.min(1, timeSinceStartCenter / centeringDuration);
positionsCenter = this.pointStars2.geometry.attributes.position;
xCenter = positionsCenter.array[i * 3];
yCenter = positionsCenter.array[i * 3 + 1];
zCenter = positionsCenter.array[i * 3 + 2];
centeringSpeed = 0.02 * easingCenter;
positionsCenter.array[i * 3] = xCenter + (0 - xCenter) * centeringSpeed;
positionsCenter.array[i * 3 + 1] =
yCenter + (0 - yCenter) * centeringSpeed;
positionsCenter.array[i * 3 + 2] =
zCenter + (0 - zCenter) * centeringSpeed;
this.pointStars2.material.opacity = 1 - easingCenter;
// Hide these stars and reveal the moving stars
if (easingCenter >= 1) {
this.pointStars2.visible = false;
this.stars.visible = true;
}
}
}
// Moves each star from its current position to the target radius
updateSinglePointStarContraction(positions, i, easing) {
xPointContr = positions.array[i * 3];
yPointContr = positions.array[i * 3 + 1];
zPointContr = positions.array[i * 3 + 2];
currentDistancePointContr = Math.sqrt(
xPointContr * xPointContr +
yPointContr * yPointContr +
zPointContr * zPointContr
);
nxPointContr = xPointContr / currentDistancePointContr;
nyPointContr = yPointContr / currentDistancePointContr;
nzPointContr = zPointContr / currentDistancePointContr;
targetXPointContr = nxPointContr * TARGET_RADIUS;
targetYPointContr = nyPointContr * TARGET_RADIUS;
targetZPointContr = nzPointContr * TARGET_RADIUS;
contractionSpeedPointContr = 0.02 * easing;
positions.array[i * 3] =
xPointContr +
(targetXPointContr - xPointContr) * contractionSpeedPointContr;
positions.array[i * 3 + 1] =
yPointContr +
(targetYPointContr - yPointContr) * contractionSpeedPointContr;
positions.array[i * 3 + 2] =
zPointContr +
(targetZPointContr - zPointContr) * contractionSpeedPointContr;
}
bannerInit() {
const banner = document.querySelector(".banner");
const controls = this.controls;
// Function to hide banner
const hideBanner = () => {
if (!this.hasInteracted && banner) {
banner.style.opacity = "0";
banner.style.transition = "opacity 0.5s ease";
// Remove banner from DOM after fade out
setTimeout(() => {
banner.remove();
}, 500);
this.hasInteracted = true;
}
};
// Listen for scroll/zoom
window.addEventListener("wheel", hideBanner, { once: true });
// Listen for drag start
this.renderer.domElement.addEventListener("pointerdown", hideBanner, {
once: true,
});
}
}
const effect = new Effect();
effect.init();

Space Globe 2

This project explores the cosmos using only Three.js and simplex 2D noise. The real MVP is Three Points, which renders countless simple elements always facing the camera. With Orbit Controls for smooth zoom and drag, you get an interactive universe ready for exploration.

My original v1 version: https://codepen.io/isladjan/full/bGpjZwN

Also, check out my portfolio for more cool projects - https://isladjan.com/

A Pen by Brian Bauska on CodePen.

License.

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
margin: 0;
overflow: hidden;
width: 100vw;
height: 100vh;
background-color: #000000;
background-image: url("https://i.ibb.co/rm2ZShj/sky1.jpg");
background-size: cover;
backdrop-filter: brightness(40%);
}
.banner {
position: fixed;
top: 0;
left: 0;
width: 100%;
padding: 15px;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(5px);
color: white;
text-align: center;
font-family: Arial, sans-serif;
font-size: 16px;
z-index: 10;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
.webgl {
width: 100%;
height: 100vh;
position: fixed;
z-index: 3;
top: 0;
left: 0;
outline: none;
canvas {
position: absolute;
top: 0;
left: 0;
display: block;
width: 100%;
height: 100vh;
}
}
.btn {
position: fixed;
bottom: 20px;
transform: translateY(-50%);
padding: 12px 24px;
background: rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 30px;
color: white;
font-family: Arial, sans-serif;
font-size: 16px;
text-decoration: none;
backdrop-filter: blur(5px);
transition: all 0.3s ease;
cursor: pointer;
z-index: 10;
&:hover {
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.3);
transform: translateY(-50%) scale(1.05);
}
}
.btn-left {
left: 30px;
}
.btn-right {
right: 30px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment