Success-morph button
Click morphs the button into a spinner, then a success check
Techniques: data-state-attribute · width-morph · pathLength · animation-choreography
<!--
============================================================
SVGarden — https://www.svgarden.dev
Author : Simon-Pierre Boucher
Contact: contact@spboucher.ai
File : snippets/buttons/btn-success-morph.html
Desc : Click morphs the button into a spinner, then a success check
============================================================
-->
<button class="sg-btn-success-morph" type="button" data-state="idle" style="--sg-color:#7F77DD; --sg-success:#2BA05A; --sg-size:16px;">
<span class="sg-btn-success-morph-label">Save changes</span>
<svg viewBox="0 0 28 28" aria-hidden="true" focusable="false">
<circle class="sg-btn-success-morph-arc" cx="14" cy="14" r="10" pathLength="100" />
<path class="sg-btn-success-morph-check" d="M8 14.5 L12.5 19 L20 9.5" pathLength="100" />
</svg>
</button>
<script>
document.querySelectorAll('.sg-btn-success-morph:not([data-sg-init])').forEach(function (btn) {
btn.setAttribute('data-sg-init', '');
btn.addEventListener('click', function () {
var state = btn.getAttribute('data-state');
if (state === 'idle') {
btn.setAttribute('data-state', 'loading');
setTimeout(function () { btn.setAttribute('data-state', 'done'); }, 1400);
} else if (state === 'done') {
btn.setAttribute('data-state', 'idle');
}
});
});
</script>
<style>
.sg-btn-success-morph {
appearance: none;
border: 0;
font: inherit;
font-size: var(--sg-size);
font-weight: 600;
color: #ffffff;
background: var(--sg-color);
height: 3em;
max-width: 12em;
padding: 0 1.8em;
border-radius: 1.5em;
cursor: pointer;
position: relative;
overflow: hidden;
white-space: nowrap;
transition: max-width 0.45s cubic-bezier(0.6, 0, 0.3, 1), padding 0.45s ease, background 0.4s ease;
}
.sg-btn-success-morph-label {
transition: opacity 0.2s ease;
}
.sg-btn-success-morph svg {
position: absolute;
inset: 0;
margin: auto;
width: 1.9em;
height: 1.9em;
}
.sg-btn-success-morph-arc,
.sg-btn-success-morph-check {
fill: none;
stroke: #ffffff;
stroke-width: 2.5;
stroke-linecap: round;
stroke-linejoin: round;
}
.sg-btn-success-morph-arc {
stroke-dasharray: 28 72;
opacity: 0;
transform-box: fill-box;
transform-origin: center;
}
.sg-btn-success-morph-check {
stroke-dasharray: 100;
stroke-dashoffset: 100;
}
.sg-btn-success-morph[data-state='loading'] {
max-width: 3em;
padding: 0;
cursor: progress;
}
.sg-btn-success-morph[data-state='loading'] .sg-btn-success-morph-label,
.sg-btn-success-morph[data-state='done'] .sg-btn-success-morph-label {
opacity: 0;
}
.sg-btn-success-morph[data-state='loading'] .sg-btn-success-morph-arc {
opacity: 1;
animation: sg-btn-success-morph-spin 0.8s linear infinite;
}
.sg-btn-success-morph[data-state='done'] {
max-width: 3em;
padding: 0;
background: var(--sg-success);
}
.sg-btn-success-morph[data-state='done'] .sg-btn-success-morph-check {
animation: sg-btn-success-morph-draw 0.45s ease-out 0.1s forwards;
}
@keyframes sg-btn-success-morph-spin {
to { transform: rotate(360deg); }
}
@keyframes sg-btn-success-morph-draw {
to { stroke-dashoffset: 0; }
}
</style>