Search code examples
csscss-transitionscss-shapescss-transforms

Button with right arrow - transition


I've made css button with right border in arrow shape, with the help of css transforms and pseudo elements. Now I'm trying to add transition to background on hover but realized there is a problem, in the place where button is overlapped by pseudo element, during transition color is different. See it below, try to hover the button:

body {
  background: gray;
background-image: linear-gradient(30deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),
linear-gradient(150deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),
linear-gradient(30deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),
linear-gradient(150deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),
linear-gradient(60deg, #99a 25%, transparent 25.5%, transparent 75%, #99a 75%, #99a), 
linear-gradient(60deg, #99a 25%, transparent 25.5%, transparent 75%, #99a 75%, #99a);
background-size:80px 140px;
background-position: 0 0, 0 0, 40px 70px, 40px 70px, 0 0, 40px 70px;
  font-family: sans-serif;
}
.more-skewed {
  display: inline-block;
  position: relative;
  color: white;
  text-decoration: none;
  border: solid 2px white;
  border-right: none;
  box-sizing: border-box;
  padding: 15px 40px;
  letter-spacing: 3px;
  transition: all 1s ease-out;
}

.more-skewed:before {
  content: '';
  position: absolute;
  left: 100%;
  margin-left: -10px;
  top: -2px;
  bottom: 50%;
  border-right: solid 2px white;
  transform: skewX(25deg);
  width: 15px;
  background: inherit;
}

.more-skewed:after {
  content: '';
  position: absolute;
  left: 100%;
  margin-left: -10px;
  bottom: -2px;
  top: 50%;
  border-right: solid 2px white;
  transform: skewX(-25deg);
  width: 15px;
  background: inherit;
}

.more-skewed:hover {
  color: black;
	background: white;
}
<a class="more-skewed" href="#">MORE</a>

Is it possible to resolve this? JSfiddle - https://jsfiddle.net/k9jhuc34/
(Button is initially transparent, and it is over image background.)


Solution

  • As stated in my comment, this is kind of expected because roughly speaking what happens is that a semi-transparent white layer is being placed on top of another such layer in the areas where there is an overlap and so, that area quickly appears whiter than the rest.

    Using Skew Transform:

    One solution would be like in the below snippet but it makes the hover/hit area a rectangle. What I've done here is to make the pseudo-elements create the entire shape (other than the left border) and a overflow-hidden on the parent prevents the unwanted skewed portions on left side from showing up.

    body {
      background: gray;
      font-family: sans-serif;
    }
    .more-skewed {
      display: inline-block;
      position: relative;
      color: white;
      text-decoration: none;
      border-left: solid 2px white;
      box-sizing: border-box;
      padding: 15px 40px;
      letter-spacing: 3px;
      transition: all 1s ease-out;
      overflow: hidden;
    }
    .more-skewed:before {
      content: '';
      position: absolute;
      left: -2px;
      top: 0px;
      bottom: 50%;
      border-right: solid 2px white;
      border-top: solid 2px white;
      transform: skewX(25deg);
      transform-origin: right bottom;
      width: 100%;
      transition: all 1s ease-out;
      z-index: -1;
    }
    .more-skewed:after {
      content: '';
      position: absolute;
      left: -2px;
      bottom: 0px;
      top: 50%;
      border-right: solid 2px white;
      border-bottom: solid 2px white;
      transform: skewX(-25deg);
      transform-origin: right top;
      width: 100%;
      transition: all 1s ease-out;
      z-index: -1;
    }
    .more-skewed:hover {
      color: black;
    }
    .more-skewed:hover:before,
    .more-skewed:hover:after {
      background: white;
    }
    <a class="more-skewed" href="#">MORE</a>


    Using Rotate with Perspective:

    Another solution could be to use rotate transforms with a bit of perspective added to it. By setting the appropriate value for transform-origin we can make it produce a trapezoid which can be placed at the appropriate positions to produce an arrow. Here, the hover/hit area remains within the shape but this approach is useful only if the text is small and static. If the text becomes longer, the slope on the right side of the shape becomes more gradual instead of being steep (of-course, we can change the transforms to make it look better, but it is not very useful if we have to keep changing it often).

    .more-skewed {
      display: inline-block;
      position: relative;
      color: white;
      text-decoration: none;
      border: solid 2px white;
      border-right: none;
      box-sizing: border-box;
      padding: 16px 40px;
      letter-spacing: 3px;
      margin: 10px;
    }
    .more-skewed:before,
    .more-skewed:after {
      position: absolute;
      content: '';
      height: 50%;
      width: 100%;
      left: 0;
      border-right: 3px solid white;
      transition: all 1s ease-out;
    }
    .more-skewed:before {
      top: -2px;
      border-top: 2px solid white;
      transform-origin: left top;
      transform: perspective(100px) rotateX(30deg);
    }
    .more-skewed:after {
      bottom: -2px;
      border-bottom: 2px solid white;
      transform-origin: left bottom;
      transform: perspective(100px) rotateX(-30deg);
    }
    .more-skewed:hover {
      color: black;
    }
    .more-skewed:hover:before,
    .more-skewed:hover:after {
      background: white;
    }
    .more-skewed span {
      position: relative;
      z-index: 1;
    }
    body {
      background: gray;
      font-family: sans-serif;
    }
    <a class="more-skewed" href="#">
      <span>MORE</span>
    </a>
    <br/>
    <a class="more-skewed" href="#">
      <span>MORE LONGER TEXT</span>
    </a>

    (Feedback is that the above snippet breaks in IE. I will address that when I find time.)


    Here is another version that I came up with as I was playing around. It is a bit like a background slide-fill effect which keeps the hover/hit area within the shape but due to the way gradients are rendered by browsers, it isn't very clean. Thought of leaving it here just in-case you find it useful.

    body {
      background: gray;
      font-family: sans-serif;
    }
    .more-skewed {
      display: inline-block;
      position: relative;
      color: white;
      text-decoration: none;
      border: solid 2px white;
      border-right: none;
      box-sizing: border-box;
      padding: 15px 40px;
      letter-spacing: 3px;
      transition: all 1s linear;
      background: linear-gradient(245deg, transparent 9px, white 10px), linear-gradient(295deg, transparent 9px, white 10px);
      background-size: 0% 50%;
      background-position: top right, bottom right;
      background-repeat: no-repeat;
    }
    .more-skewed:before {
      content: '';
      position: absolute;
      left: 100%;
      margin-left: -7px;
      top: -2px;
      bottom: 50%;
      transform: skewX(25deg);
      width: 15px;
      background: linear-gradient(white, white);
      background-size: 3px 100%;
      background-repeat: no-repeat;
      background-position: top right;
      transition: all 1s 1s linear;
    }
    .more-skewed:after {
      content: '';
      position: absolute;
      left: 100%;
      margin-left: -7px;
      bottom: -2px;
      top: 50%;
      transform: skewX(-25deg);
      width: 15px;
      background: linear-gradient(white, white);
      background-size: 3px 100%;
      background-repeat: no-repeat;
      background-position: top right;
      transition: all 1s 1s linear;
    }
    .more-skewed:hover {
      color: black;
      background-size: 100% 50%;
      transition: all 1s 1s linear;
    }
    .more-skewed:hover:before,
    .more-skewed:hover:after {
      color: black;
      background-size: 100% 100%;
      transition: all 1s linear;
    }
    <a class="more-skewed" href="#">MORE</a>