Search code examples
htmlcssreactjssvg

Prevent SVG border radius from scaling


I'm working on creating a react button component with the following animation:

enter image description here

I found some helpful code online and started off by playing around with plain html/css just to get a feel for it and resulted with the below code:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}


body {
  font-family: sans-serif;
  background-color: black;
  height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.button {
  position: relative;
  width: 200px;
  height: 40px;
  background-color: #083a65;
  border: 0;
  color: white;
  font-size: 18px;
  border-radius: 10px;
  cursor: pointer;
  max-width: 100%;
}

button svg {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
}

button rect {
  animation: button-border 2s linear both;
}

button path {
  animation: button-border 1s linear both;
}

@keyframes button-border {
  to {
    stroke-dashoffset: 0;
  }
}
<button
  type="button"
  class="button"
>
  My button
  <svg
    viewBox="0 0 200 40"
    fill="none"
    preserveAspectRatio="none"
  >
  
    <path
    d="M100,1
       h90
       q9.5,0 9.5,9.5
       v19
       q0,9.5 -9.5,9.5
       h-90
    "
      stroke="red"
      stroke-dasharray="460"
      stroke-dashoffset="460"
      stroke-width="1"
      vectorEffect="non-scaling-stroke"
  />
       <path
    d="M100,1
       h-90
       q-9.5,0 -9.5,9.5
       v19
       q0,9.5 9.5,9.5
       h90
    "
      stroke="red"
      stroke-dasharray="460"
      stroke-dashoffset="460"
      stroke-width="1"
      vectorEffect="non-scaling-stroke"
 / > 
  </svg>
</button>

Overall this achieved my goal but I found that I had the following issue. This code worked if the button width was 200px and if the height was 40px (the same size as the viewport). When I have a different button size though, the border effectively scales as desired but so does the border radius (i.e. The entire border is simply stretched out). I'd like to keep the border radius the same pixel amount even when the SVG is being scaled to a different button size. How can I achieve this?


Solution

  • If you make the SVG to fill the button, it will scale the whole SVG which will stretch the border. Instead, you could modify the SVG viewbox and path to be fit for the size you need. Here are a few examples.

    Since you are using React, if you know the height and width, you'd be able to build it based on that using props. (If it needs to work for an unknown height and width, you could use JavaScript to calculate the width of the text and set the attributes in runtime based on that.)

    [Note: I also moved the background color to the SVG, made the button background transparent, and set the SVG to -1 z-index to appear behind the text.]

    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    
    body {
      font-family: sans-serif;
      background-color: black;
      height: 100dvh;
      display: flex;
      align-items: center;
      justify-content: center;
      flex-wrap: wrap;
    }
    
    section {
      display: flex;
      align-items: center;
      justify-content: center;
      flex-wrap: wrap;
      gap: 1em;
    }
    
    .button {
      position: relative;
      background-color: transparent;
      border: 0;
      color: white;
      font-size: 18px;
      border-radius: 10px;
      cursor: pointer;
      max-width: 100%;
    }
    
    button svg {
      position: absolute;
      left: 0;
      top: 0;
      z-index: -1;
    }
    
    button rect {
      animation: button-border 2s linear both;
    }
    
    button path {
      animation: button-border 1s linear both;
    }
    
    @keyframes button-border {
      to {
        stroke-dashoffset: 0;
      }
    }
    <section>
    
    <button
      type="button"
      class="button"
      style="width: 200px; height: 40px"
    >
      My Button
    <svg viewBox="0 0 200 40" fill="none" preserveAspectRatio="none">
      <rect x="1" y="1" width="198" height="38" fill="#083a65" rx="9.5" ry="9.5" />
    
      <path
        d="M100,1
           h90
           q9.5,0 9.5,9.5
           v19
           q0,9.5 -9.5,9.5
           h-90
        "
        stroke="red"
        stroke-dasharray="460"
        stroke-dashoffset="460"
        stroke-width="1"
        vectorEffect="non-scaling-stroke"
      />
    
      <path
        d="M100,1
           h-90
           q-9.5,0 -9.5,9.5
           v19
           q0,9.5 9.5,9.5
           h90
        "
        stroke="red"
        stroke-dasharray="460"
        stroke-dashoffset="460"
        stroke-width="1"
        vectorEffect="non-scaling-stroke"
      />
    </svg>
    </button>
    
    &nbsp;&nbsp;
    
    <button
      type="button"
      class="button"
      style="width: 400px; height: 40px"
    >
      My Button with Longer Text
    <svg viewBox="0 0 400 40" fill="none" preserveAspectRatio="none">
      <rect x="1" y="1" width="398" height="38" fill="#083a65" rx="9.5" ry="9.5" />
    
      <path
        d="M200,1
           h190
           q9.5,0 9.5,9.5
           v19
           q0,9.5 -9.5,9.5
           h-190
        "
        stroke="red"
        stroke-dasharray="860"
        stroke-dashoffset="860"
        stroke-width="1"
        vectorEffect="non-scaling-stroke"
      />
    
      <path
        d="M200,1
           h-190
           q-9.5,0 -9.5,9.5
           v19
           q0,9.5 9.5,9.5
           h190
        "
        stroke="red"
        stroke-dasharray="860"
        stroke-dashoffset="860"
        stroke-width="1"
        vectorEffect="non-scaling-stroke"
      />
    </svg>
    </button>
    
    &nbsp;&nbsp;
    
    <button
      type="button"
      class="button"
      style="width: 400px; height: 120px"
    >
      My Button with Longer Text
    <svg viewBox="0 0 400 120" fill="none" preserveAspectRatio="none">
      <rect x="1" y="1" width="398" height="118" fill="#083a65" rx="9.5" ry="9.5" />
    
      <path
        d="M200,1
           h190
           q9.5,0 9.5,9.5
           v99
           q0,9.5 -9.5,9.5
           h-190
        "
        stroke="red"
        stroke-dasharray="1020"
        stroke-dashoffset="1020"
        stroke-width="1"
        vectorEffect="non-scaling-stroke"
      />
    
      <path
        d="M200,1
           h-190
           q-9.5,0 -9.5,9.5
           v99
           q0,9.5 9.5,9.5
           h190
        "
        stroke="red"
        stroke-dasharray="1020"
        stroke-dashoffset="1020"
        stroke-width="1"
        vectorEffect="non-scaling-stroke"
      />
    </svg>
    </button>
    
    </section>

    In General:

    <button
      type="button"
      class="button"
      style="width: {{width}}px; height: {{height}}px"
    >
      My Button with Longer Text
    <svg viewBox="0 0 {{width}} {{height}}" fill="none" preserveAspectRatio="none">
      <rect x="1" y="1" width="{{width - 2}}" height="{{height - 2}}" fill="#083a65" rx="9.5" ry="9.5" />
    
      <path
        d="M{{width / 2}},1
           h{{width / 2 - 10}}
           q9.5,0 9.5,9.5
           v{{height - 9.5*2 - 2}}
           q0,9.5 -9.5,9.5
           h-{{width / 2 - 10}}
        "
        stroke="red"
        stroke-dasharray="{{height * 2 + width * 2 - 20}}"
        stroke-dashoffset="{{height * 2 + width * 2 - 20}}"
        stroke-width="1"
        vectorEffect="non-scaling-stroke"
      />
    
      <path
        d="M{{width / 2}},1
           h-{{width / 2 - 10}}
           q-9.5,0 -9.5,9.5
           v{{height - 9.5*2 - 2}}
           q0,9.5 9.5,9.5
           h{{width / 2 - 10}}
        "
        stroke="red"
        stroke-dasharray="{{height * 2 + width * 2 - 20}}"
        stroke-dashoffset="{{height * 2 + width * 2 - 20}}"
        stroke-width="1"
        vectorEffect="non-scaling-stroke"
      />
    </svg>
    </button>