ostinred
ostinred

Reputation: 116

How to create responsive triangle as right border?

I am trying to make a responsive triangle for one border, which will be visible only on hover. I tried the below code but it doesn't work because it uses static border-width. When I have one-string (single line) link it's perfect, but when strings more (more than one line), it's failing.

Example here

Code here:

<ul>
  <li><a href="#">Lorem ipsum</a></li>
  <li><a href="#">Lorem ipsum</a></li>
  <li><a href="#">Lorem ipsum dolor sit amet</a></li>
</ul>  

SCSS here:

ul{
  width:120px;
  li{
    list-style-type: none;
    a{
      position:relative;
      display:block;
      padding:10px;
      color:#00f;
      text-decoration: none;
      &:hover{
        color:#fff;
        background:#00f;
        &:after{
          content:'';
          position: absolute;
          top: 0;
          right: -5px;
          height: 0;
          border-style: solid;
          border-width: 19px 0 19px 11px;
          border-color: #fff #fff #fff #00f;
        }
      }
    }
  }
}

The last link fails. Using an image or picture is not a solution for the problem.

Upvotes: 3

Views: 635

Answers (2)

Harry
Harry

Reputation: 89750

You cannot use the border triangle method to create the triangles here because the height of your element is dynamic. Instead you could use any of the following alternatives:

Inline SVG + Clip Path: Recommended

You can make use of inline SVG and clip-path to produce the bar with a triangle effect. The clip-path is applied only while hovering on the a tag and so the normal state remains unaffected. The browser support for this is much better than the CSS equivalent.

ul {
  width: 120px;
}
li {
  list-style-type: none;
}
a {
  position: relative;
  display: block;
  padding: 10px;
  color: #00f;
  text-decoration: none;
}
a:hover {
  -webkit-clip-path: url(#clip-shape);
  -moz-clip-path: url(#clip-shape);
  clip-path: url(#clip-shape);
  background: crimson;
}
<svg width="0" height="0">
  <defs>
    <clipPath id="clip-shape" clipPathUnits="objectBoundingBox">
      <polygon points="0,0 0.8,0 1,0.5 0.8,1 0,1" />
    </clipPath>
  </defs>
</svg>

<ul>
  <li><a href="#">Lorem ipsum</a>
  </li>
  <li><a href="#">Lorem ipsum</a>
  </li>
  <li><a href="#">Lorem ipsum dolor sit amet</a>
  </li>
</ul>


Transforms:

You can use transform: rotate(45deg) on a pseudo-element to produce the triangle and then position it at the end of the a to produce the shape. The parent has a overflow: hidden setting to cut out the portions of the triangle that is not required to be displayed.

ul {
  width: 120px;
}
li {
  list-style-type: none;
}
a {
  position: relative;
  display: inline-block;
  color: blue;
  text-decoration: none;
  padding: 5px 25px 5px 5px;
  overflow: hidden;
}
a:after {
  content: '';
  position: absolute;
  top: 50%;
  right: 0%;
  height: 100%;
  width: 100%;
  background-color: inherit;
  transform-origin: 100% 0;
  transform: rotate(45deg);
  z-index: -1;
}
a:before {
  position: absolute;
  content: '';
  top: 0px;
  left: 0px;
  height: 100%;
  width: calc(100% - 25px);
  background-color: inherit;
  z-index: -1;
}
a:hover {
  background: crimson;
  background-clip: content-box;
  color: beige;
}
<!-- Library included just to avoid prefixes so that users with older browser can view -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>

<ul>
  <li><a href="#">Lorem ipsum</a>
  </li>
  <li><a href="#">Lorem ipsum</a>
  </li>
  <li><a href="#">Lorem ipsum dolor sit amet</a>
  </li>
</ul>

Alternately, you could use two pseudo-elements with a transform: skew(45deg) applied to them (in opposite directions) to get the triangle shape. Here also the parent has overflow: hidden setting.

ul {
  width: 120px;
}
li {
  list-style-type: none;
  margin-bottom: 10px;
}
a {
  position: relative;
  display: block;
  padding: 10px;
  color: #00f;
  text-decoration: none;
  overflow: hidden;
}
a:before,
a:after {
  position: absolute;
  content: '';
  height: 50%;
  width: 100%;
  left: -15%;
  z-index: -1;
}
a:before {
  top: 0px;
  transform: skew(45deg);
}
a:after {
  bottom: 0px;
  transform: skew(-45deg);
}
a:hover:after,
a:hover:before {
  background: crimson;
}
a:hover{
  color: beige;
}
<!-- Library included just to avoid prefixes so that users with older browser can view -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>

<ul>
  <li><a href="#">Lorem ipsum</a>

  </li>
  <li><a href="#">Lorem ipsum</a>

  </li>
  <li><a href="#">Lorem ipsum dolor sit amet</a>

  </li>
</ul>

These are probably the closest to pure CSS solutions with good browser support. However, they still needs some tweaking of the properties like padding-right (for rotate method) and left (for skew method) etc when the height increases further and hence is not recommended.


CSS Clip Path:

You can make use of a polygonal clip-path to create a bar with a triangle effect on hover. The drawback here is the poor browser support for CSS clip-path.

ul {
  width: 120px;
}
li {
  list-style-type: none;
}
a {
  position: relative;
  display: block;
  padding: 10px;
  color: #00f;
  text-decoration: none;
}
a:hover {
  -webkit-clip-path: polygon(0% 0%, 80% 0%, 100% 50%, 80% 100%, 0% 100%);
  background: crimson;
}
<ul>
  <li><a href="#">Lorem ipsum</a>
  </li>
  <li><a href="#">Lorem ipsum</a>
  </li>
  <li><a href="#">Lorem ipsum dolor sit amet</a>
  </li>
</ul>


Linear Gradients:

You can also use linear-gradient and pseudo-element combination like in the below snippet but gradients are known to produce jagged edges and are not really recommended.

ul {
  width: 120px;
}
li {
  list-style-type: none;
}
a {
  position: relative;
  display: block;
  padding: 10px;
  color: #00f;
  text-decoration: none;
}
a:after {
  content: '';
  display: none;
  position: absolute;
  right: -25px;
  top: 0px;
  width: 50px;
  height: 100%;
  background: linear-gradient(to top left, rgba(0, 0, 0, 0) 50%, crimson 50%), linear-gradient(to top right, crimson 50%, rgba(0, 0, 0, 0) 50%);
  background-size: 50% 50%;
  background-repeat: no-repeat;
  background-position: 100% 100%, 100% 0%;
  z-index: -1;
}
a:hover {
  background: crimson;
}
a:hover:after {
  display: block;
}
<!-- Library included just to avoid prefixes so that users with older browser can view -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>

<ul>
  <li><a href="#">Lorem ipsum</a>
  </li>
  <li><a href="#">Lorem ipsum</a>
  </li>
  <li><a href="#">Lorem ipsum dolor sit amet</a>
  </li>
</ul>

Upvotes: 7

Dima
Dima

Reputation: 287

You have border with static width:

border-width: 19px 0 19px 11px;

but the height of element changes according to number of rows or text size, so if you update to

border-width: 30px 0 30px 11px;

it will solve the problem for 2 lines text but will not be good for one line text. You will have to check with JS heigt of element and the according to it update the :

border-width: 'dinamic result from JS'px 0 'dinamic result from JS'px  11px;

Upvotes: 0

Related Questions