SPB Git

spb/svgarden Public

SVGarden — searchable bank of 74 self-contained SVG+CSS animation snippets (svgarden.dev)

HTML 79.2% Astro 10.3% JavaScript 6% CSS 3.7% Shell 0.8%
3.3 KB · 89 lines html
Raw Blame History
1<!--2  ============================================================3  SVGarden — https://www.svgarden.dev4  Author : Simon-Pierre Boucher5  Contact: contact@spboucher.ai6  File   : snippets/buttons/btn-border-draw.html7  Desc   : Button whose border draws itself around the label on hover8  ============================================================9-->10<!--svgarden11title: Border-draw button12slug: btn-border-draw13category: buttons14tags: [button, border, draw, hover]15difficulty: intermediate16techniques: [pathLength, stroke-dashoffset, transition-reversal, letter-spacing]17how_it_works: >18  A rect stretched over the button (preserveAspectRatio="none" + non-scaling19  stroke) is normalized to pathLength="100", so one dash of 100 hides the whole20  border. Hovering transitions stroke-dashoffset to 0 and the outline traces21  itself around the perimeter; because this is a transition rather than a22  keyframe animation, leaving the button plays the exact same motion backwards23  for free. A faint static track rect keeps the affordance visible at rest, and24  a small letter-spacing transition on the label makes the whole gesture feel25  intentional rather than decorative.26customizable:27  - { var: "--sg-color",    label: "Border",    type: color, default: "#7F77DD" }28  - { var: "--sg-size",     label: "Font size", type: range, min: 12, max: 26, default: 16, unit: px }29  - { var: "--sg-duration", label: "Speed",     type: range, min: 0.2, max: 2, step: 0.1, default: 0.6, unit: s }30created: 2026-08-1031-->32<button class="sg-btn-border-draw" type="button" style="--sg-color:#7F77DD; --sg-size:16px; --sg-duration:0.6s;">33  <svg viewBox="0 0 100 40" preserveAspectRatio="none" aria-hidden="true" focusable="false">34    <rect class="sg-btn-border-draw-track" x="1.5" y="1.5" width="97" height="37" rx="9" pathLength="100" />35    <rect class="sg-btn-border-draw-ink"   x="1.5" y="1.5" width="97" height="37" rx="9" pathLength="100" />36  </svg>37  <span class="sg-btn-border-draw-label">Draw me in</span>38</button>39<style>40  .sg-btn-border-draw {41    position: relative;42    appearance: none;43    border: 0;44    background: transparent;45    color: inherit;46    font: inherit;47    font-size: var(--sg-size);48    font-weight: 600;49    padding: 0.75em 1.6em;50    cursor: pointer;51  }52  .sg-btn-border-draw svg {53    position: absolute;54    inset: 0;55    width: 100%;56    height: 100%;57    pointer-events: none;58  }59  .sg-btn-border-draw rect {60    /* no non-scaling-stroke here: Chromium computes dash patterns in screen61       space with it, which breaks the pathLength=100 normalization */62    fill: none;63    stroke: var(--sg-color);64    stroke-width: 2;65    stroke-linecap: round;66  }67  .sg-btn-border-draw-track {68    opacity: 0.25;69  }70  .sg-btn-border-draw-ink {71    /* gap 102 > pathLength so the round end-caps never bleed onto the path at rest */72    stroke-dasharray: 100 102;73    stroke-dashoffset: 101;74    transition: stroke-dashoffset var(--sg-duration) ease-in-out;75  }76  .sg-btn-border-draw-label {77    letter-spacing: 0.02em;78    transition: letter-spacing var(--sg-duration) ease;79  }80  .sg-btn-border-draw:hover .sg-btn-border-draw-ink,81  .sg-btn-border-draw:focus-visible .sg-btn-border-draw-ink {82    stroke-dashoffset: 0;83  }84  .sg-btn-border-draw:hover .sg-btn-border-draw-label,85  .sg-btn-border-draw:focus-visible .sg-btn-border-draw-label {86    letter-spacing: 0.09em;87  }88</style>89