Search code examples
csscss-animations

Animating a box-shadow/text-shadow on a circular path?


I'm trying to use CSS animations to create the effect of a light source pointing down on an object, casting a shadow and moving in a circular motion around it. I've created a snippet below to show where I've gotten to so far.

It's sort-of close but at the moment (because I only have 4 keyframes) it's like the light source is moving along a square path. I'd like it to look like it was moving along a circular path.

The only solution I can think of to come close is to add a bunch of more keyframes and create a (for the sake of simplicity) a dodecagon-shaped path, but is there a simpler solution? Is there a type of timing function I could use to ease it into a smoother path? Or could I use some sort of Sass function to automatically calculate the intermediate keyframes?

I should have noted that once I get this working with box-shadows, I'd also like to apply the same method to text-shadows.

.container {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100vh;
}

.circle {
  width: 200px;
  height: 200px;
  border-radius: 100%;
  background-color: teal;
  box-shadow: 50px 50px 5px darkgrey;
  animation: orbit-shadow 5s linear infinite;
}

@keyframes orbit-shadow {
  0% {
    box-shadow: 50px 50px 5px darkgrey;
  }
  25% {
    box-shadow: -50px 50px 5px darkgrey;
  }
  50% {
    box-shadow: -50px -50px 5px darkgrey;
  }
  75% {
    box-shadow: 50px -50px 5px darkgrey;
  }
  1000% {
    box-shadow: 50px 50px 5px darkgrey;
  }
}
<div class="container">
  <div class="circle"></div>
</div>


Solution

  • You have to consider rotation for this. Use a pseudo element to avoid rotating the main element:

    .container {
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      position:relative;
      z-index:0;
    }
    
    .circle {
      width: 200px;
      height: 200px;
      margin:50px;
      border-radius: 100%;
      background-color: teal;
      position:relative;
    }
    .circle::before {
      content:"";
      position:absolute;
      z-index:-1;
      top:0;
      left:0;
      right:0;
      bottom:0;
      border-radius:inherit;
      box-shadow: 50px 50px 5px darkgrey;
      animation: orbit-shadow 5s linear infinite;
    }
    
    @keyframes orbit-shadow {
      100% {
        transform:rotate(360deg);
      }
    }
    
    body{
     margin:0;
    }
    <div class="container">
      <div class="circle"></div>
    </div>

    Or you simply rotate the element if you won't have any content:

    .container {
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
    }
    
    .circle {
      width: 200px;
      height: 200px;
      border-radius: 100%;
      background-color: teal;
      box-shadow: 50px 50px 5px darkgrey;
      animation: orbit-shadow 5s linear infinite;
    }
    
    @keyframes orbit-shadow {
      100% {
        transform:rotate(360deg);
      }
    }
    
    body{
     margin:0;
    }
    <div class="container">
      <div class="circle"></div>
    </div>

    Another idea:

    .container {
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      position:relative;
      z-index:0;
    }
    
    .circle {
      width: 200px;
      height: 200px;
      margin:50px;
      border-radius: 100%;
      background-color: teal;
      position:relative;
    }
    .circle::before {
      content:"";
      position:absolute;
      z-index:-1;
      top:0;
      left:0;
      right:0;
      bottom:0;
      border-radius:inherit;
      background:darkgrey;
      filter:blur(5px);
      animation: orbit-shadow 5s linear infinite;
    }
    
    @keyframes orbit-shadow {
      0% {
        transform:rotate(0deg)   translate(50px);
      }
      100% {
        transform:rotate(360deg) translate(50px);
      }
    }
    
    body{
     margin:0;
    }
    <div class="container">
      <div class="circle"></div>
    </div>

    You can also do the same for text-shadow with a slightly different animation in order to not rotate the text:

    .container {
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
    }
    
    .circle {
      position:relative;
      font-size:40px;
      font-weight:bold;
    }
    .circle::before,
    .circle::after{
      content:attr(data-text);
      position:relative;
      z-index:1;
    }
    
    .circle::before {
      position:absolute;
      top:0;
      left:0;
      right:0;
      bottom:0;
      color:transparent;
      text-shadow:0 0 5px darkgrey;
      animation: orbit-shadow 5s linear infinite;
    }
    /* the 50px is your offset */
    @keyframes orbit-shadow {
      0% {
        transform:rotate(0deg)   translate(50px) rotate(0deg);
      }
      100% {
        transform:rotate(360deg) translate(50px) rotate(-360deg);
      }
    }
    body{
     margin:0;
    }
    <div class="container">
      <div class="circle" data-text="some text"></div>
    </div>