Hello everyone, I am 97 front end little fresh meat green raw, daily docking designer “strange” idea 🤔. Today we have a practical front end tip for you. In doing some H5 activity page, the designer Yamada out of a planet around the movement of 🪐 renderings, five balls need to rotate around the inclined orbit. JavaScript can draw a lot of complex animations, and there are many implementations of various planet classes on the Web, so how do you achieve this effect with CSS?

CSS implementation effect:

Link to online Demo

Get to know CSS Transform

Before we get started, let’s review some CSS Transform

Transform

Coordinate system

X-axis: The origin is in the upper left corner of the screen, and the X-axis is in the horizontal direction

Y axis: The origin is in the upper left corner of the screen, and the vertical direction is the Y axis

Z axis: the upper left corner of the screen is the origin, and the axis perpendicular to the computer is the Z axis, which can be understood as the axis pointing towards us

The order in which the transform parameters are executed

In transform, the effects passed in are executed in sequence (in effect, it can be understood that the effects passed in are executed first, but the actual calculation is carried out in the way of matrix: matrix), and the transformation will change the coordinate axis.

Skew, Scale, rotate… It’s basically implemented with matrix, but the rotate form is much easier to use.

There are two matrix methods: 1, matrix() 3×3 matrix 2, matrix3D () 4×4 matrix

Here’s an example:

transform: rotate(30deg); / / matrix (cos30 °, sin30 °, - sin30 °, cos30 °.0.0);
transform: matrix(0.866025.0.500000, -0.500000.0.866025.0.0);
Copy the code

The results are the same in both cases, but if you want to do rotate with a pure matrix, you have to manually compute the various sines and cosines.

Here are two simple examples to help you quickly understand the order in which a Transform is computed (executed) :

  1. Execute scaleX(0.5) to turn the square into a rectangle, then rotateZ(45deg) to rotate the elements 45 degrees clockwise, resulting in a tilted rectangle:
.test_transform {
  width:100px;
  height:100px;
  background-color: #c685d9;
  transform: rotate(45deg) scaleX(0.5);
}
Copy the code

  1. First, rotate clockwise by 45 degrees, and then compress the X-axis to obtain a diamond:
.test_transform_2{...transform: scaleX(0.5) rotate(45deg);
}
Copy the code

rotate

So the simple rotation, rotate(45deg) is rotateZ(45deg).

scale

Scale, such as scaleY(0.6)

Scale () only applies to transformations on Euclidean planes (two-dimensional planes). If you want to scale in space, you must use scale3D().

Here’s how to implement a simple single-ball wrap effect using CSS:

Achieve single ball surround effect

Step1 – base style

<div className='wrap'>
  <div className='planet'>
    <div className='ball' />
  </div>
</div>
Copy the code
.wrap {
  display: flex;
  background-image: linear-gradient(180deg.# 020205 0%.#170f39 51%.#35247a 95%);
  width: 600px;
  height: 600px;
  align-items: center;
  justify-content: center;
}

.planet {
  position: absolute;
  border: 2px solid #fff;
  transform-style: preserve-3d;
  width: 200px;
  height: 200px;
}

.ball {
  width: 50px;
  height: 50px;
  position: absolute;
  border-radius: 50%;
  background-color: yellowgreen;
}
Copy the code

Step2 – cross the circle

.ball{/ /...left: calc(50% - 25px);
  top: -25px;
}
Copy the code

Why do you need this step? If you add border-RADIUS: 50% to the square and turn it into a circle, the points in the current graph will be attached to the circle orbit, and the radius of the four corners of the square will be larger than the radius of the circle.

Step3 – rotate orbit

.planet {
  transform: rotateZ(45deg);
}

.ball {
  transform: rotateZ(-45deg); // Neutral orbit rotation}Copy the code

Step4 – compress the Y axis of the orbit to form a 3D effect

Notice the order, you need to rotate and then compress, otherwise you will become an inclined rectangle

.planet {
  transform: scaleY(0.5) rotateZ(45deg);
}

.ball{// scaleY compression of neutral orbits,2 * 0.5 = 1Return to the original, notice the incoming order, and.planettransformIt's the opposite. It's like having several different locks in a row, and when you open them, you have to unlock them in the reverse ordertransform: rotateZ(-45deg) scaleY(2);
}
Copy the code

Step5 – turn the orbital into an ellipse

.planet {
  border-radius: 50%;
}
Copy the code

Step6 – make the track spin

The steps above have turned the original figure into a figure that looks like an orbit and a planet. By following the neutralizing rules for rotateZ and scaleY, you can get the orbit to rotate while keeping the sphere’s shape uncompressed:

// Revolution animation@keyframes planet-rotate {
  0% {
    transform:  scaleY(0.5) rotate(0);
  }
  100% {
    transform:  scaleY(0.5) rotate(360deg); }} // Spin animation@keyframes self-rotate {
  0% {
    transform: rotate(0) scaleY(2);
  }
  100% {
    transform: rotate(-360deg) scaleY(2); }}.planet {
  animation: planet-rotate 20s linear infinite;
}

.ball {
  animation: self-rotate 20s linear infinite;
}
Copy the code

Step7 – make the orbital tilt

Using the same order as the Transform, the rotate(Z) is executed at the end to make the whole plane feel tilted

@keyframes planet-rotate {
  0% {
    transform:  rotate(45deg) scaleY(0.5) rotate(0);
  }
  100% {
    transform:  rotate(45deg) scaleY(0.5) rotate(360deg); }}@keyframes self-rotate {
  0% {
    transform: rotate(0) scaleY(2) rotate(-45deg);
  }
  100% {
    transform: rotate(-360deg) scaleY(2) rotate(-45deg); }}Copy the code

Achieve multi-ball surround effect

Because an orbital container can only guarantee the movement of four balls in circular orbit at most, if the movement of more than four balls is to be realized, in fact, it only needs to overlap multiple orbits + the plane of the ball, but only show one border.

Motion model

Independent moving individual = single sphere + orbit of single sphere (parent element) Multi-sphere encircling = independent moving individual * N overlapped in the same position, and only the orbit of the lowest sphere was shown, and the rest of the orbit was hidden. Finally, the initial rotation position of each independent moving individual was offset

Implementation steps

Use React + Sass to make it easier to write:

1. Write the basic DOM structure and style

Jsx

// Pass in data
const dataSource = [
  {
    name: 'yamada',},// ...
];

// Render a DOM with a ball + name
const renderCircleBoxItem = (name: string) = > {
  return (
    <div className={styles.circleBoxItem}>
      <div className={styles.ball} />
      <div className={styles.name}>{name}</div>
    </div>
  );
};

// Render multiple rotations based on the number of datasources
<div className={styles.circleBoxWrap}>
  {
    dataSource.map((item, key) => (
      <div key={key} className={styles.circleBox}>{renderCircleBoxItem(item.name)}</div>))}</div>
Copy the code

Sass

// Track layer keyframes
@function getPlanetRotate($rotateValue) {
  @return rotate(45deg) scaleY(0.5) rotate(#{$rotateValue});
}

@keyframes planet-rotate {
  0% {
    transform: getPlanetRotate(0deg); 100%} {transform: getPlanetRotate(360deg); }}// Spin the sphere keyframes
@function getSelfRotate($rotateValue) {
  @return rotate(#{$rotateValue}) scaleY(2) rotate(-45deg) scale(1)) translateX(50px);
  // Here translateX is used to correct the ball's position so that it stays in orbit as much as possible
}

@keyframes self-rotate {
  0% {
    transform: getSelfRotate(0deg); 100%} {transform: getSelfRotate(-360deg); }}// The orbit element contains a sphere
.circleBox {
  // Uniform rotation speed
  $planet-rotate-speed: 30s;
  
  // Specify the size of the track
  width: 648px;
  height: 648px;
  position: absolute;
  transform-style: preserve-3d;
  	
  // Make the orbit circular
  border-radius: 50%;

  // Sphere element (sphere + label)
  .circleBoxItem {
    position: absolute;
    display: flex;
    flex-direction: row;
    align-items: center;
      
    // Position correction so that the ball + text unit is centered on one side of the parent element (which is a square without border-RADIUS)
    //, in order to form an elliptical orbit, always fit the orbit motion
    width: 200px; // Ball + text fixed width, easy to calculate position correction
    top: -30px; // The diameter of the ball is 60px, offset up by half
    left: calc(50% - #{p2r(100)}); // Horizontal center
      
    .ball {
      width: 60px;
      height: 60px;
      border-radius: 50%;
      overflow: hidden;
      border: 6px solid #fff;
      background-color: #6d45ca;
      margin-right: p2r(20);
    }
  
    .name {
      // Text-related styles...}} &:nth-child(1) {
    border: p2r(2) solid #fff;
    
    animation: planet-rotate $planet-ratate-speed linear infinite;
    .circleBoxItem {
      animation: self-rotate $planet-ratate-speedlinear infinite; }}}Copy the code

2. Handle track deviation

In order to offset the spheres, we need to offset the initial rotation position of each individual moving by rewriting the keyframes of the orbit:

Sass

// Use CSS variables to control the step size (i.e. offset distance)
:root {
  --planet-rotate-step: 72deg; // 72 = 360/5
}

@function getPlanetRotate($rotateValue) {
  @return rotate(45deg) scaleY(0.5) rotate(#{$rotateValue});
}

@keyframes planet-rotate-1{{0%transform: getPlanetRotate(0deg); 100%} {transform: getPlanetRotate(360deg); }}@keyframes planet-rotate-2{{0%transform: getPlanetRotate(calc(0deg + var(--planet-rotate-step) * 1)); 100%} {transform: getPlanetRotate(calc(360deg + var(--planet-rotate-step) * 1)); }}@keyframes planet-rotate-3{{0%transform: getPlanetRotate(calc(0deg + var(--planet-rotate-step) * 2)); 100%} {transform: getPlanetRotate(calc(360deg + var(--planet-rotate-step) * 2)); }}// ...
Copy the code

3. Handle ball motion

The sphere needs to be positioned for the rotation path of the orbit. Write the keyframes of the sphere:

@function getSelfRotate($rotateValue) {
  @return rotate(#{$rotateValue}) scaleY(2) rotate(-45deg) scale(1) translateX(50px);
}

@keyframes self-rotate-1{{0%transform: getSelfRotate(0deg); 100%} {transform: getSelfRotate(-360deg); }}@keyframes self-rotate-2{{0%transform: getSelfRotate(calc(0deg - var(--planet-rotate-step) * 1)); 100%} {transform: getSelfRotate(calc(-360deg - var(--planet-rotate-step) * 1)); }}@keyframes self-rotate-3{{0%transform: getSelfRotate(calc(0deg - var(--planet-rotate-step) * 2)); 100%} {transform: getSelfRotate(calc(-360deg - var(--planet-rotate-step) * 2)); }}// ...

Copy the code

Write the Animations statement

Adjust the animation of the element:

.circleBox{&:nth-child(1) {
    // Show only the first track
    border: p2r(2) solid #fff;

    animation: planet-rotate-1 $planet-rotate-speed linear infinite;
    .circleBoxItem {
      animation: self-rotate-1 $planet-rotate-speedlinear infinite; }} &:nth-child(2) {
    animation: planet-rotate-2 $planet-rotate-speed linear infinite;
    .circleBoxItem {
      animation: self-rotate-2 $planet-rotate-speedlinear infinite; }} &:nth-child(3) {
    animation: planet-rotate-3 $planet-rotate-speed linear infinite;
    .circleBoxItem {
      animation: self-rotate-3 $planet-rotate-speedlinear infinite; }}// ...
}
Copy the code

5. Automatically calculate the ball spacing with CSS variables

In real scenarios, the ball spacing (offset distance) needs to be automatically processed according to the number of incoming data. At this time, you can use JS to dynamically calculate and modify the CSS variables just now to perfect solve:

const number = dataSource.length;
const step = 360 / number;

document.documentElement.style.setProperty('--planet-rotate-step'.`${step}deg`);
Copy the code

Thanks for watching, we’ll see you next time


The 2021 college enrollment (for those graduating in 2022) has officially begun. To contact us please specify the source of the nugget, see the image below 👇 for more information ~