Reputation: 2265
I'm trying to create a loading DIV
that has a border that looks like an indeterminate progress ring spinner.
I'm pretty close based on one of the examples on https://css-tricks.com/gradient-borders-in-css/
This is great when the border doesn't rotate. When you set the border
in the :before
element to match the transparent border
in the gradient-box
element then the static gradient border looks perfect.
However, once the animation is added, because the whole :before
element rotates you get a pretty odd effect - as shown in the example below.
.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: -1;
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; }
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Loading DIV Test</title>
</head>
<body>
<div id="loadingBox" class="gradient-box">
<p>Loading.</p>
</div>
</body>
I've tried playing about with overflow: hidden; but the border just disappears.. is there any way to 'mask' the :before element in a way that whatever is behind this loading Div is still visible behind it and so that the border stays as its intended width?
Basically, my goal is that the colour gradient in the border
rotates to give the effect of a spinning/rotating edge.
Upvotes: 0
Views: 4339
Reputation: 2577
I like your original idea with using overflow: hidden
, but to make it work I had to include an extra wrapper div.
.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>
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.
Upvotes: 2
Reputation: 5337
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>
Upvotes: 0