Search code examples
htmlcsstransformscaleborder-radius

Scaling an element while keeping its border radius constant


I am trying to scale an element on the Y-axis. The element has a border-radius of 20px. I am following this tutorial on how to scale an element while keeping its border-radius constant. The TLDR is that for the border to remain constant after scaling it, the border-radius has to be scaled as well. This works if you scale it without animating the transition, as shown in the article provided above. However, when animating it, the border-radius looks really bad during the transition.

Below is an example of this. The right side element in the example below is scaled by 2 vertically, and the border-radius is scaled as well to account for this. You can see the border-radius at the top does not remain constant during the animation and it looks quite jarring to my eye. I was wondering if there was a way to make the border-radius remain unchanged throughout the transition.

https://codepen.io/KR1S71AN/pen/qBQbgYR

Here is an example of this live too.

html { 
    height: 1000px;
}

.single-item {
    position: relative;
    margin: 0 auto;
    width: 40vw;
    height: 40vh;
  display: inline-block;
}

.single-item::after {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    border-radius: 8vmin;
    background: linear-gradient(#000, #f90);
    content: '';
    transition: transform 0.3s;
    transform-origin: top;
}

.hover:hover::after {
  border-radius: 8vmin/ 4vmin;
    transform: scale(1, 2)
}
<section>
    <div class='single-item'></div>
    <div class='single-item hover'></div>
</section>


Solution

  • Instead of transform, which actually stretches your element, just change the height.

    html {
      height: 1000px;
    }
    
    .single-item {
      position: relative;
      margin: 0 auto;
      width: 40vw;
      height: 40vh;
      display: inline-block;
    }
    
    .single-item::after {
      position: absolute;
      border-radius: 8vmin;
      background: linear-gradient(#000, #f90);
      content: '';
      width: 100%;
      height: 100%;
      transition: height 0.3s;
    }
    
    .hover:hover::after {
      height: 200%;
    }
    <section>
      <div class='single-item'></div>
      <div class='single-item hover'></div>
    </section>

    Edit

    If you need to use transforms, the "more performant" (and non-JS) version would be to animate the border-radius as well. It's a bit wonky, but at least it's smooth.

    html { 
        height: 1000px;
    }
    
    .single-item {
        position: relative;
        margin: 0 auto;
        width: 40vw;
        height: 40vh;
      display: inline-block;
    }
    
    .single-item::after {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        border-radius: 8vmin;
        background: linear-gradient(#000, #f90);
        content: '';
        transition: transform 0.3s, border-radius 0.3s;
        transform-origin: top;
    }
    
    .hover:hover::after {
      border-radius: 8vmin / 4vmin;
      transform: scale(1, 2)
    }
    <section>
        <div class='single-item'></div>
        <div class='single-item hover'></div>
    </section>