Skewed Hit Boxes with CSS Transforms

Tommy Marshall, Former Front-End Developer

Article Category: #Design & Content

Posted on

One of the neatest things about CSS Transforms is that they change the hit area of an element to whatever transformed value we set. So, if we rotate an element, the hit area for that element doesn’t stay a box in the defined X and Y plane; it changes to the transformed shape.

CSS Transformed Hit Box

See the Pen rhAje by Tommy Marshall (@tommymarshall) on CodePen.

With that in mind, when I was handed a design comp with a skewed design element and links with angled edges within it, I realized for great justice it was achievable by skewing an element and applying overflow: hidden to the container.

The markup for this demo is really simple:

 <div class="container">
 <div class="inner">
 <ul>
 <li>
 <a href="#">Something Awesome</a>
 </li>
 <li>
 <a href="#">Something Awesome</a>
 </li>
 <li>
 <a href="#">Something Awesome</a>
 </li>
 </ul>
 </div>
</div>

Based on that markup, we first transform the .container element and skew it 18 degrees on the X-axis, then undo that skew on the .inner container so our links display properly instead of at an angle (Using SASS).

 .container {
 background: #555;
  margin-left: 100px;
  padding: 100px 0;
 position: relative;
 width: 400px;

  /* the important stuff */
  overflow: hidden;
  @include transform(skewX(-18deg));
  
  .inner {
   /* revert the transform */
  @include transform(skewX(18deg));
  }
​}

If you looked at the above example in a Codepen you’ll notice the edges of the skewed element are a bit choppy. You can use the translateZ trick to fix that rendering issue, but that blurs the child elements of the container as well. Instead, I found adding a simple box-shadow to the container works totally fine.

 .container {
  box-shadow: 0 0 1px #000
  ...
}

Next, to fix the alignment of the nested links, I add a bit of left positioning to each <li> using the :nth-child selector. 

 ul {
 list-style: none;
 margin: 0;
 padding: 0;
 width: 100%;
 
 li {
 position: relative;
 
 @for $i from 1 through 3 {
 &:nth-child(#{$i}) {
 left: 18px + ($i * -18);
 }
 }

 a {
 background: #5579c2;
 color: #fff;
 display: block;
 padding: 1em 5em;
 text-decoration: none;
 text-transform: uppercase;
 
 &:hover {
 background: darken(#5579c2, 20%);
 }
 }
 }
}

The bonus of using CSS Transforms and :nth-child is that in browsers without support the fallback looks exactly as you'd expect: an unskewed element without the extra left spacing for each of the links.

You'll notice even with the width: 100% on our links, it doesn't span the full width of the .container. To get around this you can just apply a bit extra to the <ul>, since our .container element is set to overflow: hidden.

 ul {
  width: 120%;
}

Final Result

See the Pen Jxcbu by Tommy Marshall (@tommymarshall) on CodePen.

When you hover over the links you’ll see that the hit areas are exactly as we’d expect them to be (not going beyond the bounds of the container) and thanks to CSS Transforms and overflow: hidden.

Found this helpful or have another application of skewing elements change an elements hit area? Let me know below!

Related Articles