Search code examples
javascripthtmlcsscss-animationscss-transforms

Aim element towards circle centre while rotating (CSS/JS)


I'm trying to move 10 objects in circular motion, while they point towards the centre of the circle.

I've positioned the elements on the circle using a bit of trig in .js, but when I try to rotate them around the centre of the circle, they rotate incorrectly, with seemingly random centres. I'm trying to rotate them around the centre using the transformOrigin property.

If I do not specify an 'initial rotation' for the SeedParent, then the rotation works as expected, but the orientation of the arrows all point in the same direction (I want them all to point inwards towards the centre). I would assume this is because the axis of rotation is changed when changing the 'initial rotation' of the parent. Hopefully the code will explain better:

N.B: I cannot give out the original 'Seed' image, so I have an arrow shape instead.

function AddImageToDiv(Index){
    var Seed = document.createElement("div");
    var SeedParent = document.createElement("div");
    var SpiralDiv = document.getElementById("spiralDiv");
    //The seeds are added to the SpiralDiv
   
    
    Seed.classList.add("seedDiv", "Rotate");
    SeedParent.classList.add("seedParentDiv", "Move")
    
    SpiralDiv.appendChild(SeedParent);
    SeedParent.appendChild(Seed);

    SetSeedPositionAndAngle(Seed, SeedParent, Index)
    //Position the seed on the edge of the circle using index to determine its position


}

function SetSeedPositionAndAngle(Seed, SeedParent, Index){
    var Angle = 2*Math.PI/NumberOfSeeds * Index;

    var PositionOffset = {x: CircleRadius * Math.cos(Angle), y: CircleRadius * Math.sin(Angle)}
  
    SeedParent.style.transform = 
    " translate(" + PositionOffset.x + "px," + PositionOffset.y +"px) rotate(" + (Angle + Math.PI * 0.9) + "rad)";
    //Positioning handled in the parent element
    //If the ROTATE portion is taken out, the rotation works as expected, but the arrows do 
    //not point towards the centre of the circle.
            
    Seed.style.transformOrigin = (-PositionOffset.x + Seed.clientWidth/2) + "px "  +(- PositionOffset.y + Seed.clientWidth/2) + "px";
    //Transform origin set as the coordinate to rotate around

}

const NumberOfSeeds = 10; //The number of elements in the circle
const CircleRadius = 190; //Radius in pixels

for (let index = 1; index < 11; index++) {
    AddImageToDiv(index);
    
}
*{
    margin:0px;
    padding: 0px;
    box-sizing: border-box;
}

:root{
    --greytextcolor: #808080;
}

/* SEED ANIMATION */

.seedDiv {
    display: inline-block;
    position: absolute;
    
    width: 110px;
 background:linear-gradient(45deg,transparent,orange);
    -webkit-mask:url('https://lh3.googleusercontent.com/proxy/5xwQBbe4tsxwqwDthUeiLd_UFRsFEDl3_TT2skCOLzQpgAnKyI5PT9Lfukwjdan6jDQNL0g7qrtupEXM_wQN3VZW') center/contain no-repeat;
          mask:url('https://lh3.googleusercontent.com/proxy/5xwQBbe4tsxwqwDthUeiLd_UFRsFEDl3_TT2skCOLzQpgAnKyI5PT9Lfukwjdan6jDQNL0g7qrtupEXM_wQN3VZW') center/contain no-repeat;

  }

  .seedDiv::before {
    content:"";
    display:block;
    padding-top:113.6%;

    /* Sets the padding of the top to 113.6% of the padding of the width due to ratio of the image */
 }

 .seedDiv.Rotate{
    animation-name: RotateAroundCenter;
    animation-fill-mode: forwards;
    animation-timing-function: ease-in-out;
    animation-duration: 3s;
    animation-delay: 3s;
/*I've set an animation delay, so you can see the inital positioning of the arrows*/

 }
 
#spiralDiv{
    display: flex;
    justify-content: center;
    position: relative;
    top: 200px;
}

@keyframes RotateAroundCenter{
    100%{
        transform: rotate(-360deg);
        
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="stylesheet.css">
    <link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
    
    <script defer src="node_modules/swup/dist/swup.min.js"></script>
    <script defer src='index.js'></script>
    
    <title>Seed Rotate</title>
</head>
<body>

    <main id="swup" class="transition-fade">

        <div id = "spiralDiv">
            
        </div>
    </main>
</body>
</html>

If there is a better way of doing this, I am all ears :)


Solution

  • I would simplify your code and rely on CSS variables to make it easy. The trick is to know how to chain the transformation.

    In the below you will notice two rotations, the one that will define the orientation of the arrow (placed after the translation) and the one that will rotate the element around the circle (placed before the translation). You also don't need any transform-origin:

    function AddImageToDiv(Index) {
      var SeedParent = document.createElement("div");
      var SpiralDiv = document.getElementById("spiralDiv");
      //The seeds are added to the SpiralDiv
      SeedParent.classList.add("seedDiv", "Rotate");
    
      SpiralDiv.appendChild(SeedParent);
    
      SetSeedPositionAndAngle(SeedParent, Index)
    }
    
    function SetSeedPositionAndAngle(SeedParent, Index) {
      var Angle = 2 * Math.PI / NumberOfSeeds * Index;
    
      var PositionOffset = {
        x: CircleRadius * Math.cos(Angle),
        y: CircleRadius * Math.sin(Angle)
      }
    
      SeedParent.style.setProperty("--x", PositionOffset.x + "px");
      SeedParent.style.setProperty("--y", PositionOffset.y + "px");
      SeedParent.style.setProperty("--r", (Angle + Math.PI) + "rad");
    
    }
    
    const NumberOfSeeds = 10; //The number of elements in the circle
    const CircleRadius = 190; //Radius in pixels
    
    for (let index = 1; index < 11; index++) {
      AddImageToDiv(index);
    
    }
    * {
      margin: 0px;
      padding: 0px;
      box-sizing: border-box;
    }
    /* SEED ANIMATION */
    
    .seedDiv {
      position: absolute;
      width: 110px;
      background: linear-gradient(45deg, transparent, orange);
      -webkit-mask: url('https://lh3.googleusercontent.com/proxy/5xwQBbe4tsxwqwDthUeiLd_UFRsFEDl3_TT2skCOLzQpgAnKyI5PT9Lfukwjdan6jDQNL0g7qrtupEXM_wQN3VZW') center/contain no-repeat;
      mask: url('https://lh3.googleusercontent.com/proxy/5xwQBbe4tsxwqwDthUeiLd_UFRsFEDl3_TT2skCOLzQpgAnKyI5PT9Lfukwjdan6jDQNL0g7qrtupEXM_wQN3VZW') center/contain no-repeat;
    }
    
    .seedDiv::before {
      content: "";
      display: block;
      padding-top: 113.6%;
    }
    
    .seedDiv.Rotate {
      animation: RotateAroundCenter 3s 3s ease-in-out forwards;
      transform: rotate(0) translate(var(--x), var(--y)) rotate(var(--r));
    }
    
    #spiralDiv {
      display: flex;
      justify-content: center;
      position: relative;
      top: 200px;
    }
    
    @keyframes RotateAroundCenter {
      100% {
        transform: rotate(-360deg) translate(var(--x), var(--y)) rotate(var(--r));
      }
    }
    <div id="spiralDiv">
    
    </div>

    If you do the tranformation differently you can simplify more the code

    function AddImageToDiv(Index) {
      var SeedParent = document.createElement("div");
      var SpiralDiv = document.getElementById("spiralDiv");
      //The seeds are added to the SpiralDiv
      SeedParent.classList.add("seedDiv");
    
      SpiralDiv.appendChild(SeedParent);
      
      var Angle = 2 * Math.PI / NumberOfSeeds * Index;
      SeedParent.style.setProperty("--r", (Angle + Math.PI) + "rad");
    }
    
    const NumberOfSeeds = 10; //The number of elements in the circle
    
    for (let index = 0; index < NumberOfSeeds; index++) {
      AddImageToDiv(index + 1);
    }
    /* SEED ANIMATION */
    .seedDiv {
      position: absolute;
      width: 110px;
      background: linear-gradient(45deg, transparent, orange);
      -webkit-mask: url('https://lh3.googleusercontent.com/proxy/5xwQBbe4tsxwqwDthUeiLd_UFRsFEDl3_TT2skCOLzQpgAnKyI5PT9Lfukwjdan6jDQNL0g7qrtupEXM_wQN3VZW') center/contain no-repeat;
              mask: url('https://lh3.googleusercontent.com/proxy/5xwQBbe4tsxwqwDthUeiLd_UFRsFEDl3_TT2skCOLzQpgAnKyI5PT9Lfukwjdan6jDQNL0g7qrtupEXM_wQN3VZW') center/contain no-repeat;
      animation: RotateAroundCenter 3s 3s ease-in-out forwards;
      transform: rotate(0)  rotate(var(--r)) translate(-200px);
    }
    
    .seedDiv::before {
      content: "";
      display: block;
      padding-top: 113.6%;
    }
    
    #spiralDiv {
      display: flex;
      justify-content: center;
      position: relative;
      top: 200px;
    }
    
    @keyframes RotateAroundCenter {
      100% {
        transform: rotate(-360deg) rotate(var(--r)) translate(-200px);
      }
    }
    <div id="spiralDiv"></div>

    Related question to understand why order matters when chaining transformation: Why does order of transforms matter? rotate/scale doesn't give the same result as scale/rotate


    We can still optimize the last code:

    const NumberOfSeeds = 10; //The number of elements in the circle
    
    for (let index = 0; index < NumberOfSeeds; index++) {
      var SeedParent = document.createElement("div");
      var SpiralDiv = document.getElementById("spiralDiv");
      //The seeds are added to the SpiralDiv
      SeedParent.classList.add("seedDiv");
    
      SpiralDiv.appendChild(SeedParent);
      
      var Angle = 360 / NumberOfSeeds * (index + 1);
      SeedParent.style.setProperty("--r", (Angle + 180) + "deg");
    }
    /* SEED ANIMATION */
    .seedDiv {
      position: absolute;
      width: 110px;
      background: linear-gradient(45deg, transparent, orange);
      -webkit-mask: url('https://lh3.googleusercontent.com/proxy/5xwQBbe4tsxwqwDthUeiLd_UFRsFEDl3_TT2skCOLzQpgAnKyI5PT9Lfukwjdan6jDQNL0g7qrtupEXM_wQN3VZW') center/contain no-repeat;
              mask: url('https://lh3.googleusercontent.com/proxy/5xwQBbe4tsxwqwDthUeiLd_UFRsFEDl3_TT2skCOLzQpgAnKyI5PT9Lfukwjdan6jDQNL0g7qrtupEXM_wQN3VZW') center/contain no-repeat;
      animation: RotateAroundCenter 3s 3s ease-in-out forwards;
      transform: rotate(var(--r)) translate(-200px);
    }
    
    .seedDiv::before {
      content: "";
      display: block;
      padding-top: 113.6%;
    }
    
    #spiralDiv {
      display: flex;
      justify-content: center;
      position: relative;
      top: 200px;
    }
    
    @keyframes RotateAroundCenter {
      100% {
        transform: rotate(calc(var(--r) - 360deg)) translate(-200px);
      }
    }
    <div id="spiralDiv"></div>