I like your original idea with using overflow: hidden, but to make it work I had to include an extra wrapper div.
- The outer wrapper defines a padding which serves as the display area for the gradient border
- The inner div is just the content box with a black background
.loading-box-container {
--size: 200px;
--radius: 10px;
position: relative;
width: var(--size);
height: var(--size);
padding: var(--radius);
border-radius: var(--radius);
overflow: hidden;
}
.loading-box {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
background: #000;
border-radius: var(--radius);
}
.loading-box-container::before {
content: '';
width: 150%; /* The upscaling allows the box to fill its container even when rotated */
height: 150%;
position: absolute;
top: -25%; left: -25%;
background: conic-gradient(#0000ff00, #ff0000ff);
animation: rotate-border 5s linear infinite;
}
@keyframes rotate-border {
to {
transform: rotate(360deg);
}
}
<div class="loading-box-container">
<div class="loading-box">
<p>Loading</p>
</div>
</div>
An alternative: Using @property
There's a much more elegant solution using @property, but unfortunately it only works on Chrome. I'm including here in case one day it becomes more universally supported or support for other browsers isn't important for your use case.
The conic-gradient function has a parameter that allows you to specify at what angle the gradient starts. If we can animate just that parameter, perhaps using a CSS variable, then we can animate the border with just a single div and without actually rotating anything.
Unfortunately, without some hinting the browser doesn't know how to transition a CSS variable. Therefore, we use @property to indicate the variable is an angle, telling the browser how to transition it.
@property --rotation {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
.loading-box {
--size: 200px;
--radius: 10px;
position: relative;
width: var(--size);
height: var(--size);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
background: #000;
border-radius: var(--radius);
margin: var(--radius);
}
.loading-box::before {
--rotation: 0deg;
content: '';
width: calc(100% + 2 * var(--radius));
height: calc(100% + 2 * var(--radius));
border-radius: var(--radius);
position: absolute;
top: calc(-1 * var(--radius)); left: calc(-1 * var(--radius));
background: conic-gradient(from var(--rotation), #0000ff00, #ff0000ff);
animation: rotate-border 5s linear infinite;
z-index: -1;
}
@keyframes rotate-border {
to {
--rotation: 360deg;
}
}
<div class="loading-box">
<p>Loading</p>
</div>
CanIUse for @property indicates this will only work in Chrome and Edge as of this post.
Answer from Auroratide on Stack OverflowCreating an animated gradient border with CSS
html - CSS animated gradient border on a DIV - Stack Overflow
Moving Rainbow Border Around Div
How is this wavy effect created (between the gradient header and next section)?
Videos
Hey everyone! I just published a new article on creating an animated gradient border using CSS. I've also included a version for Tailwind CSS users (with React and TypeScript too!).
I'm planning on writing more articles about design, UI, software, and other topics in the future. If you have any suggestions, feedback, or any cool ways you've used this effect in your projects please feel free to share! I'd love to hear your thoughts.
https://www.julienthibeaut.xyz/blog/create-animated-gradient-borders-with-css
I like your original idea with using overflow: hidden, but to make it work I had to include an extra wrapper div.
- The outer wrapper defines a padding which serves as the display area for the gradient border
- The inner div is just the content box with a black background
.loading-box-container {
--size: 200px;
--radius: 10px;
position: relative;
width: var(--size);
height: var(--size);
padding: var(--radius);
border-radius: var(--radius);
overflow: hidden;
}
.loading-box {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
background: #000;
border-radius: var(--radius);
}
.loading-box-container::before {
content: '';
width: 150%; /* The upscaling allows the box to fill its container even when rotated */
height: 150%;
position: absolute;
top: -25%; left: -25%;
background: conic-gradient(#0000ff00, #ff0000ff);
animation: rotate-border 5s linear infinite;
}
@keyframes rotate-border {
to {
transform: rotate(360deg);
}
}
<div class="loading-box-container">
<div class="loading-box">
<p>Loading</p>
</div>
</div>
An alternative: Using @property
There's a much more elegant solution using @property, but unfortunately it only works on Chrome. I'm including here in case one day it becomes more universally supported or support for other browsers isn't important for your use case.
The conic-gradient function has a parameter that allows you to specify at what angle the gradient starts. If we can animate just that parameter, perhaps using a CSS variable, then we can animate the border with just a single div and without actually rotating anything.
Unfortunately, without some hinting the browser doesn't know how to transition a CSS variable. Therefore, we use @property to indicate the variable is an angle, telling the browser how to transition it.
@property --rotation {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
.loading-box {
--size: 200px;
--radius: 10px;
position: relative;
width: var(--size);
height: var(--size);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
background: #000;
border-radius: var(--radius);
margin: var(--radius);
}
.loading-box::before {
--rotation: 0deg;
content: '';
width: calc(100% + 2 * var(--radius));
height: calc(100% + 2 * var(--radius));
border-radius: var(--radius);
position: absolute;
top: calc(-1 * var(--radius)); left: calc(-1 * var(--radius));
background: conic-gradient(from var(--rotation), #0000ff00, #ff0000ff);
animation: rotate-border 5s linear infinite;
z-index: -1;
}
@keyframes rotate-border {
to {
--rotation: 360deg;
}
}
<div class="loading-box">
<p>Loading</p>
</div>
CanIUse for @property indicates this will only work in Chrome and Edge as of this post.
Hi is this what you are looking for?
What I did was I added a new div which will be the "mask" as well as a container div for both the mask and the loadingBox.
I then sized the mask to be a little larger than your visible area, make it a transparent background, and then gave it a large outline the same color as your background to effectively mask out a border. I then fiddled with z-indexs of the mask, the loadingbox and the before. I also added some actual borders on mask to box it out into a nice shape.
Take a look:
.gradient-box {
display: flex;
align-items: center;
width: 90%;
margin: auto;
max-width: 22em;
position: relative;
padding: 30% 2em;
box-sizing: border-box;
border: 5px solid blue;
color: #FFF;
background: #000;
background-clip: padding-box; /* !importanté */
border: solid 5px transparent; /* !importanté */
border-radius: 1em;
}
.gradient-box:before {
content: '';
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
z-index: -3;
margin: -35px; /* !importanté */
border-radius: inherit; /* !importanté */
background: conic-gradient(#0000ff00, #ff0000ff);
-webkit-animation: rotate-border 5s linear infinite;
-moz-animation: rotate-border 5s linear infinite;
-o-animation: rotate-border 5s linear infinite;
animation: rotate-border 3s linear infinite;
}
@keyframes rotate-border {
to {
transform: rotate(360deg);
}
}
html { height: 100%; background: #000; display: flex; }
body { margin: auto; }
.mask {
position: absolute;
box-sizing: border-box;
background-color: transparent;
outline: 65px solid black;
height: 100%;
width: 100%;
border: 2px solid black;
border-left: 7px solid black;
border-right: 7px solid black;
z-index: -1;
}
.container {
position: relative;
}
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Loading DIV Test</title>
</head>
<body>
<div class="container">
<div class="mask"></div>
<div id="loadingBox" class="gradient-box">
<p>Loading.</p>
</div>
</div>
</body>