Why do we declare no margin on the body? The <body> element has some margin set to it by default in the user agent stylesheet. This prevents the elements inside the <body> to touch the edges of the screen. Since we want our wrapper to cover the entire screen, edge to edge, we removed that default margin on <body> element by setting it to 0.
We’ve given our .fullpage-wrapper the full height of the viewport. We don’t have to specify a width because a div is full width by default. We could have gone with another approach by setting both the width and height of the element to 100% but that comes with some possible drawbacks as more elements are added to the screen. Using viewport units ensures our wrapper always occupies the full vertical space of the screen, regardless of what it is or what other elements are added to the layout.
We also used a radial gradient on our wrapper using radial-gradient() CSS function. The parameters inside the function are the color start and end points. So, the center of the background will be more #353c44 and more #222931 towards the edges. It’s subtle, but a nice touch.
Centering the Reactor Container
Before we start creating our reactor, let’s create a container for it and center it:
This gives us a 300px x 300px box with dashed border. The margin: auto; declaration ensures equal horizontal spacing.
But why it doesn’t center it vertically?
Per CSS2 specs, if we give auto margin to the left and right side, then the entire available space will be equally divided to left and right margin. This isn’t the same case for the top and bottom margin though. For top and bottom margin the CSS2 spec says:
If margin-top, or margin-bottom are auto, their used value is 0.
However, we do have a good news. The flexbox layout follows new rules of alignment, and here’s what the Flexbox spec has to say:
Prior to alignment via justify-content and align-self, any positive free space is distributed to auto margins in that dimension.
This means if the element in consideration is displayed as a flex item, then margin: auto; will work in both the directions. Let’s make our wrapper a flex container and its child elements flex items:
We know that new elements in HTML are created from left to right (for left-to-right languages), or top to bottom. Elements never overlap, until you provide some negative margin.
So, how are we going to display concentric circles? We will use absolute positioning.
The default value of position property is static. Static and relative positioning follow the flow of top to bottom and left to right. However, an absolutely positioned element is not treated as a part of the document flow and can be positioned anywhere using the top, right, bottom and left properties.
Let’s start by creating the smallest circle:
<div class="fullpage-wrapper"> <div class="reactor-container"> <!-- the smallest circle --> <div class="core-inner"></div> </div> </div>
We need to center this div. You might be tempted to apply the same flexbox concept we used on the reactor element to center this circle as well. But, here’s what CSS2 spec has to say about setting margin: auto; on absolutely positioned elements:
If none of the three (top, height, and bottom) are auto: If both margin-top and margin-bottom are auto, solve the equation under the extra constraint that the two margins get equal values.
This means if an absolutely positioned element has any value for top, height and bottom other than auto, then setting the top and bottom margin to auto will center the element vertically.
Same case for horizontal centering: if an absolutely positioned element has any value for left, width and right other than auto, then setting the left and right margin to auto will center the element horizontally.
That means we don’t need flexbox layout to center an absolutely positioned element with a known height and width. We just have to make sure that we give some value to top, right, bottom and left other than auto. So, we will use 0:
We do not want to repeat these five lines for all the concentric circles we are going to have, so let’s create a separate class for this. We also don’t want to define border-radius: 50%; for all the divs that we want to display as circles, so we will create a class for that too:
Let’s take a break and understand the syntax of box-shadow first because we will be using it a lot. Here are what those values for box-shadow mean in that order:
x-offset: how much we want to push the shadow on the right side (x-axis). Negative values will push the shadow to the left side.
y-offset: how much we want to push the shadow up or down (y-axis).
blur-radius: how blurry we want our shadow to be.
spread-radius: how much we want our shadow to spread.
color: color of the shadow.
Play with these values a bit to see their effects in real time.
In real life, shadows don’t drop only outside of an object. Shadows drop upon the objects too. Imagine a pit dug by a dog, it will have a shadow inside it, right?
We can give a shadow inside an element using the inset keyword in the box-sizing property. To give an element both, outside and inside shadow, we simply separate them with a comma. Let’s do this to get an outside and inside glow to our reactor’s inner core:
Let’s create one more circle on top. We want the inner circle to display on top of the other circles, so we will add new circle divs *above* the inner-circle in code:
<div class="fullpage-wrapper"> <div class="reactor-container"> <!-- the second circle --> <div class="core-outer circle abs-center"></div> <!-- the smallest circle --> <div class="core-inner circle abs-center"></div> </div> </div>
The elements down in the code, are displayed above on the screen. If we put the core-outer below the core-inner in the code, then core-inner won’t be visible, because core-outer will cover it.
Let’s give style to outer-code. The outer core will be a little bigger than the inner core and we will give an outer and inner glow to core-outer too:
I want you to do one thing: look at the shadows (glow) and try to identify which one is of which circle. There are four shadows and two circles (until now).
To finish designing the core, we will need one more circle that will wrap the inner and outer circles:
<div class="fullpage-wrapper"> <div class="reactor-container"> <!-- the third circle --> <div class="core-wrapper circle abs-center"></div> <!-- the second circle --> <div class="core-outer circle abs-center"></div> <!-- the smallest circle --> <div class="core-inner circle abs-center"></div> </div> </div>
This one will be a little bigger, and will again have same shadows, we will use a dark background for core-wrapper:
Creating Reactor Coils and Rotating with CSS3 Transforms
We have the core of the reactor, now we need a tunnel around the core. Actually, we can create an illusion of a round tunnel by drawing just one more circle little bigger than the core-wrapper. Let’s do it:
<div class="fullpage-wrapper"> <div class="reactor-container"> <!-- the largest circle --> <div class="tunnel circle abs-center"></div> <!-- the third circle --> <div class="core-wrapper circle abs-center"></div> <!-- the second circle --> <div class="core-outer circle abs-center"></div> <!-- the smallest circle --> <div class="core-inner circle abs-center"></div> </div> </div>
Now, we want to place this coil in the center at top of the tunnel. Like this:
Our reactor-container is 300px x 300px, so the center is at 150px from top and left. The tunnel is 220px wide, so its radius will be 110px. This gives us the top offset of the coil: 150px - 110px.
We can keep left of the coil to 150px, but since our coil is 30px wide, it will shift the middle of the coil by 15px to right, that’s why we need to subtract 15px from 150px to get the left offset of the coil.
We can either calculate these ourselves and put the value, or we can use the CSS calc() function. Let’s use the CSS calc() function to calculate the top and left properties of the coil:
As you can see, the calc() function takes a mathematical expression as its argument and solves it.
Now we need eight such coils but they must lie on the tunnel. To do that, as discussed, we can simply place the eight coils at this same place, then transform their origin to the center of the reactor, and rotate each coil by an increment of 45 degrees.
We need to update the coil’s origin because by default it is set to the center of the coil; we want it at center of the reactor:
We will use transform-origin property to set the origin of the coil:
The first value, 15px, in transform-origin is the x-offset (horizontal distance) from the top-left corner of the element, and the second value, 110px, is the y-offset (vertical distance) from the top-left corner of the element.
The coil’s origin is now at the center of the reactor, let’s rotate it by 45 degrees using the CSS3 transform property and see what happens:
Before creating all the eight coils, let’s create a coil container div that will contain all the eight coils:
<div class="fullpage-wrapper"> <div class="reactor-container"> <div class="tunnel circle abs-center"></div> <div class="core-wrapper circle abs-center"></div> <div class="core-outer circle abs-center"></div> <div class="core-inner circle abs-center"></div> <!-- the coil container --> <div class="coil-container"> <div class="coil coil-1"></div> </div> </div> </div>
You will notice we also added a class “coil” to the “coil-1” element. We will keep all the common styles for coils in the “coil” class, and the individual coil element classes will only have their angle of rotation:
Notice, we also gave the duration of animation using animation-duration property. This defines how much time it should take to go from the “from” state to the “to” state defined using the @keyframes at-rule.
We need to change two things here: we want the animation to go on infinitely and we want a linear animation. You can see the animation is slow at the beginning, then fast, then again slow at the end—this behavior is defined by the timing function of an animation.
Cheers! Our Arc Reactor is ready and even with a little animation as an added bonus. To level this up, we could explore using custom properties to create reusable variables for our color and number values for easier maintenance. Similarly, we could look into using a preprocessor—like Sass, Less or PostCSS—to write functions that do the mathematical lifting for us. Would love to see examples like that in the comments!