constructing a 3d pyramid with css - css

Here is a jsbin of what I have so far and below is an image of what I am trying to construct:
Here is the html, I have only 2 sides of the pyramid added so far:
<div class="pyramid-container">
<div id="pyramid">
<div class="base"></div>
<div></div>
<div></div>
</div>
</div>
And here is the css:
.pyramid-container {
perspective: 800px;
}
#pyramid {
position: relative;
transform-style: preserve-3d;
transform-origin: 116px 200px 116px;
padding-left: 100px;
margin-top: 50px
}
#pyramid div:not(.base) {
position: absolute;
width: 0;
height: 0;
border-left: 100px solid transparent; /* left arrow slant */
border-right: 100px solid transparent; /* right arrow slant */
border-bottom: 100px solid; /* bottom, add background color here */
font-size: 12px;
line-height: 0;
opacity: .5;
}
.base {
position: absolute;
width: 200px;
height: 200px;
background-color: #ff0000;
transform: rotateX(80deg) translate3d(0px, 10px, 0px);
}
#pyramid div:nth-child(2) {
border-bottom-color: blue;//#e04545;
transform: rotateX(-30deg) rotateY(0deg) rotateZ(0deg) translate3d(0px, 0px, 100px);
}
#pyramid div:nth-child(3) {
border-bottom-color: yellow;//#ccaf5a;
transform: rotateX(30deg) rotateY(0deg) rotateZ(0deg) translate3d(0px, 25px, 0px);
}
I am using css3 to create 1 base rectangular div and 4 triangular divs.
I would like some help with the maths involved as well as how to position the elements.
I would like all 4 of the triangle sides to meet at an apex and have the four triangle bottom sides positioned on the rectangular div.
I am struggling with how to get the points of the different triangles to meet in at an apex.
Can anyone help me out with the maths or what logic to use to achieve this.
I'm not looking for code but more the logic or maths I should use.

So first, a couple fundamental points on CSS transforms
the order of transforms is important
the perspective will change with the size of container (#pyramid for you)
the origin of the transform is not inherited, and if not set with transform-origin, it is by default the center of the object (borders et al. included, afaict)
x is the horizontal axis, y the vertical one and z the one orthogonal to the screen's surface (when you don't have perspective)
Then on pyramids:
Let us look at the Base angle (let's call it α). Expressing the triangle sizes based on the angle α, you get:
width = Base
apothem (height of triangle) = (1/2 Base) / cos(α)
height of the pyramid = (1/2 Base) * tan(α)
In the pyramid of your attempt, Base = 200px thus 1/2 Base = 100px and Apothem = triangle height = 100px. This forces cos(α) = 1 thus α = 0° -- you will have a flat pyramid.
If you want α = 60°, you want apothem = 200px, and you'll get pyramid height = 173.2
If you want α = 45°, you want apothem = 141px, and you'll get pyramid height = 100
etc.
The main secret now you know the parameters, is to reason at each step, about where the axis are pointed, and at which point of the object you are applying the transforms. Paper & pencil or trial & error, whatever works is good.
So here's how I would do to place triangles:
put css-transform at the middle of the base of all triangles
rotate by a multiple of 90° around axis Y to orient each triangle for a different face of the pyramid (respectively 0, 90, 180, 270)
translate along Z by 1/2 Base to get the right position
rotate along X axis by the desired angle (90°-α) to make them meet at apex.
Thanks to the first rotation, the two identical last steps will do what you want on all triangles.
For the base, it's easier, you can keep the default css-transform
rotate by 90° along X (will make it "flat" on the plane perpendicular to the screen)
shift it along Z by (Apothem - 1/2 Base) to align with the base of the triangles
Below is what it would look like for α = 45°, feel free to not open the snippet if you don't want spoilers.
#pyramid {
perspective: 400px;
padding: 50px 0 0 200px;
}
#pyramid div:not(.base) {
position: absolute;
border-left: 100px solid transparent; /* 1/2 Base */
border-right: 100px solid transparent; /* 1/2 Base */
border-bottom: 141px solid; /* Apothem */
transform-origin: 100px 141px 0; /* bottom of trangle (1/2 Base, Apothem) */
opacity: .5;
}
.base {
position: absolute;
width: 200px;
height: 200px;
background-color: #2f2f2f;
/* transform by default from middle (100px, 100px)
move by Apothem - 1/2 Base = 41px */
transform: rotateX(90deg) translate3d(0px, 0px, -41px);
opacity: .5;
}
#pyramid div:nth-child(2) {
border-bottom-color: #e04545;
transform: rotateY(0deg) translate3d(0px, 0px, 100px) rotateX(45deg);
}
#pyramid div:nth-child(3) {
border-bottom-color: #ccaf5a;
transform: rotateY(90deg) translate3d(0px, 0px, 100px) rotateX(45deg);
}
#pyramid div:nth-child(4) {
transform: rotateY(180deg) translate3d(0px, 0px, 100px) rotateX(45deg);
}
#pyramid div:nth-child(5) {
border-bottom-color: #4ccfc7;
transform: rotateY(270deg) translate3d(0px, 0px, 100px) rotateX(45deg);
}
<!doctype html>
<html>
<head><title>Pyramid</title></head>
<body>
<div class="pyramid-container">
<div id="pyramid">
<div class="base"></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
</body>
</html>

Related

CSS: Changing width/height after rotation in one direction only

My end goal is a draggable, resizable, Scalable, and rotatable element, just like the example on: https://daybrush.com/moveable/ only by using css width,height, and transform: rotate, translate.
Say I have a div with following css:
.rect {
background-color: red;
width: 200px;
height: 100px;
top:100px;
left:100px;
position: absolute;
transform: rotate(0deg);
}
<div class="rect"></div>
If I want to resize the div horizontally to the left, I just change the width by x pixels. If I want to change it to the right I just change the width by x pixels, and translate(-xpx, 0).
But what if I change the angle? From trying a lot of stuff, I found some of the x and y values for translate to the respective angle, however I feel like there is a more straight forward way than just guessing. E.g: For 90deg, if I want to resize to the left by x px I do translate(-x0.5px, x0.5px).
More: what if I want to change both the width & height at the same time?
P.S.: I would rather avoid using libraries, transform: scale or svg
P.P.S:Example to further demonstrate the problem, just changing the width:
.rect {
background-color: red;
width: 200px;
height: 100px;
top:100px;
left:100px;
position: absolute;
transform: rotate(45deg);
animation: expand 5s infinite
}
#keyframes expand {
from {width: 200px;}
to {width: 2000px;}
}
<div class="rect"></div>
Fixed, stretching the left side of the original rectagle (now up since rotated 90deg):
.rect {
background-color: red;
width: 200px;
height: 100px;
top:100px;
left:100px;
position: absolute;
transform: rotate(90deg);
animation: expand 3s infinite
}
#keyframes expand {
from {
width: 200px;
}
to {
width: 800px;
transform: rotate(90deg) translate(-300px, 300px);
}
}
<div class="rect"></div>
You can apply several transformations to the same object and they will be composed in the order that you specify. Move then rotate, is different than rotate then move.
.rect {
background-color: red;
width: 200px;
height: 100px;
top:100px;
left:100px;
position: absolute;
}
.t1 {
background-color: #40d04080;
/* green shaded rectangle: rotate after translation */
transform: translate(2cm, 0) rotate(30deg);
}
.t2 {
background-color: #f0404080;
transform: rotate(30deg);
}
.t3 {
background-color: #4040f080;
/* blue shaded rectangle: translate after rotation */
transform: rotate(30deg) translate(2cm, 0) ;
}
<div class="rect t1"></div>
<div class="rect t2"></div>
<div class="rect t3"></div>

How to create quadrilateral (rhombus shape) with specific degree?

How can I create a quadrilateral with css, when I know which degree each corner has.
I already tried to recreate a quadrilateral I have with transform and skew.
However, this does not work really well.
This is what I try to archive.
The exact requirements are:
A div with this as background in one color. On the image are just construction lines. It should be a solid quadrilateral with these angles.
This was my first idea:
transform: rotate(-45deg) skew(27.5deg, 62.5deg)
transform-origin: top center;
I would consider multiple background to achieve this where I simply need to find the width/height of the element. Based on your illustration we have this:
From this we can have the following formula:
tan(alpha) = W/H
and
tan(beta/2) = H/W
We only need to use one of them and you will notice that there isn't one solution which is logical as you simply need to keep a ratio between H and W and the width of our element will simply be 2*W and its height 2*H.
Since H/W is also the same as 2*H/2*W we can simply consider that width = tan(alpha)*height
.box {
height:var(--h);
width:calc(1.92098213 * var(--h)); /* tan(62.5)xH */
background:
linear-gradient(to bottom right,transparent 49%,red 50%) top left,
linear-gradient(to top right,transparent 49%,red 50%) bottom left,
linear-gradient(to bottom left ,transparent 49%,red 50%) top right,
linear-gradient(to top left ,transparent 49%,red 50%) bottom right;
background-size:50% 50%;
background-repeat:no-repeat;
}
<div class="box" style="--h:50px;"></div>
<div class="box" style="--h:100px;"></div>
<div class="box" style="--h:200px;"></div>
You can adjust the gradient if you want only borders:
.box {
height:var(--h);
width:calc(1.92098213 * var(--h)); /* tan(62.5)xH */
background:
linear-gradient(to bottom right,transparent 49%,red 50%,transparent calc(50% + 2px)) top left,
linear-gradient(to top right,transparent 49%,red 50%,transparent calc(50% + 2px)) bottom left,
linear-gradient(to bottom left ,transparent 49%,red 50%,transparent calc(50% + 2px)) top right,
linear-gradient(to top left ,transparent 49%,red 50%,transparent calc(50% + 2px)) bottom right;
background-size:50% 50%;
background-repeat:no-repeat;
}
<div class="box" style="--h:50px;"></div>
<div class="box" style="--h:100px;"></div>
<div class="box" style="--h:200px;"></div>
Using transform the idea is to rely on rotateX() in order to visually decrease the height to keep the formula defined previously. So we start by having Width=height (a square) then we rotate like below:
This is a view from the side. The green is our rotated element and the red the initial one. It's clear that we will see the height H1 after performing the rotation and we have this formula:
cos(angle) = H1/H
And we aleardy have tan(alpha)=W/H1 so we will have
cos(angle) = W/(H*tan(alpha))
and H=W since we defined a square initially so we will have cos(angle) = 1/tan(alpha) --> angle = cos-1(1/tan(alpha))
.box {
width:150px;
height:150px;
background:red;
margin:50px;
transform:rotateX(58.63017731deg) rotate(45deg); /* cos-1(0.52056)*/
}
<div class="box">
</div>
We can also apply the same logic using rotateY() to update the width in the situation where you will have beta bigger than 90deg and alpha smaller than 45deg. In this case we will have W < H and the rotateX() won't help us.
The math can easily confirm this. when alpha is smaller than 45deg tan(alpha) will be smaller than 1 thus 1/tan(alpha) will bigger than 1 and cos is only defined between [-1 1] so there is no angle we can use with rotateX()
Here is an animation to illustrate:
.box {
width:100px;
height:100px;
display:inline-block;
background:red;
margin:50px;
animation:change 5s linear infinite alternate;
}
.alt {
animation:change-alt 5s linear infinite alternate;
}
#keyframes change {
from{transform:rotateX(0) rotate(45deg)}
to{ transform:rotateX(90deg) rotate(45deg)}
}
#keyframes change-alt {
from{transform:rotateY(0) rotate(45deg)}
to{ transform:rotateY(90deg) rotate(45deg)}
}
<div class="box">
</div>
<div class="box alt">
</div>
In various way you can do it. Since you're trying to use degree value so here I can give you an example: first of you can take four lines for your rectangle and rotate them as you want with degree value. Here is what I mean:
<div class="top_line"></div>
<div class="right_line"></div>
<div class="bottom_line"></div>
<div class="left_line"></div>
Css
.top_line { height: 170px; border-right: 1px solid yellow; transform: rotate(50deg);
position: absolute; top: 140px; left: 400px; transform-origin: 0% 130%; }
.right_line {height: 140px; border-right: 1px solid red; transform: rotate(130deg);
position: absolute; top: 140px; left: 500px; transform-origin: 0% 50%; }
.bottom_line { height: 140px; border-right: 1px solid green; transform: rotate(130deg);
position: absolute; top: 140px; left: 400px; transform-origin: -1800% 80%; }
.left_line { height: 140px; border-right: 1px solid blue; transform: rotate(50deg);
position: absolute; top: 140px; left: 400px; }
Here is the live preview

Rotate 45-degree element around vertical axis

I've got a series of elements, as shown in the image below:
They are rotated 45 degrees to one side (the content inside -45 degrees, to remain upright).
Now I'd like to rotate each element around a vertical axis, going through the element's center. RotateY doesn't work, as it is at a 45-degree angle.
How would you go about doing this?
The trick is to set this rotation before the 45 degrees rotation:
Notice also that to make the rotation behave really as expect, you need to set it to 0 in the base state
.container {
width: 200px;
height: 200px;
margin: 100px;
border: solid 1px;
transform: rotateY(0deg) rotate(45deg); /* needs Y at 0 deg to behave properly*/
transition: transform 2s;
}
.container:hover {
transform: rotateY(180deg) rotate(45deg); /* notice the order */
}
.inner {
margin: 50px;
transform: rotate(-45deg);
}
<div class="container">
<div class="inner">INNER</div>
</div>
This is how I interpret the question. I'm not very happy with the demo since it needs a lot of structure.
But maybe you can verify the behavior?
Basically I use a wrapper to rotate on the y-axis.
It is key to set the transform origin to the center.
The additional wrapper is used to prevent a flickering on mouse hover.
https://jsfiddle.net/nm59mqky/1/
.tile {
transform: rotateY(0);
transform-origin: center center;
}
.wrapper:hover .tile {
transform: rotateY(180deg);
}
I dont know exactly what your code looks like, but for a simple spinning tile (div) i would try something like this:
#keyframes rotate-vertical {
0% {
transform: rotate3d(0, 1, 0, 0deg);
}
100% {
transform: rotate3d(0, 1, 0, 360deg);
}
}
body {
padding: 20px;
}
.tile {
width: 65px;
height: 65px;
background-color: red;
text-align: center;
transform: rotate3d(0, 0, 1, 45deg);
display: inline-block;
}
.turndiv {
width: 65px;
}
.turndiv:hover {
animation: rotate-vertical 1.1s ease-out;
}
<div class="turndiv">
<div class="tile">
</div>
</div>
You could just do it with transform: rotate3d(); and without a parent div, but to keep it easy i did it like this.

How to make a tetrahedron?

I am trying to make a CSS tetrahedron, so I have tackled the problem by doing some CSS3 triangles and activating the 3D transformations with the perspective property.
But I have some issues to wrap my mind over all the transformations, here is some of my code:
.navbar-brand-logo {
width: 64px;
height: 64px;
transform-style: preserve-3d;
perspective: 600px;
position: relative;
}
.face {
width: 0;
height: 0;
position: absolute;
border-style: solid;
border-width: 64px 32px 0 32px;
transform-origin: 0 0;
border-color: transparent transparent transparent rgba(50, 50, 50, 0.6);
}
.logo-base-left {
transform: rotateX(180deg) translateY(-64px);
}
.logo-base-right {
transform: rotateY(180deg) rotateX(180deg) translateY(-64px);
}
.logo-up {
border-color: transparent transparent transparent rgba(50, 50, 50, 0.8);
transform: rotateY(180deg) scaleY(0.5) translateY(-64px);
}
.logo-down-up {
border-color: transparent transparent transparent rgba(50, 50, 50, 0.9);
border-width: 64px 0 0px 4px;
transform: scaleX(128px) translateZ(0px);
}
<section class="navbar-brand-logo">
<figure class="face logo-base-left"></figure>
<figure class="face logo-base-right"></figure>
<figure class="face logo-up"></figure>
<figure class="face logo-down-up"></figure>
</section>
I am having issues to imagine how can I make the two other faces (the left up one and the right one).
Here is a CodePen which illustrate the current attempt:
Furthermore, is it a good idea to use a CSS3 tetrahedron as a logo?
Would it be better if it was an SVG?
WebGL / Canvas is a no-no due to browser support.
Here are a few steps describing an approach to make a responsive tetrahedron: demo - responsive tetrahedron.
link to animation
Step 1 the faces:
A tetrahedron has 4 triangular faces. Each face is an equilateral triangle.
In the following example, I used the clip-path property to make the 4 equilateral triangles:
.tetra{
position:relative;
width:20%;
padding-bottom:17.32%; /* height of equilateral triangle = sin60° * width */
margin:0 auto;
}
.tetra div{
position:absolute;
top:0;left:0;
width:100%; height:100%;
-webkit-clip-path:polygon(50% 0, 100% 100%, 0% 100%);
clip-path:polygon(50% 0, 100% 100%, 0% 100%);
background:teal;
}
.tetra .face2{
transform-origin:0% 100%;
transform:rotate(-60deg);
background:gold;
}
.tetra .face3{
transform-origin:100% 100%;
transform:rotate(60deg);
background:darkorange;
}
.tetra .face4{
transform-origin:50% 100%;
transform:rotate(180deg);
background:pink;
}
<div class="tetra">
<div class="face1"></div>
<div class="face2"></div>
<div class="face3"></div>
<div class="face4"></div>
</div>
Step 2 make it 3d
For this, we rotate each face separately in the 3d environment with perspective and transform-style:
body{
perspective:9000px;
}
.tetra{
position:relative;
width:20%;
padding-bottom:17.32%; /* height of equilateral triangle = sin60° * width */
margin:0 auto;
transform-style:preserve-3d;
}
.tetra div{
position:absolute;
top:0;left:0;
width:100%; height:100%;
-webkit-clip-path:polygon(50% 0, 100% 100%, 0% 100%);
clip-path:polygon(50% 0, 100% 100%, 0% 100%);
background:teal;
transform-style:preserve-3d;
}
/* Rotation of –109.5° is angle(C, M[AB], D), per http://www.f-lohmueller.de/pov_tut/geo/geom_200e.htm, 180° – atan(2 * sqrt(2)) ≈ 109.5° */
.tetra .face2{
transform-origin:0% 100%;
transform:rotate(-60deg) rotatex(-109.5deg);
background:gold;
}
.tetra .face3{
transform-origin:100% 100%;
transform:rotate(60deg) rotatex(-109.5deg);
background:darkorange;
}
.tetra .face4{
transform-origin:50% 100%;
transform:rotate(180deg) rotatex(-109.5deg);
background:pink;
}
<div class="tetra">
<div class="face1"></div>
<div class="face2"></div>
<div class="face3"></div>
<div class="face4"></div>
</div>
At this point, you have a tetrahedron but you can only see 3 face so to see the whole 3d shape:
Step 3 make it rotate!
Top see the whole tetrahedron, you need to rotate it with a transition or keyframe animation :
body{
perspective:9000px;
padding-top:10%;
}
.tetra{
position:relative;
width:20%;
padding-bottom:17.32%; /* height of equilateral triangle = sin60° * width */
margin:0 auto;
transform-style:preserve-3d;
transform:rotatex(90deg) rotateY(0deg) rotatez(0deg);
animation: rotate 10s linear infinite;
}
.tetra div{
position:absolute;
top:0;left:0;
width:100%; height:100%;
-webkit-clip-path:polygon(50% 0, 100% 100%, 0% 100%);
clip-path:polygon(50% 0, 100% 100%, 0% 100%);
background:teal;
transform-style:preserve-3d;
}
/* Rotation of –109.5° is angle(C, M[AB], D), per http://www.f-lohmueller.de/pov_tut/geo/geom_200e.htm, 180° – atan(2 * sqrt(2)) ≈ 109.5° */
.tetra .face2{
transform-origin:0% 100%;
transform:rotate(-60deg) rotatex(-109.5deg);
background:gold;
}
.tetra .face3{
transform-origin:100% 100%;
transform:rotate(60deg) rotatex(-109.5deg);
background:darkorange;
}
.tetra .face4{
transform-origin:50% 100%;
transform:rotate(180deg) rotatex(-109.5deg);
background:pink;
}
#keyframes rotate{
50%{transform:rotatex(100deg) rotateY(10deg) rotatez(180deg);}
100%{transform:rotatex(90deg) rotateY(0deg) rotatez(360deg);}
}
<div class="tetra">
<div class="face1"></div>
<div class="face2"></div>
<div class="face3"></div>
<div class="face4"></div>
</div>
Note that this uses properties that aren't supported by all browsers, especialy clip-path that is only supported by chrome. This property is used to make the equilateral triangles and you can use other approaches (see here).
For browser support and vendor prefixes, also see canIuse for:
clip-path
3d transforms
CSS animations
If you want to animate the logo (once, on hover, doesn't matter), then going with 3D CSS is probably a good idea.
With SVG, you would get better browser support, faster rendering, and easier control over the shape, so if you're not animating the logo I'd suggest going with SVG.
For building the shape of a tetrahedron in 3D, check out Ana Tudor's codepen, this is but one of many tetrahedron examples she's made: http://codepen.io/thebabydino/pen/vFrHx – you can play with the animation rot in this pen to get an idea of how to rotate / animate it.
In organic molecules tetrahedra are often related helices, f.e.:
Boerdijk–Coxeter helix. So it would be nice to write code to show this helix.
Here is javascript for the helix:
var s set equal to the sides of the square canvas
var a=s/6, b=s/9;
c.beginPath();
for (var i=0; i<2*Math.PI; i+=0.01){
x=s/4+a*Math.cos(i)-b*Math.sin(i);
y=3*s/4+a*Math.cos(i)+b*Math.sin(i);
if(i==0) {c.moveTo(x,y);} else {c.lineTo(x,y);}
}
c.lineWidth=1;
c.strokeStyle="#0f6";
c.stroke();

CSS3 full-width rotate

I would like to rotate a full-width div (from side to side without free space) in which will be some content.
I want the corners on the right side to touch the right side of the page and the corners on the left side to touch the left side of the page. I don't think width:200% and overflow-x:hidden is the best solution.
How can I achieve this?
Here is an example. Note that the corners don't touch the sides of the page.
.rotated {
width: 100%;
height: 100px;
background-color: red;
-moz-transform: rotate(-6deg);
-webkit-transform: rotate(-6deg);
-o-transform: rotate(-6deg);
-ms-transform: rotate(-6deg);
transform: rotate(-6deg);
}
<div class="rotated"></div>
You might find the CSS transform skewY() helpful. It will skew the element without rotating the corners.
I've also set the transform-origin to the top right so that the element doesn't skew off the top of the page.
html,body {
margin: 0;
}
.rotated {
height: 100px;
background-color: red;
-webkit-transform-origin: top right;
-ms-transform-origin: top right;
transform-origin: top right;
-webkit-transform: skewY(-6deg);
-ms-transform: skewY(-6deg);
transform: skewY(-6deg);
}
<div class="rotated"></div>
For further reference, see the Skewing and Translating example at MDN.
You could increase the horizontal proportion with scale, but the content will be scaled as well (as long as you know it you can compensate)
.rotated {
width: 100%;
height: 100px;
background-color: red;
transform: scale(1.2 , 1) rotate(-6deg);
}
<div class="rotated"></div>

Resources