R. Bright R. Bright - 7 months ago 9
HTML Question

Arrow pseudo-element with transform rotate doesn't stay centered when re-sizing window

My problem is that I cannot place a triangle pointer right in the middle. Well, I can set pointer for some size of window, but when I shrink or extend window it places in the wrong place again. What am I missing ?



body {
background: #333333;
}
.container {
width: 98%;
height: 80px;
line-height: 80px;
position: relative;
top: 20px;
min-width: 250px;
margin-top: 50px;
}
.container-decor {
border: 4px solid #C2E1F5;
color: #fff;
font-family: times;
font-size: 1.1em;
background: #88B7D5;
text-align: justify;
}
.container:before {
top: -33px;
left: 48%;
transform: rotate(45deg);
position: absolute;
border: solid #C2E1F5;
border-width: 4px 0 0 4px;
background: #88B7D5;
content: '';
width: 56px;
height: 56px;
}

<div class="container container-decor">great distance</div>




Answer

You have your arrow centered with left:48%;. This positions the arrow near the center of the container based on the arrow element's left edge.

In other words, assume you used left:50% (which is the correct way to go), this doesn't center the arrow element in the container. It actually centers the left edge of the arrow element in the container.

In the image below, using the same width of your container (98%), a marker is placed using text-align:center. This marker is perfectly centered on the page.

For comparison, see your arrow centered with left:50%.

enter image description here

The whole element is center-right. This positioning will be more apparent as the window gets smaller.

This is why it is common to see centered, absolutely positioned items use the transform property:

.triangle {
   position: absolute;
   left: 50%;
   transform: translate(-50%,0);
}

The transform rule tells the triangle to move itself back by 50% of its width, so it's perfectly centered on the line. Now it emulates text-align:center.

enter image description here

In translate(-50%,0), the first value targets the x-axis (horizontal), the other applies to the y-axis. An equivalent rule would be transform:translateX(-50%).

However, you already have transform:rotate(45deg); in the declaration block. Adding a second transform means that one would be ignored (CSS would use the last one).

So, as a solution, try this:

.container::before {
    left: 50%;
    transform: translate(-50%,0) rotate(45deg);
}

By chaining them together in one rule, multiple transform methods can be applied.

Just note that order matters. If translate and rotate were reversed, the triangle would first rotate 45 degrees and then shift -50% along the rotated axis. This would break the layout. So make sure that translate goes first. (Thanks @Oriol for pointing this out in the comments.)

Here's the full code:

body {
    background: #333333;
}

.container {
    width: 98%;
    height: 80px;
    line-height: 80px;
    position: relative;
    top: 20px;
    min-width: 250px;
    margin-top: 50px;
}

.container-decor {
    border: 4px solid #C2E1F5;
    color: #fff;
    font-family: times;
    font-size: 1.1em;
    background: #88B7D5;
    text-align: justify;
}

.container::before {
    top: -33px;
    left: 50%;
    transform: translate(-50%,0) rotate(45deg);
    position: absolute;
    border: solid #C2E1F5;
    border-width: 4px 0 0 4px;
    background: #88B7D5;
    content: '';
    width: 56px;
    height: 56px;
}
<div class="container container-decor">great distance</div>

jsFiddle