htmlcsscss-animationscss-transforms

Keep box-shadow direction consistent while rotating


When giving e.g. a <div> a box-shadow as well as rotating it will cause a rotation of the box-shadow direction - which is problematic when the box-shadow should create an illusion of lighting.

Example: https://jsfiddle.net/5h7z4swk/

div {
  width: 50px;
  height: 50px;
  margin: 20px;
  box-shadow: 10px 10px 10px #000;
  display: inline-block;
}
#box1 {
  background-color: #b00;
}
#box2 {
  background-color: #0b0;
  transform: rotate(30deg);
}
#box3 {
  background-color: #00b;
  transform: rotate(60deg);
}
#box4 {
  background-color: #b0b;
  transform: rotate(90deg);
}
@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
#box6 {
  background-color: #0bb;
  animation-name: spin;
  animation-duration: 2s;
  animation-iteration-count: infinite;
}
<div id="box1"></div>
<div id="box2"></div>
<div id="box3"></div>
<div id="box4"></div>
<div id="box6"></div>

box-shadow rotating with div

The answer to this problem should look similar to this mock up:

Shdaow direction is respected during rotation

How can I rotate a <div> and still keep the box-shadow going the same direction?

The solution should be pure CSS...

Note: The animation in the CSS is for demonstration purposes. The use case will use JavaScript to set the rotation. But the JavaScript will know nothing about the box-shadow as it is in the responsibility of the design to define a (or many...) shadows. That's why it should be a pure CSS solution.


Solution

  • Keeping direction of an offset box-shadow consistent during rotation is simple with CSS transforms.
    This approach relies on the fact that the transform origin is moved with the transforms. This means that when several transforms are set on the same element, the coordinate system of each transform changes according to the previous ones.

    In the following example, the blue element is a pseudo element and the shadow is the div element:

    div {
      width: 40px; height: 40px;
      margin: 40px;
      box-shadow: 0px 0px 10px 5px #000;
      animation: spinShadow 2s infinite;
      background-color: #000;
    }
    @keyframes spinShadow {
      to { transform: rotate(360deg); }
    }
    div:before {
      content: '';
      position: absolute;
      left:-5px; top:-5px;
      width: 50px; height: 50px;
      transform: rotate(0deg) translate(-10px, -10px) rotate(0deg);
      animation:inherit;
      animation-name: spinElt;
      background-color: #0bb;
    }
    @keyframes spinElt {
      to { transform: rotate(-360deg) translate(-10px, -10px) rotate(360deg); }
    }
    <div></div>

    Explanation of the transition property on the pseudo element (See the following code snippet for an illustration of the steps):

    transform: rotate(-360deg) translate(-10px, -10px) rotate(360deg)
    
    1. rotate(-360deg) counters the rotation of the parent to make the pseudo element static.
    2. translate(-10px, -10px) the pseudo element is translated to make the shadow offset
    3. rotate(360deg) the pseudo element is rotated in the same direction as parent

    div {
      width: 40px; height: 40px;
      margin: 40px;
      box-shadow: 0px 0px 10px 5px #000;
      animation: spinShadow 2s infinite;
      background-color: #000;
    }
    @keyframes spinShadow {
      to { transform: rotate(360deg); }
    }
    div:before {
      content: '';
      position: absolute;
      left:-5px; top:-5px;
      width: 50px; height: 50px;
      animation:inherit;
      background-color: #0bb;
    }
    #first:before{
      transform: rotate(0deg);
      animation-name: first;
    }  
    @keyframes first {
      to { transform: rotate(-360deg); }
    }
    #second:before{
      transform: rotate(0deg) translate(-10px, -10px);
      animation-name: second;
    }  
    @keyframes second {
      to { transform: rotate(-360deg) translate(-10px, -10px); }
    }
    #complete:before{
      transform: rotate(0deg) translate(-10px, -10px) rotate(0deg);
      animation-name: complete;
    }  
    @keyframes complete {
      to { transform: rotate(-360deg) translate(-10px, -10px) rotate(360deg); }
    }
    <ol>
      <li>Counter rotate:<div id="first"></div></li>
      <li>Translate :<div id="second"></div></li>
      <li>Rotate:<div id="complete"></div></li>
    <ol>