Animating a box-shadow/text-shadow on a circular path?
Asked Answered
D

2

8

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>
Dowel answered 20/8, 2020 at 9:55 Comment(0)
V
11

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>
Vive answered 20/8, 2020 at 10:1 Comment(10)
Yeah, that seems like a much simpler solution. I didn't mention it in my original question but this grew out of wanting to apply the same animation to a text-shadow, so I think I'd just got it stuck it my head that I also had to use box-shadow so it would translate easily. But I guess I could just apply your method to text too - just put a duplicate of the text in a pseudo element and animate that in the same way. Thanks!Dowel
@Dowel I am updating with a text shadow solutionVive
All those examples mess with the scroll bar, but I don't know if there's a solution for it. However, I doubt this will be used somewhere where the scrollbar wasn't there before. However, it can cause content to shift up and down, making it rather annoying. However, I don't know if there's a solution for it... :/ But it looks cool!Catharine
@IsmaelMiguel you can increase the margin of the main element to make sure the rotated element will rotate in that area and you will avoid any shift of the content.Vive
@TemaniAfif Well, I totally forgot about margins ... Would a padding work better, with a box-sizing: border-box?Catharine
@IsmaelMiguel I don't think so because it's not about box-sizing. Margin is more suitable because in all the cases the rotation will make the element go outside unless you apply the padding to a parent elementVive
@TemaniAfif Makes sense. Thank you for your point of view. Do you think you can add this info to the answer?Catharine
hey brother, do u know if there is a way to appease the SEO gods even if i use an image of text on a circular path instead of this so that the circular text is easier to align, etc?Discovery
@Discovery I think you can always have a hidden text close to your image. Crawler can still see it in the HTML code (not a SEO expert to confirm this btw)Vive
@TemaniAfif found out that John Mueller himself said that its not really a problem. the only thing the Hs are used for in SEO is to understand the following text or images or content :)Discovery
M
1

You can use the ease-in-out timing function to achieve circular motion.

The complication is that you need 2 separate animations running simultaneously on the 2 axes of motion. In the case of text-shadow, which doesn't have separate x offset and y offset properties, you can separately animate using @properties (which aren't available on Firefox right now).

And since you're not moving elements about, there's no overflow or scrollbar issue. I am using it to create circular animations to background images: https://rainbowunicornkitten.com/#red-square

div {
/*just for demo*/
display: flex;
flex-direction: column;
align-items: center;
font-size: 4rem;
}

@property --shadow-x {
syntax: "<length-percentage>";
inherits: false;
initial-value: 0%;
}

@property --shadow-y {
syntax: "<length-percentage>";
inherits: false;
initial-value: 0%;
}

.animate-shadow {
text-shadow: var(--shadow-x) var(--shadow-y) 0.25em black;

animation-name: animate-x, animate-y;
animation-duration: 4s;
animation-delay: 0s, -2s;
animation-fill-mode: forwards;
animation-direction: alternate;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}

@keyframes animate-x {
0% {
    --shadow-x: 1em;
}
100% {
    --shadow-x: -1em;
}
}

@keyframes animate-y {
0% {
    --shadow-y: 1em;
}
100% {
    --shadow-y: -1em;
}
}
<div class="animate-shadow">Watch my shadow!</div>

The important points are:

  1. You have separate animations for the axes.
  2. The animations run alternate direction (so that the easing function is applied in both directions)
  3. You use a negative delay for one of the animations that is exactly half of the duration that they both use. This puts the second animation 1/4 out of phase with the first one, creating the circular effect. If your animation is just moving diagonally you got that bit wrong.
Micropyle answered 8/4 at 12:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.