javascripthtmlcssreactjsresponsive-design

Rotating Text around a circle to make it look like a sphere


Trying to get my nav bar to be a series of circles where if you hover over them it displays the text that goes with them. Heres what I have so far:

document.querySelectorAll('.color-circle').forEach((circle, index) => {
  circle.addEventListener('click', () => {
    alert(`Navigate to: ${circle.querySelector('.orbit-text').innerText}`);
  });
});
.color-circle {
  position: relative;
  border-radius: 50%;
  cursor: pointer;
  transition: transform 0.3s ease;
}

.color-circle:hover {
  transform: scale(1.2);
}

.orbit {
  position: absolute;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.color-circle:hover .orbit {
  opacity: 1;
  animation: orbit 2s linear infinite;
}

.orbit-text {
  position: absolute;
  left: 50%;
  top: 50%;
  transform-origin: center;
  transform: translate(-50%, -50%) rotateY(0deg);
  color: white;
  font-size: 8px;
  font-weight: bold;
  text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
  animation: orbit3D 4s linear infinite;
}

@keyframes orbit3D {
  from {
    transform: translate(-50%, -50%) rotateY(0deg) translateZ(15px);
  }
  to {
    transform: translate(-50%, -50%) rotateY(360deg) translateZ(15px);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>

<div class="top-bar visible">
  <div class="circle-container">
    <div class="color-circle" style="background-color: rgb(227, 197, 103); width: 40px; height: 40px;">
      <div class="orbit">
        <span class="orbit-text">HOME</span>
      </div>
    </div>
    <div class="color-circle" style="background-color: rgb(200, 150, 62); width: 30px; height: 30px;">
      <div class="orbit">
        <span class="orbit-text">DEV & DESIGN</span>
      </div>
    </div>
    <div class="color-circle" style="background-color: rgb(87, 61, 28); width: 25px; height: 25px;">
      <div class="orbit">
        <span class="orbit-text">ACTING</span>
      </div>
    </div>
    <div class="color-circle" style="background-color: rgb(217, 174, 97); width: 20px; height: 20px;">
      <div class="orbit">
        <span class="orbit-text">FILMMAKING</span>
      </div>
    </div>
    <div class="color-circle" style="background-color: rgb(209, 70, 47); width: 15px; height: 15px;">
      <div class="orbit">
        <span class="orbit-text">GALLERY</span>
      </div>
    </div>
  </div>
</div>

Spacing and other formatting isn't done but I am hitting a wall with having the text distort as if its curving when turned away and flatter when closer as if it was actually etched onto the rotating sphere.

Thanks!


Solution

  • Position the desired text on the bottom a "stick" DIV that is positioned at the center of the circle. Set the "stick" transformation origin to its top. Apply the needed rotations and counter-rotations, transform-style: preserve-3d;, and the infinite rotation animation:

    body { background: #343536; }
    
    .color-circle {
      position: relative;
      width: calc(var(--size) * 1em);
      aspect-ratio: 1;
      background-color: var(--bg);
      border-radius: 50%;
      margin: 1em;
      perspective: 220px;
      transform-style: preserve-3d;
      display: flex;
      
      .orbit {
        position: absolute;
        left: 50%;
        top: 50%;
        background: #0006;
        width: 2px;
        height: calc(var(--size) / 2 * 1em + 1em);
        display: flex;
        transform-origin: 50% 0%;
        transform-style: preserve-3d;
        rotate: 1 0 0 90deg;
        animation: orbit 4s linear infinite;
        display: flex;
        
        .orbit-text {
          translate: -50% 50%;
          align-self: flex-end;
          /* white-space: nowrap; */
          rotate: -1 0 0 90deg;
          color: white;
          font-size: 1rem;
          font-weight: bold;
          text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
        }
      }
    }
    
    @keyframes orbit {
      to {
        transform: rotate3d(0, 0, 1, 360deg);
      }
    }
    <div class="color-circle" style="--size:4; --bg:#ec6;">
      <div class="orbit">
        <span class="orbit-text">HOME</span>
      </div>
    </div>
    <div class="color-circle" style="--size:3.5; --bg:#c96;">
      <div class="orbit">
        <span class="orbit-text">DEV &amp; DESIGN</span>
      </div>
    </div>
    <div class="color-circle" style="--size:3; --bg:#754;">
      <div class="orbit">
        <span class="orbit-text">ACTING</span>
      </div>
    </div>
    <div class="color-circle" style="--size:2.5; --bg:#da6;">
      <div class="orbit">
        <span class="orbit-text">FILMMAKING</span>
      </div>
    </div>
    <div class="color-circle" style="--size:2; --bg:#d42;">
      <div class="orbit">
        <span class="orbit-text">GALLERY</span>
      </div>
    </div>

    The code is inspired by this similar answer: Create a 3D asteroid belt.

    What I would also do is: use white-space: nowrap; on the text, create (with JS) separate SPAN elements for every character, add a CSS property --index and rotate each SPAN element by the needed index degrees to make it look like every character orbits on its own. The only downside I guess would be using monospace font.