I'm looking to create an animated border input similar to the one shown in this Dribbble post: https://dribbble.com/shots/20266537-Login-page.
Currently, I have an idea using a background image, but it doesn't support border radius:
@keyframes bg {
0% {
background-size: 0% 3px, 3px 0, 0 3px, 3px 0;
background-position: 70px -2px, calc(100% + 2px) -1px, 100% 100%,
-2px 100%;
}
20% {
background-size: 90% 3px, 3px 0, 0 3px, 3px 0;
background-position: 70px -2px, calc(100% + 2px) -1px, 100% 100% -2px 100%;
}
40% {
background-size: 90% 3px, 3px calc(100% + 2px), 0 3px, 3px 0;
background-position: 70px -2px, calc(100% + 2px) -1px,
calc(100% + 1px) calc(100% + 2px), -2px 100%;
}
60% {
background-size: 90% 3px, 3px calc(100% + 2px), calc(100% + 2px) 3px,
3px 0;
background-position: 70px -2px, calc(100% + 2px) -1px,
calc(100% + 1px) calc(100% + 2px), -2px 100%;
}
100% {
background-size: 100% 3px, 3px calc(100% + 2px), calc(100% + 2px) 3px,
3px calc(100% + 2px);
background-position: 0 -2px, calc(100% + 2px) -1px,
calc(100% + 1px) calc(100% + 2px), -2px 100%;
/* border: 2px solid #82c0fe; */
}
}
.box {
position:relative;
border-radius:10px;
border: 1px solid #ccc;
width: 25%;
margin: 2rem auto;
padding: 2em;
background-repeat: no-repeat;
background-image: linear-gradient(to right, #5D26C1, #a17fe0, #59C173),
linear-gradient(to bottom, #5D26C1, #a17fe0, #59C173),
linear-gradient(to right,#5D26C1, #a17fe0, #59C173),
linear-gradient(to bottom, #5D26C1, #a17fe0, #59C173);
animation: bg 2s forwards 1;
}
<div class="box"></div>
I would appreciate any assistance or alternative ideas you may have to achieve the desired effect. Thank you!
Consider using an <svg>
:
div {
position: relative;
}
input {
border: 0;
width: 300px;
height: 50px;
}
input:focus {
outline: 2px solid transparent;
}
svg {
position: absolute;
left: 0;
top: 0;
pointer-events: none;
}
.focus {
transition: stroke-dashoffset 1s;
}
input:focus + * .focus {
stroke-dashoffset: 0;
}
<div>
<input type="text" />
<svg width=300 height=50 viewBox="0 0 300 50">
<defs>
<linearGradient id="gradient" gradientTransform="rotate(10)">
<stop offset="5%" stop-color="blue" />
<stop offset="95%" stop-color="purple" />
</linearGradient>
<mask id=border>
<rect
x=1
y=1
rx=10
ry=10
stroke-width=2
stroke=#ccc
width=298
height=48
fill=none
pathLength=1
stroke-dasharray=1
stroke-dashoffset=-.08
id=border
/>
</mask>
</defs>
<rect
x=1
y=1
rx=10
ry=10
stroke-width=2
stroke=#ccc
width=298
height=48
fill=none
mask=url(#border)
/>
<rect
x=1
y=1
rx=10
ry=10
stroke-width=2
stroke="url(#gradient)"
width=298
height=48
fill=none
mask=url(#border)
pathLength=1
stroke-dasharray=1
stroke-dashoffset=.92
class="focus"
/>
</svg>
The <svg>
should be the same size as the input
:
input {
…
width: 300px;
height: 50px;
}
<svg width=300 height=50 viewBox="0 0 300 50">
…
</svg>
Have the <svg>
positioned on top of the <input>
:
div {
position: relative;
}
…
svg {
position: absolute;
left: 0;
top: 0;
pointer-events: none;
}
<div>
<input type="text" />
<svg …>
Use a stroked <rect>
for the inactive border state:
<svg …>
<rect
x=1
y=1
rx=10
ry=10
stroke-width=2
stroke=#ccc
width=298
height=48
fill=none
/>
</svg>
Add another rectangle on top for the gradient stroke:
<svg …>
<defs>
<linearGradient id="gradient" gradientTransform="rotate(10)">
<stop offset="5%" stop-color="blue" />
<stop offset="95%" stop-color="purple" />
</linearGradient>
</defs>
<rect
x=1
y=1
rx=10
ry=10
stroke-width=2
stroke=#ccc
width=298
height=48
fill=none
/>
<rect
x=1
y=1
rx=10
ry=10
stroke-width=2
stroke="url(#gradient)"
width=298
height=48
fill=none
/>
</svg>
Add a mask to both <rect>
s for the gap
<svg …>
<defs>
…
<mask id=border>
<rect
x=1
y=1
rx=10
ry=10
stroke-width=2
stroke=#ccc
width=298
height=48
fill=none
pathLength=1
stroke-dasharray=1
stroke-dashoffset=-.08
id=border
/>
</mask>
</defs>
<rect
…
mask=url(#border)
/>
<rect
…
mask=url(#border)
pathLength=1
/>
</svg>
Add the stroke animation on focus by having a stroke length with dashes, where the dash is the length of the <rect>
. Have the gap of the dash be displayed and then transition it such that the "filled-in" portion of the stroke dash becomes visible.
.focus {
transition: stroke-dashoffset 1s;
}
input:focus + * .focus {
stroke-dashoffset: 0;
}
<svg …>
…
<rect
…
pathLength=1
stroke-dasharray=1
stroke-dashoffset=.92
class="focus"
/>
</svg>