├── index.html ├── script.js └── styles.css /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 |
18 | 19 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | const btn = document.querySelector("[data-btn]") 2 | btn.addEventListener("click", () => { 3 | btn.classList.add("animating") 4 | }) 5 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: white; 3 | min-height: 100vh; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | margin: 0; 8 | } 9 | 10 | :root { 11 | --squish-animation-time: 500ms; 12 | --progress-animation-time: 1000ms; 13 | --circle-animation-time: 300ms; 14 | --checkmark-animation-time: 300ms; 15 | --btn-width: 125px; 16 | --btn-height: 40px; 17 | } 18 | 19 | .btn { 20 | position: relative; 21 | background-color: #2B2D2F; 22 | color: #71DFBE; 23 | border: none; 24 | border-radius: .125em; 25 | width: var(--btn-width); 26 | height: var(--btn-height); 27 | font-weight: bold; 28 | cursor: pointer; 29 | padding: 0; 30 | } 31 | 32 | .btn.animating { 33 | background-color: transparent; 34 | color: transparent; 35 | user-select: none; 36 | cursor: default; 37 | animation: hide 0ms calc(var(--squish-animation-time) + var(--progress-animation-time)) forwards; 38 | } 39 | 40 | .btn::before { 41 | content: ""; 42 | display: none; 43 | position: absolute; 44 | background-color: #2B2D2F; 45 | inset: 0; 46 | border-radius: .125em; 47 | animation: squish var(--squish-animation-time) cubic-bezier(0.26, 0.6, 0.46, 1.7); 48 | animation-fill-mode: forwards; 49 | } 50 | 51 | 52 | .btn::after { 53 | content: ""; 54 | display: none; 55 | position: absolute; 56 | background-color: #AAA; 57 | left: 51%; 58 | right: 51%; 59 | top: 45%; 60 | bottom: 45%; 61 | border-radius: .25em; 62 | animation: progress var(--progress-animation-time) var(--squish-animation-time); 63 | animation-fill-mode: forwards; 64 | } 65 | 66 | .btn.animating::before, 67 | .btn.animating::after { 68 | display: block; 69 | } 70 | 71 | .btn.animating + .checkmark-container { 72 | background-color: #2B2D2F; 73 | border-radius: .25em; 74 | width: 0; 75 | height: 0; 76 | animation: circle var(--circle-animation-time) calc(var(--squish-animation-time) + var(--progress-animation-time)) forwards cubic-bezier(0.26, 0.6, 0.46, 1.7); 77 | display: flex; 78 | justify-content: center; 79 | align-items: center; 80 | } 81 | 82 | .btn.animating + .checkmark-container .checkmark-svg { 83 | stroke: white; 84 | width: 25px; 85 | stroke-dashoffset: 40.84104919433594; 86 | stroke-dasharray: 40.84104919433594; 87 | stroke-linecap: round; 88 | stroke-linejoin: round; 89 | stroke-width: 3px; 90 | animation: checkmark var(--circle-animation-time) calc(var(--squish-animation-time) + var(--progress-animation-time) + var(--circle-animation-time)) forwards; 91 | } 92 | 93 | @keyframes squish { 94 | 100% { 95 | left: -25%; 96 | right: -25%; 97 | top: 45%; 98 | bottom: 45%; 99 | border-radius: .25em; 100 | } 101 | } 102 | 103 | @keyframes progress { 104 | 100% { 105 | left: -25%; 106 | right: -25%; 107 | } 108 | } 109 | 110 | @keyframes hide { 111 | 100% { 112 | width: 0; 113 | height: 0; 114 | } 115 | } 116 | 117 | @keyframes circle { 118 | 0% { 119 | width: calc(var(--btn-width) * 1.50); 120 | height: calc(var(--btn-height) * .1); 121 | } 122 | 100% { 123 | background-color: #71DFBE; 124 | width: 50px; 125 | height: 50px; 126 | border-radius: 100%; 127 | } 128 | } 129 | 130 | @keyframes checkmark { 131 | 100% { 132 | stroke-dashoffset: 0; 133 | } 134 | } --------------------------------------------------------------------------------