Search code examples
csscss-shapescss-transforms

Div elements to follow a curved path with CSS3


So the basic idea is 1 - 9 seats at a curved table as though you are looking at them through first person view. I'm trying to get div elements which would be seats to flow on the outside of another div element that has a border radius to make it a semi oblong circle. I've found a few examples with an element being animated to flow across the container in an arc, but I will need the div/seats to be static. I am looking for any ideas or examples that can lead me in the right path.


Solution

  • Finding points on ellipse and translating:

    If your oblong circle resembles an ellipse then you can find points on the ellipse using mathematical formula and then translate each div element to that particular point.

    Mathematical formula for calculating point (x,y) on an ellipse is(a * cos(t), b * sin(t)). In this formula, a represents the radius of the ellipse in x-axis, b represents the radius of the ellipse in the y-axis and t represents the angle in radians. Angle in radians = Angle in degrees * pi / 180.

    To make use of this approach, we do the following:

    • Place the div elements absolutely at the centre point of the ellipse.
    • Calculate the (x,y) corresponding to each angle and translate the div to its place by using transform: translateX(...) translateY(...). The angles are in steps of 22.5 deg because there are a total 9 elements to be placed within 180 degrees.

    .container {
      position: relative;
      height: 400px;
      width: 600px;
      padding: 12.5px;
      border: 1px solid;
      border-radius: 50%;
    }
    div > div {
      position: absolute;
      top: 0px;
      left: 0px;
      height: 50%;
      width: 50%;
      transform-origin: bottom right;
    }
    div > div:after {
      position: absolute;
      content: '';
      bottom: 0px;
      right: 0px;
      height: 25px;
      width: 25px;
      background: black;
      border-radius: 50%;
      transform: translateX(50%) translateY(50%);
    }
    div > div:after {
      background: red;
    }
    div > div:nth-child(n+4):after {
      background: orange;
    }
    div > div:nth-child(n+7):after {
      background: green;
    }
    div > div:nth-child(1) {
      transform: translateX(-300px) translateY(0px);
    }
    div > div:nth-child(2) {
      transform: translateX(-277.17px) translateY(-76.5px);
    }
    div > div:nth-child(3) {
      transform: translateX(-212.13px) translateY(-141.42px);
    }
    div > div:nth-child(4) {
      transform: translateX(-114.80px) translateY(-184.77px);
    }
    div > div:nth-child(5) {
      transform: translateX(0px) translateY(-200px);
    }
    div > div:nth-child(6) {
      transform: translateX(114.80px) translateY(-184.77px);
    }
    div > div:nth-child(7) {
      transform: translateX(212.13px) translateY(-141.42px);
    }
    div > div:nth-child(8) {
      transform: translateX(277.17px) translateY(-76.5px);
    }
    div > div:nth-child(9) {
      transform: translateX(300px) translateY(0px);
    }
    <div class="container">
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
    </div>

    enter image description here

    Note: The coordinates are approximate values and hence they may not align 100% correctly.


    Using rotation and scale transform: (original idea)

    The below snippet provides a very rough idea on how to position the elements along a circle. It is by no means a complete output but you can adapt it to fit your needs.

    The components are very simple:

    • One container element which is a circle and serves as the reference element against which the seats are positioned.
    • 9 individual div elements for each seat. All of them have 50% width of the container and 50% height.
    • A pseudo-element (:after) attached to the child div elements that produce the circle/dot like seats and they are absolutely positioned at the bottom of the container.
    • Each child div element is rotated by 180/(n-1) deg as we need them to be positioned around a semicircle.

    .container {
      position: relative;
      height: 200px;
      width: 200px;
      border: 1px solid;
      border-radius: 50%;
    }
    div > div {
      position: absolute;
      top: 0px;
      left: 0px;
      height: 50%;
      width: 50%;
      transform-origin: bottom right;
    }
    div > div:after {
      position: absolute;
      content: '';
      bottom: 0px;
      left: 0px;
      height: 25px;
      width: 25px;
      background: black;
      border-radius: 50%;
      transform: translateY(50%);
    }
    div > div:nth-child(1) {
      transform: rotate(0deg);
    }
    div > div:nth-child(2) {
      transform: rotate(22.5deg);
    }
    div > div:nth-child(3) {
      transform: rotate(45deg);
    }
    div > div:nth-child(4) {
      transform: rotate(67.5deg);
    }
    div > div:nth-child(5) {
      transform: rotate(90deg);
    }
    div > div:nth-child(6) {
      transform: rotate(112.5deg);
    }
    div > div:nth-child(7) {
      transform: rotate(135deg);
    }
    div > div:nth-child(8) {
      transform: rotate(157.5deg);
    }
    div > div:nth-child(9) {
      transform: rotate(180deg);
    }
    div > div:after {
      background: red;
    }
    div > div:nth-child(n+4):after {
      background: orange;
    }
    div > div:nth-child(n+7):after {
      background: green;
    }
    
    /* Just for demo */
    
    .container{
      transition: all 1s;
    }  
    .container:hover {
      height: 400px;
      width: 400px;
      transition: all 1s;
    }
    <div class="container">
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
    </div>

    There is a simple method to convert the above into an oblong circle and that would be to scale the container in the X-axis. One point to note is that the children would also get scaled and hence would need to be reverse transformed.

    .container {
      position: relative;
      height: 200px;
      width: 200px;
      border: 1px solid;
      border-radius: 50%;
      transform: scaleX(1.25);
      transform-origin: left;
    }
    div > div {
      position: absolute;
      top: 0px;
      left: 0px;
      height: 50%;
      width: 50%;
      transform-origin: bottom right;
    }
    div > div:after {
      position: absolute;
      content: '';
      bottom: 0px;
      left: 0px;
      height: 25px;
      width: 25px;
      background: black;
      border-radius: 50%;
      transform: translateY(50%);
    }
    div > div:nth-child(1) {
      transform: rotate(0deg);
    }
    div > div:nth-child(2) {
      transform: rotate(22.5deg);
    }
    div > div:nth-child(3) {
      transform: rotate(45deg);
    }
    div > div:nth-child(4) {
      transform: rotate(67.5deg);
    }
    div > div:nth-child(5) {
      transform: rotate(90deg);
    }
    div > div:nth-child(6) {
      transform: rotate(112.5deg);
    }
    div > div:nth-child(7) {
      transform: rotate(135deg);
    }
    div > div:nth-child(8) {
      transform: rotate(157.5deg);
    }
    div > div:nth-child(9) {
      transform: rotate(180deg);
    }
    div > div:after {
      background: red;
    }
    div > div:nth-child(n+4):after {
      background: orange;
    }
    div > div:nth-child(n+7):after {
      background: green;
    }
    
    /* Just for demo */
    
    .container {
      transition: all 1s;
    }
    .container:hover {
      height: 400px;
      width: 400px;
      transform: scaleX(1.25);
      transform-origin: left;
    }
    <div class="container">
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
    </div>

    The first method is the perfect and recommended approach as it doesn't cause any distortion to the div elements. The second is rough idea which avoids complex trigonometric calculations.