#app
View Compiled
@use "sass:math";
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,400i,700");
:root {
--font-size: 16px;
@for $i from 1 through 16 {
--n-#{$i}: #{0.25 * $i}rem;
}
--button-color: #fff059;
--button-color-light: #fff58a;
--button-color-dark: #ffed24;
}
#app {
color: #ffffff;
font-family: "Open Sans", sans-serif;
font-size: var(--font-size);
background-image: linear-gradient(45deg, #fafafa, #e5e5e5);
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.sub {
--width: calc(100vw - var(--n-16));
background-image: linear-gradient(20deg, #3084ff, #1dbdff);
width: var(--width);
height: var(--height);
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
border-radius: var(--n-4);
box-shadow: 0 var(--n-3) var(--n-5) rgba(#000000, 0.3);
box-sizing: border-box;
}
.sub-text, .sub-form {
width: 100%;
box-sizing: border-box;
}
.sub-text {
padding: var(--n-8);
padding-bottom: var(--n-2);
h2 {
font-size: var(--n-8);
}
h2, p {
margin: 0;
margin-bottom: var(--n-4);
&:last-child {
margin-bottom: 0;
}
}
}
.sub-form {
padding: var(--n-2) 0 var(--n-6);
.field {
padding: var(--n-2) var(--n-8);
input[type="email"], a {
font-family: inherit;
font-size: inherit;
width: 100%;
padding: var(--n-3);
border: 0;
margin: 0;
outline: none;
border-radius: var(--n-2);
box-sizing: border-box;
}
input[type="email"] {
background-color: #ffffff;
}
a {
color: #5c4400;
text-align: center;
text-decoration: none;
background-color: var(--button-color);
display: inline-block;
transition: all 128ms ease-out;
&:hover {
background-color: var(--button-color-light);
}
&:active {
background-color: var(--button-color-dark);
}
&:focus {
box-shadow: 0 0 0 var(--n-1) rgba(#fff059, 0.6);
}
span {
margin-left: var(--n-1);
}
}
}
}
.sub-mask {
background-image: linear-gradient(20deg, #009116, #48e30b);
width: 100%;
height: 100%;
overflow: hidden;
position: absolute;
top: 0;
left: 0;
border-radius: 50%;
transform: scale(0);
}
.sub-check-circle {
fill: transparent;
stroke: rgba(#ffffff, 0.5);
stroke-width: 8;
stroke-dasharray: 0 #{2 * math.$pi * 45};
width: 60%;
height: 60%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-90deg) scaleY(-1);
}
.sub-check {
--width: 0%;
--height: 0%;
width: 30%;
height: 20%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-55deg);
span {
background-color: rgba(#ffffff, 0.5);
position: absolute;
&:first-child {
width: 16%;
height: var(--height);
top: 0;
left: 0;
}
&:last-child {
width: var(--width);
height: 24%;
bottom: 0;
left: 16%;
}
}
}
@media screen and (min-width: 768px) {
.sub {
--width: 720px;
width: var(--width);
max-width: var(--width);
max-height: var(--width);
flex-direction: row;
}
.sub-text {
flex-shrink: 1;
padding-bottom: var(--n-8);
}
.sub-form {
width: 360px;
padding: var(--n-6) 0;
flex-shrink: 0;
}
}
View Compiled
import React, { useState, useRef } from "https://cdn.skypack.dev/react@17.0.1";
import ReactDOM from "https://cdn.skypack.dev/react-dom@17.0.1";
import gsap from "https://cdn.skypack.dev/gsap@3.6.1";
// GSAP timeline instance
const tl = gsap.timeline();
// Circumference of the circle of check mark
const CHECK_CIRC = 2 * Math.PI * 45;
/**
* Subscribe interaction JSX element
*/
function SubInteraction() {
// E-mail address value
const [email, setEmail] = useState("");
// If the element is animating
const [isAnimating, setIsAnimating] = useState(false);
// Subscribe element
const subElm = useRef(null);
// Subscribe mask element
const subMaskElm = useRef(null);
// Circle part of the subcribe element
const subCheckCircleElm = useRef(null);
// Check part of the subcribe element
const subCheckElm = useRef(null);
// Subscribe button click event
const subscribeClick = (evt) => {
if (isAnimating === true) {
return;
}
setIsAnimating(true);
// Main box to circle
tl.to(subElm.current, {
width: "75vmin",
height: "75vmin",
borderRadius: "50%",
ease: "power4.out",
duration: 0.5
});
// Green circle scale to normal
tl.to(subMaskElm.current, {
scale: 1,
ease: "power4.out",
duration: 0.5
}, "-=0.5");
// Draw a circle
tl.to(subCheckCircleElm.current, {
strokeDasharray: CHECK_CIRC + " " + CHECK_CIRC,
ease: "power3.out",
duration: 0.5
});
// Draw a check mark
tl.to(subCheckElm.current, {
"--height": "100%",
ease: "power2.out",
duration: 0.25
}, "-=0.5");
// Draw a check mark
tl.to(subCheckElm.current, {
"--width": "84%",
ease: "power2.out",
duration: 0.25
}, "-=0.25");
// Do nothing, just to delay the reset
tl.to(subElm.current, {
duration: 2.5,
onComplete: () => {
// Clear email
setEmail("");
// Reset animating status
setIsAnimating(false);
// Reset the animation to beginning
tl.progress(0);
tl.clear();
// Unset the size of the box
tl.set(subElm.current, {
width: null,
height: null,
});
}
});
}
// Render JSX
return (
<div className="sub" ref={subElm}>
<div className="sub-text">
<h2>Great! Ain't it?</h2>
<p>Do you want to hear from us more?</p>
</div>
<div className="sub-form">
<div className="field">
<input
type="email"
placeholder="Enter your e-mail address"
value={email}
onInput={evt => { setEmail(evt.target.value); }}
/>
</div>
<div className="field">
<a
href="#"
role="button"
onClick={subscribeClick}
>
<i className="far fa-bell"></i>
<span>Subscribe now</span>
</a>
</div>
</div>
<div className="sub-mask" ref={subMaskElm}>
<svg
viewBox="0 0 100 100"
className="sub-check-circle"
ref={subCheckCircleElm}
>
<circle r="45" cx="50" cy="50"></circle>
</svg>
<div className="sub-check" ref={subCheckElm}>
<span></span>
<span></span>
</div>
</div>
</div>
);
}
ReactDOM.render(<SubInteraction />, document.querySelector("#app"));
View Compiled
This Pen doesn't use any external JavaScript resources.