import * as THREE from "https://cdn.skypack.dev/three@0.136.0";
} from 'https://cdn.skypack.dev/three@0.136.0/examples/jsm/renderers/CSS2DRenderer.js'
import gsap from "https://cdn.skypack.dev/gsap@3.9.1";
} from "https://cdn.skypack.dev/three@0.136.0/examples/jsm/controls/OrbitControls";
const el = document.querySelector('#render')
const { width, height } = el.getBoundingClientRect()
const size = new THREE.Vector3()
const center = new THREE.Vector3()
const box = new THREE.Box3()
const planetEls = document.querySelectorAll('.pallete > div')
planetEls.forEach((p, i) => {
p.addEventListener('mouseover', () => {
activePlanet = planetArr[i]
gsap.to(mainObj.rotation, {
gsap.to(mainObj.position, {
const mesh = scene.getObjectByName(planetArr[i])
mesh.children[0].visible = true
p.addEventListener('mouseleave', () => {
gsap.to(mainObj.rotation, {
gsap.to(mainObj.position, {
const mesh = scene.getObjectByName(planetArr[i])
mesh.children[0].visible = false
function fitCameraToSelection(camera, controls, selection, fitOffset) {
for (const object of selection) {
box.expandByObject(object)
const maxSize = Math.max(size.x, size.y, size.z)
const fitHeightDistance =
maxSize / (2 * Math.atan((Math.PI * camera.fov) / 360))
const fitWidthDistance = fitHeightDistance / camera.aspect
const distance = fitOffset * Math.max(fitHeightDistance, fitWidthDistance)
const direction = controls.target
.multiplyScalar(distance)
controls.maxDistance = distance * 10
controls.target.copy(center)
camera.near = distance / 100
camera.far = distance * 100
camera.updateProjectionMatrix()
camera.position.copy(controls.target).sub(direction)
clock = new THREE.Clock()
renderer = new THREE.WebGLRenderer({
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(width, height)
el.appendChild(renderer.domElement)
labelRenderer = new CSS2DRenderer()
labelRenderer.setSize(width, height)
labelRenderer.domElement.style.position = 'absolute'
labelRenderer.domElement.style.top = '0px'
el.appendChild(labelRenderer.domElement)
scene = new THREE.Scene()
scene.background = new THREE.Color('#F6F1E5')
camera = new THREE.PerspectiveCamera(45, width / height, 1, 1000)
camera.position.set(0, 0, 1)
controls = new OrbitControls(camera, renderer.domElement)
scene.add(new THREE.AmbientLight(0x999999, 1.0))
const sun = new THREE.Mesh(
new THREE.SphereGeometry(40, 32, 16),
new THREE.MeshBasicMaterial({ color: '#FFE600' })
const div = document.createElement('div')
<div class="planet-title">SUN</div>
const label = new CSS2DObject(div)
label.position.copy(sun.position)
sun.add(new THREE.PointLight(0xdddddd, 1.0, 400, 0.7))
distance: '43.291 million mi'
distance: '66.822 million mi'
distance: '94.044 million mi'
distance: '131.56 million mi'
distance: '460.96 million mi'
distance: '915.96 million mi'
distance: '1.8297 billion mi'
distance: '2.78 billion mi'
mainObj = new THREE.Object3D()
Object.entries(planets).forEach((p) => {
const { size, orbitRadius, color, distance } = p[1]
console.log(name, size, orbitRadius)
let pts = new THREE.Path()
.absarc(0, 0, orbitRadius, 0, Math.PI * 2)
let g = new THREE.BufferGeometry().setFromPoints(pts)
let m = new THREE.LineBasicMaterial({
let o = new THREE.Line(g, m)
let pg = new THREE.SphereBufferGeometry(size, 16, 16)
let pm = new THREE.MeshToonMaterial({ color: color })
let pmesh = new THREE.Mesh(pg, pm)
planets[name].mesh = pmesh
const div = document.createElement('div')
<div class="planet-title">${name}</div>
<div class="planet-distance">${distance}</div>
const label = new CSS2DObject(div)
label.position.copy(planets[name].mesh.position)
planets[name].mesh.add(label)
planets[name].label = label
window.addEventListener('resize', () => {
const { width, height } = el.getBoundingClientRect()
camera.aspect = width / height
camera.updateProjectionMatrix()
fitCameraToSelection(camera, controls, [planets['neptune'].line], 0.6)
renderer.setSize(width, height)
labelRenderer.setSize(width, height)
fitCameraToSelection(camera, controls, [planets['neptune'].line], 0.6)
renderer.render(scene, camera)
labelRenderer.render(scene, camera)
requestAnimationFrame(animate)
let t = clock.getElapsedTime() / 50
Object.entries(planets).forEach((p) => {
const { orbitRadius, mesh, speed } = p[1]
.set(Math.cos(t * speed), 0, -Math.sin(t * speed))
.multiplyScalar(orbitRadius)
mesh.rotation.y = t * speed - Math.PI * 0.5
mesh.rotation.z = Math.PI * 0.5
camera.position.x = activePlanet.position.x
camera.position.y = -activePlanet.position.z