Pure CSS gradient circle border - css

I have this UI requirement
At the moment, I have a working solution of a div (with a fixed height and width and a background image for the outer gradient border) and a pseudo element, positioned absolute with a background image of the inner border.
.div {
position: relative;
width: 254px;
height: 254px;
border: 2px solid transparent;
border-radius: 50%;
background: url(../img/gradient_border_circle.png) no-repeat 50%;
}
div:before {
content: "";
position: absolute;
top: 50%;
transform: translate(-50%,-50%);
left: 50%;
width: 98px;
height: 98px;
border-radius: 50%;
background: url(../img/gradient_border_circle_inner.png) no-repeat 50%;
}
However, am looking for a more elegant solution (pure css or svg gradient?) without the use of background images where the gradient can scale with no pixelation.
I have researched and closest I have come across is https://codepen.io/nordstromdesign/pen/QNrBRM and Possible to use border-radius together with a border-image which has a gradient? But I need a solution where the centre is transparent in order to show through the page's background
Update: Ideally, am looking for a solution with relatively good support in all modern browsers.

SVG is the recommended way to create a circle shape and draw gradient outline / border around it.
SVG has a circle element that can be used to draw a circle shape. This shape can be filled and outlined with a solid color, gradient or pattern.
* {box-sizing: border-box;}
body {
background: linear-gradient(#333, #999);
text-align: center;
min-height: 100vh;
padding-top: 10px;
margin: 0;
}
svg {vertical-align: top;}
<svg width="210" height="210">
<defs>
<linearGradient id="grad1" x1="0" y1="1" x2="1" y2="0">
<stop offset="0" stop-color="#f5d700" />
<stop offset="1" stop-color="#0065da" />
</linearGradient>
<linearGradient id="grad2" xlink:href="#grad1" x1="1" y1="0" x2="0" y2="1"></linearGradient>
</defs>
<g fill="none">
<circle cx="100" cy="100" r="95" stroke="url(#grad1)" stroke-width="2" />
<circle cx="100" cy="100" r="40" stroke="url(#grad2)" stroke-width="5" />
</g>
</svg>

You can use a mask to achieve what you're looking for. You will need an SVG file with a transparent circle. Here I used an image from the internet, but you can make your own to accommodate your needs:
mask: url(circle.svg);
CodePen (set the background to red to show transparency)
mask reference
making your own masks

Here is a CSS only solution that should work fine in all modern browsers (tested on Chrome, Firefox and Edge)
.box {
--it:20px; /* thickness of inner gradient */
--ot:10px; /* thickness of outer gradient */
--s:30%; /* starting point of inner gradient */
width:200px;
display:inline-flex;
box-sizing:border-box;
border-radius:50%;
border:var(--ot) solid transparent;
background:
/* inner gradient clipped to the padding area */
conic-gradient(red,blue,green,red) padding-box,
/* outer gradient visible on the border area */
conic-gradient(purple,yellow,orange,purple) border-box;
-webkit-mask:radial-gradient(farthest-side,
transparent var(--s),
#fff calc(var(--s) + 1px)
calc(var(--s) + var(--it)),
#fff0 calc(var(--s) + var(--it) + 1px)
calc(100% - var(--ot)),
#fff calc(100% - var(--ot) + 1px));
}
/* keep the ratio */
.box::before {
content:"";
padding-top:100%;
}
body {
background:pink;
}
<div class="box"></div>
<div class="box" style="--s:5%;--ot:20px;width:150px;"></div>
<div class="box" style="--s:calc(100% - 20px);--it:10px;width:220px;"></div>
<div class="box" style="--s:0%;--it:50%;width:80px;"></div>
I am adding 1px in the calculation to avoid jagged edges. You can replace the conic-gradient() with another type of gradient or even an image

Related

How to apply gradient blur to one side of an image?

The goal is to blur the right side of the image like this with css only
The example:
so as you can see they applied a blurry side on top of the image. i tried the backdrop filter but didn't work, also i didn't know how to work with the mask and the clip path...
Actually, it is possible to get a fairly similar result with CSS.
More detailed: codepen
.effet {
width: 400px;
height: 300px;
margin: 0 auto 50px auto;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.5);
}
.effet img {
position: absolute;
}
.filtre--r {
-webkit-mask: -webkit-radial-gradient(
center,
closest-side,
transparent 30%,
black 80%
);
-webkit-mask: radial-gradient(
closest-side at center,
transparent 50%,
black 110%
);
-webkit-filter: blur(5px);
mask: url("#mask-radial");
filter: url("#filtre1");
}
.filtre:hover {
-webkit-mask: none;
-webkit-filter: none;
mask: none;
filter: none;
}
<div class="effet">
<img src="http://css3create.com/squelettes/images/articles/flou-localise-1.jpg" alt="" />
<img class="filtre filtre--r" src="http://css3create.com/squelettes/images/articles/flou-localise-1.jpg" alt="" />
</div>
<svg height="0">
<filter id="filtre1">
<feGaussianBlur in="SourceGraphic" stdDeviation="5" />
</filter>
</svg>

SVG vector-effect="non-scaling-stroke" artifacts

vector-effect seem to cause artifacts. If you hover over the star, you can see how it leaves trails in latest release version of Chrome:
If I remove vector-effect the artifacts are gone.
The only way I was able to fix this is by setting overflow: hidden on the svg element during the animation, such that the corners which overflow the element bounding box because of the stroke thickness are hidden, which is not desired.
body {
padding: 10px;
}
.svg-wrapper {
width: 300px;
height: 300px;
-webkit-transition: width 2s, height 2s; /* Safari */
transition: width 2s, height 2s;
}
.svg-wrapper:hover {
width: 100px;
height: 100px;
}
.svg-wrapper svg {
overflow: visible;
}
<div class="svg-wrapper">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" preserveAspectRatio="none" viewBox="31, 25, 238, 226" stroke-width="10" stroke="red" >
<polygon points="150,25 179,111 269,111 197,165 223,251 150,200 77,251 103,165 31,111 121,111" vector-effect="non-scaling-stroke" />
</svg>
</div>
Is this issue know? Looking forward for some good workarounds

Why does a rect require width and height attribute in Firefox?

In the following example I created a blinking eyes animation using CSS and an SVG: http://codepen.io/JamesTheHacker/pen/oLZVrY
It works fine in chrome, but on Firefox the eyes do not appear unless I specifically provide a width and height attribute on the <rect>.
Without the attribute the eyes are not visible. If I add the attribute the CSS height animation has no effect.
In SVG 1.1 height and width are attributes i.e. you can't set the height and width via CSS.
In SVG 2 it is proposed width and height should be CSS properties.
Back in 2016 only Chrome had implemented this part of the unfinished SVG 2 specification, since then Firefox has also implemented it so the testcase works as expected.
You have already an excellent answer indicating what the problem is
You can solve it this way
* { box-sizing: border-box; }
body { background-color: rgb(0, 184, 234); }
svg {
display: block;
margin: 90px auto;
width: 380px;
height: 130px;
}
/*
* Keyframes for blink animation
*/
#keyframes blink {
0% { transform: scaleY(1); }
40% { transform: scaleY(0); }
80% { transform: scaleY(1); }
}
.eye {
height: 20px;
width: 20px;
animation-name: blink;
animation-duration: 1s;
animation-iteration-count: infinite;
transform-origin: center 315px;
}
<svg>
<g transform="translate(-108.75 -258.41)">
<path id="specs" fill="#FFF" d="M328.911,258.412v10.29h-19.127v16.192h-19.16v16.249h19.287v-16.191h19v62.169h19.432
v-68.736h123.995v68.761h19.432v-88.709l-18.047,0.001v-0.025h-125.38H328.911z M124.069,258.454v0.001h-15.321v88.709h19.427
v-68.733h123.996l0.008,68.757h19.423v-62.401h19.032v-16.25h-19.032v-10.053h-18.047v-0.026H124.072L124.069,258.454z
M348.294,347.163v17.488h19.4v19.951h85.141v-19.976h-85.109v-17.464H348.294L348.294,347.163z M452.819,347.171v17.439h19.431
v-17.439H452.819z M128.133,347.203v17.487h19.398v19.951h85.149l-0.008-19.975h-85.117l0.001-17.464h-19.427H128.133z
M232.658,347.212v17.439h19.423v-17.439H232.658z"/>
<g id="eyes">
<rect class="eye" x="181.759" y="305.026" width="20" height="20" fill="#FFF" />
<rect class="eye" x="402.759" y="305.026" width="20" height="20" fill="#FFF" />
</g>
</g>
</svg>

Border gradient from once color to another uing css [duplicate]

I have a problem with CSS3. I don't know how to make a diagonal round gradient border like that:
I found something like this:
.box {
width: 250px;
height: 250px;
margin: auto;
background: #eee;
border: 20px solid transparent;
-moz-border-image: -moz-linear-gradient(top left, #3acfd5 0%, #3a4ed5 100%);
-webkit-border-image: -webkit-linear-gradient(top left, #3acfd5 0%, #3a4ed5 100%);
border-image: linear-gradient(to bottom right, #3acfd5 0%, #3a4ed5 100%);
border-image-slice: 1;
}
<div class="box"></div>
But unfortunately this works only with squares.
Any help would be appreciated.
Conical gradient is a gradient which goes along the circular arc around a center. This is what we see in color wheels. As Paulie_D had noted, these are currently not possible with CSS but Lea Verou has developed a polyfill for it.
Having said that, what you are looking for doesn't seem to be a conical gradient, it is normal angled linear gradient but applied only to the borders.
This cannot be achieved through CSS border-image property because of how it is intended to work as per specs.
A box's backgrounds, but not its border-image, are clipped to the appropriate curve
If the center portion of the circle is a solid color then the approach mentioned in Vitorino's answer can be used. If it is not a solid color (that is, the page background is a gradient or an image which needs to show through) then it would not help. The following approaches can be used for that case.
Using Mask Image:
This approach uses a circular mask image to mask the inner portion of the circle. This makes it look as though only the border has the gradient applied to it. The drawback is that this feature is currently supported only in Webkit powered browsers.
.border-gradient-mask {
height: 200px;
width: 200px;
border-radius: 50%;
background-image: linear-gradient(to bottom left, #7B73A4 0%, #150E5E 100%);
-webkit-mask-image: radial-gradient(circle at center, transparent 57%, white 58%);
mask-image: radial-gradient(circle at center, transparent 57%, white 58%);
}
body {
background: radial-gradient(circle at center, sandybrown, chocolate);
}
<div class="border-gradient-mask"></div>
Using SVG Shape or Mask:
The other approach is to use SVG circle element to create the circle and then assign the gradient to the stroke property. The gradient also has a gradientTransform applied to it because that is the only way to produce angled linear gradients with SVG.
.border-gradient-svg {
position: relative;
height: 200px;
width: 200px;
border-radius: 50%;
}
.border-gradient-svg svg {
position: absolute;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
}
.border-gradient-svg circle {
fill: transparent;
stroke: url(#grad);
stroke-width: 8;
}
body {
background: radial-gradient(circle at center, sandybrown, chocolate);
}
<div class="border-gradient-svg">
<svg viewBox="0 0 100 100">
<defs>
<linearGradient id="grad" gradientUnits="objectBoundingBox" gradientTransform="rotate(135 0.5 0.5)">
<stop offset="0%" stop-color="#7B73A4" />
<stop offset="100%" stop-color="#150E5E" />
</linearGradient>
</defs>
<circle r="46" cx="50" cy="50" />
</svg>
</div>
The same can be achieved by using SVG mask also. All that is needed is to create a mask with two circle elements, fill the larger circle with white, smaller circle with black and then apply the mask to our original circle element. The area occupied by smaller circle (with black fill) will be transparent.
.border-gradient-svg {
position: relative;
height: 200px;
width: 200px;
border-radius: 50%;
}
.border-gradient-svg svg {
position: absolute;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
}
.border-gradient-svg .grad-border {
fill: url(#grad);
mask: url(#masker);
}
body {
background: radial-gradient(circle at center, sandybrown, chocolate);
}
<div class="border-gradient-svg">
<svg viewBox="0 0 100 100">
<defs>
<linearGradient id="grad" gradientUnits="objectBoundingBox" gradientTransform="rotate(135 0.5 0.5)">
<stop offset="0%" stop-color="#7B73A4" />
<stop offset="100%" stop-color="#150E5E" />
</linearGradient>
<mask id="masker" x="0" y="0" width="100" height="100">
<circle r="50" cx="50" cy="50" fill="#fff" />
<circle r="42" cx="50" cy="50" fill="#000" />
</mask>
</defs>
<circle r="50" cx="50" cy="50" class="grad-border"/>
</svg>
</div>
Using Clip Path:
Another approach to creating this would be to use a clip-path (with inline SVG) with clip-rule set to evenodd. Advantage of clip path solution over the others is that this will trigger hover effects only while hovering on filled area (and not the transparent area). The drawback is that IE doesn't support clip paths (even with SVG).
.border-gradient-clip {
height: 200px;
width: 200px;
border-radius: 50%;
background-image: linear-gradient(to bottom left, #7B73A4 0%, #150E5E 100%);
-webkit-clip-path: url(#clipper);
clip-path: url(#clipper);
}
body {
background: radial-gradient(circle at center, sandybrown, chocolate);
}
<svg width="0" height="0">
<defs>
<clipPath id="clipper" clipPathUnits="objectBoundingBox">
<path d="M0,0.5 a0.5,0.5 0 1,0 1,0 a0.5,0.5 0 1,0 -1,0z M0.08,0.5 a0.42,0.42 0 1,0 0.84,0 a0.42,0.42 0 1,0 -0.84,0z" clip-rule="evenodd" />
</clipPath>
</defs>
</svg>
<div class="border-gradient-clip"></div>
You can try something like this i have used a pseudo element with -ve z-index
Note: the background is not transparent as i have used a background-color for inner element
.box {
width: 250px;
height: 250px;
position: relative;
margin: auto;
margin: 30px;
border-radius: 50%;
background: #fff;
}
.box:after {
content: '';
position: absolute;
top: -15px;
bottom: -15px;
right: -15px;
left: -15px;
background-image: linear-gradient(to bottom left, #7B73A4 0%, #150E5E 100%);
z-index: -1;
border-radius: inherit;
}
<div class="box"></div>

How to create reuleaux triangle shape using CSS3

I need a help to make reuleaux triangle shape using CSS3 like below the image. The shape has a white border around. How is it possible?
CSS is not the right tool for creating such shapes even though they can be created using it. They will require multiple real/pseudo-elements, transforms etc and even then maintenance of the curves, their radii etc are very tricky. It gets even more complex when you require borders around them or have to place images or gradients inside them.
The best and recommended tool for creating such shapes is SVG as they have the following pros:
SVGs are scalable by nature and so are very good for responsive designs
SVG shapes can take images or gradients as fills
Curve and radii control is very optimum
Below is a sample snippet for creating the reuleaux triangle shape using SVG. All it needs is a single path element with 3 Quadratic Curveto commands.
svg {
height: 200px;
width: 200px;
}
path {
fill: steelblue;
stroke: white;
stroke-width: 2;
}
path.image {
fill: url(#g-image);
}
body {
background-image: radial-gradient(circle, #3F9CBA 0%, #153346 100%);
}
<svg viewBox="0 0 105 105" preserveAspectRatio="none">
<path d="M2,15 q50,-25 100,0 q0,50 -50,85 q-50,-30 -50,-85z" />
</svg>
<svg viewBox="0 0 105 105" preserveAspectRatio="none">
<defs>
<pattern id="g-image" width="1" height="1" patternUnits="objectBoundingBox">
<image xlink:href="http://lorempixel.com/200/200/nature/4" width="200" height="200" />
</pattern>
</defs>
<path d="M2,15 q50,-25 100,0 q0,50 -50,85 q-50,-30 -50,-85z" class="image" />
</svg>
The same can be achieved by using CSS Clip-path with inline SVG for the path also but the support is non-existent in IE for this and hence it is not recommended.
div {
position: relative;
background: white;
height: 200px;
width: 200px;
-webkit-clip-path: url(#clipper);
clip-path: url(#clipper);
}
div:after {
position: absolute;
content: '';
height: calc(100% - 4px);
width: calc(100% - 4px);
top: 2px;
left: 2px;
background: steelblue;
-webkit-clip-path: url(#clipper);
clip-path: url(#clipper);
}
div.image:after{
background: url(http://lorempixel.com/200/200);
}
body {
background-image: radial-gradient(circle, #3F9CBA 0%, #153346 100%);
}
/* Just for demo */
div{
display: inline-block;
}
<svg width="0" height="0">
<defs>
<clipPath id="clipper" clipPathUnits="objectBoundingBox">
<path d="M0,0.15 q0.5,-0.25 1,0 q0,0.5 -0.5,0.85 q-0.5,-0.3 -0.5,-0.85z" />
</clipPath>
</defs>
</svg>
<div></div>
<div class='image'></div>
SVG solution
svg {
border: 1px solid black;
}
<svg width="400px" viewBox="0 0 100 100">
<path stroke="black" d="m50 90,
q -40 -20, -40 -80,
q 40 -10, 80 0,
q 0 60, -40 80z" />
</svg>
This shape is possible with pure CSS in a single element with a little bit of creativity.
It is not exactly the shape as above as it has rounded corners but its still pretty darn close.
div {
width: 200px;
height: 200px;
background: blue;
border-radius: 75% 75% 80% 80% / 15% 15% 150% 150%;
}
<div></div>
Here is another possible way to do it
div {
width: 200px;
height: 200px;
background: blue;
border-radius: 10% 100% 100% 0 / 100% 100% 10% 0;
transform: rotate(-45deg);
margin-left: 50px;
}
<div></div>

Resources