SoatDev IT Consulting
SoatDev IT Consulting
  • About us
  • Expertise
  • Services
  • How it works
  • Contact Us
  • News
  • June 8, 2023
  • Rss Fetcher

An introduction to the 3D Hexagon and its React wrapper

Interactive hexagons | Created by author

We all are tired of boring enterprise projects where all web parts consist of React (Angular, Vue, whatever) forms, standard components, and state managers. Let’s bring some fun into our development life and build something non-trivial that can be used to build a fun web page or interactive service. Let me introduce the 3D Hexagon module and its React wrapper!

Concept

3D Hexagon

We all like geometrical shapes and interact with them all the time on the web and in real life. The most common 2D ones are rectangles and circles, and cubes and spheres from the 3D world. But what if we want to present something more complex on our site?

Then the range of available libraries/packages/ready solutions drastically decreases, and finding something good becomes challenging. I want to make this gap a bit smaller, so I built a tiny module that builds 3D hexagons using pure CSS and a React component wrapper to represent its graphical part (however, you can do it on your own with plain HTML if needed. An example will follow). These hexagons can encapsulate various HTML blocks (like text, images, and videos) and can interact independently with their sides. This makes these shapes useful for various purposes.

You may check the following NPM packages: hexagon-3d and hexagon-3d-react

Live demo and Live demo code

Implementation

Since we’re talking about geometrical shapes, let’s start with some math, which helps us build a 3D hexagon. Hexagon is a six-angled shape with all sides of equal length. 3D hexagon consists of eight independent parts, with the top and bottom 2D hexagons and six rectangles as its shape sides.

Since we are building our hexagon via CSS, we technically will have eight HTML divs. Each will represent a hexagonal 2D face. All faces are originally placed in 2D, and our concept will shift them into the right position, then rotate around the x- and z-axes into the pseudo-3D CSS space. For this, we need to write several equations.

Hexagon main parameters

The size of our hexagon is determined by the width parameter, which will be passed by the user (default value set to 70). For naming purposes, let’s rename our hexagon’s width to small height:

smallHeight = width;

The hexagon diameter (or the distance between the furthest points of a hexagon), let’s call it height2d, can be determined as:

height2D = width / cos(30);

or

height2D = width * 1.1547;

Finally, the side length of the hexagon (sideLength) is just:

sideLength = height2D / 2;

To summarize, we need three constants to do all necessary shifts:

const height2D = width * 1.1547;
const sideLength = height2D / 2;
const smallHeight = width;

Important: we must assume our hexagon can dynamically change height in one of two directions: to the top or to the bottom (see images below). This direction is determined by the growTop parameter and assumes slightly different calculations of hexagon-faced positions. These calculations will be explained later.

The Hexagon side faces growth directions

The hexagon side’s divs will be shifted and rotated differently based on the chosen growth direction. During the growth to bottom, we operate with the top side points whose positions are always static, and during the top growth, we also need to consider height changes. More on that in the hexagon-3d module description.

hexagon-3d module

All the logic of this tiny module is encapsulated in the hexagon.service.ts file. Let’s go into its details later.

Except for the abovementioned equations, we also need a mechanism to create hexagons from the top and bottom div rectangles. Let’s use the CSS mechanism for that:

clipPath: 'polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%)',

Common parts of top and bottom div styling can be moved into separate objects:

const hexagonStyleCommon = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
position: 'relative',
width: width + 'px',
height: height2D + 'px',
cursor: 'pointer',
clipPath: 'polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%)',
background: topBottomColor,
overflow: 'hidden',
};

The same can be done for the common part of the side’s divs:

const sideStyleCommon = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
width: sideLength + 'px',
height: height + 'px',
cursor: 'pointer',
opacity: opacity,
boxSizing: 'border-box',
background: sidesColor,
boxShadow: showShadow ? shadowColor : 'unset',
transition: showTransition ? 'height 0.5s ease-out' : 'unset',
overflow: 'hidden',

All hexagon divs will be encapsulated into the main div container with a set of important properties:

container: {
style: {
position: 'relative',
transformStyle: 'preserve-3d',
transform: `rotateX(${rotateX}deg) rotateZ(${rotateZ}deg)`,
width: 'fit-content',
},
.......

transformStyle: ‘preserve-3d’ — tells CSS that we are going to work in the 3D space

transform: `rotateX(${rotateX}deg) rotateZ(${rotateZ}deg)` — provides hexagon rotation on x and z directions based on user inputs

width: fit-content — hexagon uses only space needed for all of its sides

The next sub-objects contain styling for each side, but before looking at it, we need to dive into some math related to how the hexagon’s height will change — to the top or bottom.

Growth to bottom

Initial position of hexagon face 1 relative to the top face

As I mentioned above, to move the hexagon sides from the initial 2D position, we need to shift them into the right position and then rotate for the correct angle around the x and z axes first. Let’s see an example based on side 1 (front side) of the hexagon. To move the front and other sides to the bottom, we operate with its top corner point, which makes this movement a bit easier than movements to the top. The points always have static positions and do not relate to the height, which grows to the bottom of the shape.

Based on this, let’s do our initial shift to get the top left point of the rectangle into the correct position:

left: smallHeight / 2 + 'px'

Then we need to tell CSS that rotation will happen around the top left point:

transformOrigin: 'top left'

And finally, we make a rotation:

transform: 'rotateZ(-30deg) rotateX(-90deg)'

Important: Hexagon is already rotated by 30 degrees by our top styles so we need to account for it in the rotateZ parameter.

The whole object will look like this:

side1: {
id: 'side1',
style: {
...sideStyleCommon,
left: smallHeight / 2 + 'px',
transformOrigin: 'top left',
transform: 'rotateZ(-30deg) rotateX(-90deg)',
}
},

You may see all the other bottom transitions in the full code example below:

container: {
style: {
position: 'relative',
transformStyle: 'preserve-3d',
transform: `rotateX(${rotateX}deg) rotateZ(${rotateZ}deg)`,
width: 'fit-content',
},
top: {
id: 'top',
style: {
...hexagonStyleCommon,
zIndex: 2
}
},
side1: {
id: 'side1',
style: {
...sideStyleCommon,
left: smallHeight / 2 + 'px',
transformOrigin: 'top left',
transform: 'rotateZ(-30deg) rotateX(-90deg)',
}
},
side2: {
id: 'side2',
style: {
...sideStyleCommon,
left: (smallHeight / 2 - sideLength) + 'px',
transformOrigin: 'top right',
transform: 'rotateZ(30deg) rotateX(-90deg)',
}
},
side3: {
id: 'side3',
style: {
...sideStyleCommon,
left: -sideLength + 'px',
top: (height2D - sideLength) / 2 + sideLength + 'px',
transformOrigin: 'top right',
transform: 'rotateZ(90deg) rotateX(-90deg)'
}
},
side4: {
id: 'side4',
style: {
...sideStyleCommon,
left: -sideLength + 'px',
top: (height2D - sideLength) / 2 + 'px',
transformOrigin: 'top right',
transform: 'rotateZ(150deg) rotateX(-90deg)'
}
},
side5: {
id: 'side5',
style: {
...sideStyleCommon,
left: smallHeight + 'px',
top: (height2D - sideLength) / 2 + 'px',
transformOrigin: 'top left',
transform: 'rotateZ(-150deg) rotateX(-90deg)'
}
},
side6: {
id: 'side6',
style: {
...sideStyleCommon,
left: smallHeight + 'px',
top: (height2D - sideLength) / 2 + sideLength + 'px',
transformOrigin: 'top left',
transform: 'rotateZ(-90deg) rotateX(-90deg)'
}
},
bottom: {
id: 'bottom',
style: {
...hexagonStyleCommon,
marginTop: -height2D + 'px',
zIndex: 1,
transform: `translateZ(${-height}px)`,
transition: 'transform 0.5s ease-out',
}
}
}
}

Growth to top

Initial position of hexagon face 1 relative to the bottom face

Sides positioning to the top occurs relative to the bottom hexagon. This differs from the bottom because the left bottom point of the rectangle (which is best for proper initial shift) has a dynamic initial position related to face height growth. Therefore, the height parameter should be accounted into the object styling shift.

Based on this, let’s do our initial shift to get the bottom-left point of the rectangle into the correct position:

left: smallHeight / 2 + 'px',
top: (height2D - smallHeight) + 'px'

Then we need to tell the CSS that the rotation will happen around the bottom-left point:

transformOrigin: 'bottom left'

And finally, we create a translation based on height value (here is where height comes into play!) and rotation:

transform: `translateY(${-height + smallHeight}px) rotateZ(-30deg) rotateX(270deg)`,

Tip: marginTop shift can be used instead of the translateY operation. It will have the same effect, and the side will be positioned correctly.

You can see all other top transitions in the full code example below:

container: {
style: {
position: 'relative',
transformStyle: 'preserve-3d',
transform: `rotateX(${rotateX}deg) rotateZ(${rotateZ}deg)`,
width: 'fit-content',
},
top: {
id: 'top',
style: {
...hexagonStyleCommon,
//marginBottom: height2D + 'px',
zIndex: 2,
transform: `translateZ(${height}px)`,
transition: 'transform 0.5s ease-out',
}
},
side1: {
id: 'side1',
style: {
...sideStyleCommon,
left: smallHeight / 2 + 'px',
top: (height2D - smallHeight) + 'px',
transformOrigin: 'bottom left',
transform: `translateY(${-height + smallHeight}px) rotateZ(-30deg) rotateX(270deg)`,
}
},
side2: {
id: 'side2',
style: {
...sideStyleCommon,
top: (height2D - sideLength) / 2 + sideLength - smallHeight + 'px',
transformOrigin: 'bottom left',
transform: `translateY(${-height + smallHeight}px) rotateZ(30deg) rotateX(-90deg)`,
}
},
side3: {
id: 'side3',
style: {
...sideStyleCommon,
left: -sideLength + 'px',
top: (height2D - sideLength) / 2 + sideLength - smallHeight + 'px',
transformOrigin: 'bottom right',
transform: ` translateY(${-height + smallHeight}px) rotateZ(90deg) rotateX(-90deg)`
}
},
side4: {
id: 'side4',
style: {
...sideStyleCommon,
left: -sideLength + 'px',
top: (height2D - sideLength) / 2 - smallHeight + 'px',
transformOrigin: 'bottom right',
transform: `translateY(${-height + smallHeight}px) rotateZ(150deg) rotateX(-90deg)`
}
},
side5: {
id: 'side5',
style: {
...sideStyleCommon,
left: smallHeight + 'px',
top: (height2D - sideLength) / 2 - smallHeight + 'px',
transformOrigin: 'bottom left',
transform: `translateY(${-height + smallHeight}px) rotateZ(-150deg) rotateX(-90deg)`
}
},
side6: {
id: 'side6',
style: {
...sideStyleCommon,
left: smallHeight + 'px',
top: (height2D - sideLength) / 2 - smallHeight + sideLength + 'px',
transformOrigin: 'bottom left',
transform: `translateY(${-height + smallHeight}px) rotateZ(-90deg) rotateX(-90deg)`
}
},
bottom: {
id: 'bottom',
style: {
...hexagonStyleCommon,
marginTop: - height2D + 'px',
zIndex: 1,
}
}
}

That’s it! After all styles are ready, we may build a hexagon visual representation.

It’s really easy to do by applying styles and ids to the corresponding HTML containers, as you can see below:

<div style={hexagonData.container.style}>
<div id={hexagonData.container.top.id} style={hexagonData.container.top.style}>
{topChildren}
</div>

<div id={hexagonData.container.side1.id} style={hexagonData.container.side1.style}>
{side1Children}
</div>

<div id={hexagonData.container.side2.id} style={hexagonData.container.side2.style}>
{side2Children}
</div>

<div id={hexagonData.container.side3.id} style={hexagonData.container.side3.style}>
{side3Children}
</div>

<div id={hexagonData.container.side4.id} style={hexagonData.container.side4.style}>
{side4Children}
</div>

<div id={hexagonData.container.side5.id} style={hexagonData.container.side5.style}>
{side5Children}
</div>

<div id={hexagonData.container.side6.id} style={hexagonData.container.side6.style}>
{side6Children}
</div>

<div id={hexagonData.container.bottom.id} style={hexagonData.container.bottom.style}>
{bottomChildren}
</div>
`</div>

But, if you are developing using React, you can create a tiny parametrized component that will make your life even easier ;).

hexagon-3d-react module

All logic of this tiny module is encapsulated in the Hexagon3D.tsx file. Let’s look at in detail:

const Hexagon3D = (props: IHexagonProps & IHexagonChildrensProps): JSX.Element => {
const {
width,
height,
rotateZ,
rotateX,
growTop,
showShadow,
shadowColor,
topBottomColor,
sidesColor,
opacity,
showTransition,
topChildren,
bottomChildren,
side1Children,
side2Children,
side3Children,
side4Children,
side5Children,
side6Children,
} = props;
const hexagonData = hexagon3D({
width, height, rotateZ, rotateX, growTop, showShadow, shadowColor, topBottomColor, sidesColor, opacity, showTransition});

return (<div style={hexagonData.container.style as CSSProperties}>
<div id={hexagonData.container.top.id} style={hexagonData.container.top.style as CSSProperties}>
{topChildren}
</div>

<div id={hexagonData.container.side1.id} style={hexagonData.container.side1.style as CSSProperties}>
{side1Children}
</div>

<div id={hexagonData.container.side2.id} style={hexagonData.container.side2.style as CSSProperties}>
{side2Children}
</div>

<div id={hexagonData.container.side3.id} style={hexagonData.container.side3.style as CSSProperties}>
{side3Children}
</div>

<div id={hexagonData.container.side4.id} style={hexagonData.container.side4.style as CSSProperties}>
{side4Children}
</div>

<div id={hexagonData.container.side5.id} style={hexagonData.container.side5.style as CSSProperties}>
{side5Children}
</div>

<div id={hexagonData.container.side6.id} style={hexagonData.container.side6.style as CSSProperties}>
{side6Children}
</div>

<div id={hexagonData.container.bottom.id} style={hexagonData.container.bottom.style as CSSProperties}>
{bottomChildren}
</div>
</div>)
};

As you may see, it’s a very simple React component that gets all hexagon-3d parameters as inputs and draws a nice hexagon on your webpage.

Important: you may find the list of all hexagon-3d and hexagon-3d-react modules on their corresponding NPM pages.

Conclusion

That’s it! We have done our fancy hexagon builder module. I hope you enjoy playing with it and build many nice interactive applications based on this shape.

If you liked this article, feel free to leave comments and questions. Also, I will be happy to see how you used hexagon-3d in the comments. Drop your links!

NPM packages: hexagon-3d and hexagon-3d-react

Live demo and Live demo code

Enjoy!


Entertaining Web Geometry: Building an Interactive 3D CSS Hexagon was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.

Previous Post
Next Post

Recent Posts

  • Trump pulls Musk ally’s NASA Administrator nomination
  • Left-leaning influencers embrace Bluesky without abandoning X, Pew says
  • NAACP calls on Memphis officials to halt operations at xAI’s ‘dirty data center’
  • Meta plans to automate many of its product risk assessments
  • The ellipse hidden inside Pascal’s triangle

Categories

  • Industry News
  • Programming
  • RSS Fetched Articles
  • Uncategorized

Archives

  • May 2025
  • April 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • October 2024
  • September 2024
  • August 2024
  • July 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023

Tap into the power of Microservices, MVC Architecture, Cloud, Containers, UML, and Scrum methodologies to bolster your project planning, execution, and application development processes.

Solutions

  • IT Consultation
  • Agile Transformation
  • Software Development
  • DevOps & CI/CD

Regions Covered

  • Montreal
  • New York
  • Paris
  • Mauritius
  • Abidjan
  • Dakar

Subscribe to Newsletter

Join our monthly newsletter subscribers to get the latest news and insights.

© Copyright 2023. All Rights Reserved by Soatdev IT Consulting Inc.