.container
  .container__item(style="--move-x: -1; --rotate: 90; --x: 10; --y: 60; --size: 30; --hue: 220;")
  .container__item(style="--move-x: 1.6; --move-y: -2; --rotate: -45; --x: 75; --y: 20; --size: 50; --hue: 240;")
  .container__item(style="--move-x: -3; --move-y: 1; --rotate: 360; --x: 75; --y: 80; --size: 40; --hue: 260;")
View Compiled
*
  box-sizing border-box
  
body
  min-height 100vh
  font-weight bold
  font-size 1.15rem
  font-family sans-serif
  text-align center
  display grid
  place-items center
  background hsl(260, 50%, 50%)
  overflow hidden
  transform-style preserve-3d
  perspective 50vmin
  
.container
  height 135px
  width 135px
  background hsl(25, 100%, 80%)
  display grid
  place-content center
  border-radius 50%
  position relative
  transition transform 0.1s
  transform rotateY(calc(var(--ratio-x, 0) * 20deg)) rotateX(calc(var(--ratio-y, 0) * -20deg))
  
  &:after
    content ''
    position absolute
    top 50%
    left 50%
    height 200px
    width 200px
    border-radius 50%
    background hsla(280, 50%, 80%, 0.5)
    transform translate(-50%, -50%)
    z-index -1
    
  &__item
    position absolute
    top calc(var(--y, 0) * 1%)
    left calc(var(--x, 0) * 1%)
    height calc(var(--size, 20) * 1px)
    width calc(var(--size, 20) * 1px)
    background 'hsl(%s, 80%, 80%)' % var(--hue, 0)
    transition transform 0.1s
    transform translate(-50%, -50%) translate(calc(var(--move-x, 0) * var(--ratio-x, 0) * 100%), calc(var(--move-y, 0) * var(--ratio-y, 0) * 100%)) rotate(calc(var(--rotate, 0) * var(--ratio-x, 0) * 1deg))
View Compiled
import gsap from 'https://cdn.skypack.dev/gsap'

const CONTAINER = document.querySelector('.container')

const generateHandler = (element, proximity, bounds, cb) => ({x, y}) => {
  const elementBounds = element.getBoundingClientRect()
  const centerX = elementBounds.left + elementBounds.width / 2
  const centerY = elementBounds.top + elementBounds.height / 2
  const boundX = gsap.utils.mapRange(centerX - proximity, centerX + proximity, -bounds, bounds, x)
  const boundY = gsap.utils.mapRange(centerY - proximity, centerY + proximity, -bounds, bounds, y)
  cb(boundX, boundY)
}

const UPDATE = (x, y) => {
  CONTAINER.style.setProperty('--ratio-x', Math.floor(gsap.utils.clamp(-100, 100, x)) / 100)
  CONTAINER.style.setProperty('--ratio-y', Math.floor(gsap.utils.clamp(-100, 100, y)) / 100)
}

document.addEventListener('pointermove', generateHandler(document.body, 100, 100, UPDATE))
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.