#app
View Compiled
*
  box-sizing border-box

body
  display grid
  place-items center
  min-height 100vh
  overflow hidden
  
canvas
  position fixed
  inset 0
  background hsl(0, 0%, 15%)
  z-index -1
  height 100vh
  width 100vw
View Compiled
import React from 'https://cdn.skypack.dev/react'
import ReactDOM from 'https://cdn.skypack.dev/react-dom'
import gsap from 'https://cdn.skypack.dev/gsap'
import { GUI } from 'https://cdn.skypack.dev/dat.gui'

const ROOT_NODE = document.querySelector('#app')

const Starscape = ({ densityRatio = 0.5, sizeLimit = 5, defaultAlpha = 0.2, scaleLimit = 2, proximityRatio = 0.1 }) => {
  const canvasRef = React.useRef(null)
  const contextRef = React.useRef(null)
  const starsRef = React.useRef(null)
  const vminRef = React.useRef(null)
  const scaleMapperRef = React.useRef(null)
  const alphaMapperRef = React.useRef(null)
  
  React.useEffect(() => {
    contextRef.current = canvasRef.current.getContext('2d')
    const LOAD = () => {
      vminRef.current = Math.min(window.innerHeight, window.innerWidth)
      const STAR_COUNT = Math.floor(vminRef.current * densityRatio)
      scaleMapperRef.current = gsap.utils.mapRange(0, vminRef.current * proximityRatio, scaleLimit, 1);
      alphaMapperRef.current = gsap.utils.mapRange(0, vminRef.current * proximityRatio, 1, defaultAlpha);
      canvasRef.current.width = window.innerWidth
      canvasRef.current.height = window.innerHeight
      starsRef.current = new Array(STAR_COUNT).fill().map(() => ({
        x: gsap.utils.random(0, window.innerWidth, 1),
        y: gsap.utils.random(0, window.innerHeight, 1),
        size: gsap.utils.random(1, sizeLimit, 1),
        scale: 1,
        alpha: defaultAlpha,
      }))
    }
    const RENDER = () => {
      contextRef.current.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height)
      starsRef.current.forEach(star => {
        contextRef.current.fillStyle = `hsla(0, 100%, 100%, ${star.alpha})`
        contextRef.current.beginPath()
        contextRef.current.arc(star.x, star.y, (star.size / 2) * star.scale, 0, Math.PI * 2)
        contextRef.current.fill()
      })
    }
    
    const UPDATE = ({ x, y }) => {
      starsRef.current.forEach(STAR => {
        const DISTANCE = Math.sqrt(Math.pow(STAR.x - x, 2) + Math.pow(STAR.y - y, 2));
        gsap.to(STAR, {
          scale: scaleMapperRef.current(
            Math.min(DISTANCE, vminRef.current * proximityRatio)
          ),
          alpha: alphaMapperRef.current(
            Math.min(DISTANCE, vminRef.current * proximityRatio)
          )
        });
      })
    };


    LOAD()
    gsap.ticker.fps(24)
    gsap.ticker.add(RENDER)
    
    // Set up event handling
    window.addEventListener('resize', LOAD)
    document.addEventListener('pointermove', UPDATE)
    return () => {
      window.removeEventListener('resize', LOAD)
      document.removeEventListener('pointermove', UPDATE)
      gsap.ticker.remove(RENDER)
    }

  }, [scaleLimit, sizeLimit, densityRatio, proximityRatio])
  return <canvas ref={canvasRef} />
}

const DEFAULT_DENSITY = 0.75
const DEFAULT_SIZE = 10
const DEFAULT_SCALE = 15
const DEFAULT_PROXIMITY = 0.2

const App = () => {
  const [density, setDensity] = React.useState(DEFAULT_DENSITY)
  const [size, setSize] = React.useState(DEFAULT_SIZE)
  const [scale, setScale] = React.useState(DEFAULT_SCALE)
  const [proximity, setProximity] = React.useState(DEFAULT_PROXIMITY)
  React.useEffect(() => {
    const CONFIG = {
      density,
      size,
      scale,
      proximity,
    }
    const CTRL = new GUI()
    CTRL.add(CONFIG, 'density', 0.1, 2, 0.1).name('Density Ratio').onChange(setDensity)
    CTRL.add(CONFIG, 'size', 1, 30, 1).name('Size Limit').onChange(setSize)
    CTRL.add(CONFIG, 'scale', 1, 10, 0.1).name('Scale Limit').onChange(setScale)
    CTRL.add(CONFIG, 'proximity', 0.1, 1, 0.01).name('Proximity Ratio').onChange(setProximity)
  }, [])
  return <Starscape densityRatio={density} sizeLimit={size} scaleLimit={scale} proximityRatio={proximity} />
}

ReactDOM.render(<App/>, ROOT_NODE)
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.