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

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

Related

Why does Google Chrome round all svg icons during animation with mix-blend-mode: multiply?

On hover, only the green svg icon (first) should smoothly change its opacity to 1.
But instead, in Google Chrome (tested on 97 and 99 version), all other svg icons inherit border-radius: 25px; for the duration of the animation, although this is not provided by anything.
.wrapper {
display: flex;
will-change: opacity;
overflow: hidden;
border-radius: 25px;
background: #ccc;
}
.wrapper:hover .first {
opacity: 1;
}
.first {
opacity: 0.5;
background: green;
transition: 0.5s color ease-in-out, 0.5s opacity ease-in-out;
}
.second {
mix-blend-mode: multiply;
background: red;
}
.third {
mix-blend-mode: multiply;
background: blue;
}
<div class="wrapper">
<svg width="50" height="50" xmlns="http://www.w3.org/2000/svg" class="first"/>
<svg width="50" height="50" xmlns="http://www.w3.org/2000/svg" class="second"/>
<svg width="50" height="50" xmlns="http://www.w3.org/2000/svg" class="third"/>
</div>
Example on gif:
Deleting will-change: opacity; makes this bug more strange:
Only deleting mix-blend-mode: multiply; resolve this bug:
Why it happend? How to fix it?
This bug does not reproduce on FireFox and Safari.
UPD: Created issue here: https://bugs.chromium.org/p/chromium/issues/detail?id=1304627

Pure CSS gradient circle border

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

I'm having trouble controlling an animated svg border using CSS

I've been trying to animate a svg border, I've gotten as far as this
html {
background: white;
}
div {
position: fixed;
height: 200px;
width: 605px;
position: fixed;
left: 30%
}
.mainNav {
position: fixed;
top: 6;
}
[class="navBorder"] .outline {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
stroke: #7272f8;
stroke-width: 11px;
fill: none;
}
.navBorder .outline {
stroke-dasharray: 2000;
stroke-dashoffset: 1900;
-webkit-transition: 0.5s;
-moz-transition: 0.5s;
-o-transition: 0.5s;
transition: 0.5s;
}
.navBorder:hover .outline {
stroke-dasharray: 1100 0;
stroke-dashoffset: 0;
}
<div>
<a class="navBorder" target="_blank">
<svg height="100%" width="100%" xmlns="http://www.w3.org/2000/svg">
<rect class="outline" height="100%" width="100%" />
</svg>
</a>
</div>
http://codepen.io/lorehill/pen/pEPXar
The problem is I can't seem to get the starting position of the border to be on the top center and then close center bottom.
I'm very confused trying to figure out how to calculate the values I need to set stroke-dasharray and stroke-dashoffset for the starting position in order to get the effect I'm after.
If anyone could explain it like I'm 5 that would be fantastic.
Thank you!
AFAIK, the starting position of the stroke is always the starting point of the rect which is top left for a rect element.
I can't seem to get the starting position of the border to be on the top center and then close center bottom.
I think you'll need two polyline elements for that, although you can use the same class on both.
svg {
height: 100px;
margin: 1em;
}
.outline {
fill: lightblue;
stroke-dasharray: 200;
stroke-dashoffset: 190;
-webkit-transition: 0.5s;
-moz-transition: 0.5s;
-o-transition: 0.5s;
transition: 0.5s;
}
svg:hover .outline {
stroke-dasharray: 200 0;
stroke-dashoffset: 0;
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 100 100">
<polyline class="outline" points="50,0 100,0, 100,100 50,100" style="stroke:#660000; stroke-width: 3;" />
<polyline class="outline" points="50,0 0,0 0,100 50,100" style="stroke:#660000; stroke-width: 3;" />
</svg>
Codepen Demo

Clip-path and SVG rect inside animation

I am trying to make a fancy animation only in CSS. I started with a tutorial on W3 School and wanted to make it better. My idea is to have a square loader turning clockwise while another inside would turn in the opposite direction.
On this link you will see what I'm talking about, the only difference is that I would like the red part to be turning in the opposite direction.
In order to do so I tried adding another div with class name .spinner. Here's my try at it: https://jsfiddle.net/avhjj4ps/
.loader-container {
position: absolute;
left: calc(50% - 75px);
width: 150px;
height: 150px;
padding: 5px;
border: 1px solid red;
top: calc(50% - 75px);
}
img {
width: 200px;
margin: 20px;
/*animation: move 2s alternate infinite linear;*/
}
#myClip, #svg {
position: absolute;
left: 50%;
top: 50%;
}
.loader, .spinner {
position: absolute;
}
.loader {
left: calc(50% - 35px);
top: calc(50% - 35px);
width: 40px;
height: 40px;
border: 15px solid transparent;
border-top: 15px solid none;
/*-webkit-animation: loader 2s linear infinite;
animation: loader 2s linear infinite;*/
}
.spinner {
left: calc(50% - 55.1px);
top: calc(50% - 55.1px);
/*clip-path: url(#myClip);*/
width: 40px;
border-radius: 50%;
height: 40px;
border: 36px solid #f3f3f3;
border-top: 36px solid #5cb85c;
/*-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;*/
}
#-webkit-keyframes loader {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
#keyframes loader {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(-360deg); }
}
#keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(-360deg); }
}
<div class="loader-container">
<div class="loader"></div>
<div class="spinner"></div>
<svg id="svg" width="0" height="0">
<defs>
<clipPath id="myClip">
<rect x="-35" y="-35" width="15" height="70" />
<rect x="20" y="-35" width="15" height="70" />
<rect x="-35" y="-35" width="70" height="15" />
<rect x="-35" y="20" width="70" height="15" />
</clipPath>
</defs>
</svg>
</div>
I am trying to show the green spinner only where there is the square loader. It would be like a mask. In the above snippet (also available here: http://codepen.io/anon/pen/ZOoByA), I'm trying to use the clip-path property.
Can some tell me why clip-path: url(#myClip); doesn't work ? When I comment this line the loader shows completely, however while active it's not showing at all.
You can create your loader in svg with some polygons and then clip the inner green loader away with clipPath.
First, define the gray border as a polygon:
<polygon id="loader" points="0,0 0,70 70,70 70,0 0,0 15,15 55,15 55,55 15,55 15,15" />
As we will reuse this shape (the actual loader and the clip-path shape), we put into the defs tag:
<svg height="0" width="0">
<defs>
<polygon id="loader" points="..." />
</defs>
</svg>
Then we put the clipPath into the same defs tag:
<clipPath id="loaderClipper">
<use xlink:href="#loader" x="15" y="15" />
</clipPath>
The offset of 15 is calculated in the following way: The loader's width is 70, but if it is rotated by 45 degrees, it's width is 70√2 which rounds to 100. The whitespace in the left and in the right is (100 - 70) / 2 = 15.
The svg for the actual used element looks like this:
<svg width="100" height="100" viewbox="0 0 100 100" clip-path="url(#loaderClipper)">
<use xlink:href="#loader" class="loader" x="15" y="15" />
<polygon class="spinner" points="0,0 100,0 50,50" x="30" y="30" />
</svg>
And some css for colors and the animation:
svg {
animation: rotate 2s linear infinite;
transform-origin: 50px 50px;
}
.loader {
fill: #dcdada;
}
.spinner {
fill: #5cb85c;
animation: rotate 1s linear infinite reverse;
transform-origin: 50px 50px;
}
Result fiddle: https://jsfiddle.net/apLepsv3/10/
Successfully tested on both mobile and desktop Firefox and Chrome.
For a CSS-only solution without SVG you need some helper elements:
<div class="loader">
<div class="square"></div>
<div class="cutter">
<div class="spinner">
</div>
</div>
</div>
And then this CSS code:
.square {
width: 40px;
height: 40px;
background: #f3f3f3;
z-index: 1;
}
.cutter {
width: 70px;
height: 70px;
left: -15px;
top: -15px;
overflow: hidden;
}
.spinner {
width: 54px;
border-radius: 50%;
height: 54px;
border: 8px solid transparent;
border-top: 8px solid #5cb85c;
-webkit-animation: spin 1s linear infinite;
animation: spin 1s linear infinite;
margin-left: -15px;
margin-top: -15px;
}
Result: https://jsfiddle.net/avhjj4ps/3/
Disadvantage: Inner square must have a solid background (no gradient or image) if it has to match the parent's / body's background.
You can create the spinner with HTML and CSS and then cut the overflow away by using the clip-path property in combination with a svg <clipPath> element.
Your html structure of the spinner:
<div class="loader">
<div class="spinner">
</div>
</div>
Now position the two elements over each other:
.loader {
width: 40px;
height: 40px;
position: relative;
left: 30px;
top: 30px;
border: 15px solid #dcdada;
border-top: 15px solid none;
-webkit-animation: loader 2s linear infinite;
animation: loader 2s linear infinite;
}
.spinner {
width: 0px;
height: 0px;
position: relative;
left: -30px;
top: -30px;
border: 50px solid transparent;
border-top: 50px solid #5cb85c;
-webkit-animation: spin 1s linear infinite;
animation: spin 1s linear infinite;
}
But there's still that green overflow outside and inside of the gray border. So we need to cut it away with a svg <polygon>.
<svg height="0" width="0">
<defs>
<clipPath id="loaderClipper">
<polygon points="0,0 0,70 70,70 70,0 0,0 15,15 55,15 55,55 15,55 15,15"/>
</clipPath>
</defs>
</svg>
The points define a 70x70 square with a 40x40 square cut off.
Then add the clip-path property that references to the svg <clipPath> element:
.loader {
clip-path: url(#loaderClipper);
}
Fiddle: https://jsfiddle.net/apLepsv3/2/
Disadvantage: Only supported in Firefox, not Chrome

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>

Resources