TweenMax.js transform rotate doesn't work in IE 9 - html

I am trying to animate few Svg elements( Windmill , Door Open) using Tweenmax.js
The Windmill rotation works fine but the door opening animation doesn't work at all in Internet Explorer 9.
Here's my code :
var dooropen = $('#door-open');
var windmill = $('#windmill');
function DoorOpen() {
TweenMax.to(dooropen, 3, {
rotationY: 180,
transformOrigin: "0% 0%"
});
}
function rotateFan() {
TweenMax.to(windmill, 40, {
rotation: 360,
transformOrigin: "51% 64%"
});
}
$('.run').click(function() {
DoorOpen();
rotateFan();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.0/TweenMax.min.js"></script>
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" width="887.996" height="567.947">
<path fill="#231F20" d="M89.917 99.025h31.58v468.42h-31.58z" />
<g id="windmill" fill="#FFF" stroke="#231F20" stroke-miterlimit="10">
<path d="M105.706 116.57s-50.877-56.142 0-115.79c0 0 47.368 57.894 0 115.79zM105.707 116.57s73.15-19.732 103.088 52.726c0 0-73.114 15.808-103.088-52.727z" />
<path d="M105.707 116.57S76.25 186.372.71 165.385c0 0 32.528-67.36 104.997-48.817z" />
</g>
<path fill="#FFF" stroke="#231F20" stroke-miterlimit="10" d="M485.496 162.376h402v402h-402z" />
<path fill="#231F20" d="M567.496 266.376h246v298h-246z" />
<g id="door-open">
<path fill="#B51543" d="M567.496 266.376h246v298h-246z" />
<path fill="#841C3F" d="M791.496 419.376c0 2.762-2.238 5-5 5h-26c-2.762 0-5-2.238-5-5v-26c0-2.762 2.238-5 5-5h26c2.762 0 5 2.238 5 5v26z" />
</g>
</svg>
Run

It looks like your issue is that your mixing string values with a numerical ones. The default transform-origin are percentages 50% 50% 0. So also it is best to stick with percentage based or numerical values for transform-origin in GSAP.
A two-value syntax is preferred like #Tahir Ahmed commented above. Even though the third parameter is accepted. The third value will automatically default to zero anyway, since that is the default value from the spec 0. And is only necessary when using 3D transforms, since it aligns itself to the z-axis. Which you cant use in SVG, since SVG does not support CSS 3D Transforms.
Have a look at the transform-origin spec:
https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin
Try this:
var dooropen = $('#door-open');
var windmill = $('#windmill');
function DoorOpen() {
TweenMax.to(dooropen, 3, {
rotationY: 180,
transformOrigin: "0% 0%" /* top and left, omit 0 since is the default */
});
}
function rotateFan() {
TweenMax.to(windmill, 40, {
rotation: 360,
transformOrigin: "51% 64%"
});
}
$('.run').click(function() {
DoorOpen();
rotateFan();
});
tranform-origin keyword equivalents:
left = 0%
center = 50%
right = 100%
top = 0%
bottom = 100%
Also check out the CSSPlugin Docs and the use of SVG transformOrigin
http://greensock.com/docs/#/HTML5/GSAP/Plugins/CSSPlugin/
GSAP svgOrigin property, taken from CSSPlugin Docs:
[Only for SVG elements] Works exactly like transformOrigin but it uses the SVG's global coordinate space instead of the element's local coordinate space. This can be very useful if, for example, you want to make a bunch of SVG elements rotate around a common point. You can either define an svgOrigin or a transformOrigin, not both (for obvious reasons).
See codepen example here of svgOrigin.
So you can do TweenLite.to(svgElement, 1, {rotation:270, svgOrigin:"250 100"}) if you'd like to rotate svgElement as though its origin is at x:250, y:100 in the SVG canvas's global coordinates. Units are not required. It also records the value in a data-svg-origin attribute so that it can be parsed back in. svgOrigin doesn't accommodate percentage-based values.

If you're trying to animate the rotationY of an SVG element, that's the problem - the SVG spec does NOT allow 3D at all. That's not a GSAP problem or limitation - it's literally the SVG spec. Some webkit browsers do technically recognize 3D CSS transforms, but that's non-standard and it definitely isn't supported in IE because IE ignores all CSS transforms on SVG elements. Transforms are supposed to be applied via the "transform" attribute which, again, does not support 3D.
My advice: don't do 3D in SVG. I wish I had better news for you.

Related

SVG image not reacting to CSS colour changes [duplicate]

html
<img src="logo.svg" alt="Logo" class="logo-img">
css
.logo-img path {
fill: #000;
}
The above svg loads and is natively fill: #fff but when I use the above css to try change it to black it doesn't change, this is my first time playing with SVG and I am not sure why it's not working.
You could set your SVG as a mask. That way setting a background-color would act as your fill color.
HTML
<div class="logo"></div>
CSS
.logo {
background-color: red;
-webkit-mask: url(logo.svg) no-repeat center;
mask: url(logo.svg) no-repeat center;
}
JSFiddle: https://jsfiddle.net/KuhlTime/2j8exgcb/
MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/mask
Please check whether your browser supports this feature:
https://caniuse.com/#search=mask
If your goal is just to change the color of the logo, and you don't necessarily NEED to use CSS, then don't use javascript or jquery as was suggested by some previous answers.
To precisely answer the original question, just:
Open your logo.svg in a text editor.
look for fill: #fff and replace it with fill: #000
For example, your logo.svg might look like this when opened in a text editor:
<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" fill="#fff"/>
</svg>
... just change the fill and save.
Try pure CSS:
.logo-img {
/* to black */
filter: invert(1);
/* or to blue */
filter: invert(0.5) sepia(1) saturate(5) hue-rotate(175deg);
}
more info in this article https://blog.union.io/code/2017/08/10/img-svg-fill/
If you want a dynamic color, do not want to use javascript and do not want an inline SVG, use a CSS variable. Works in Chrome, Firefox and Safari. edit: and Edge
<svg>
<use href="logo.svg" style="--color_fill: #000;"></use>
</svg>
In your SVG, replace any instances of style="fill: #000" with style="fill: var(--color_fill)".
You will first have to inject the SVG into the HTML DOM.
There is an open source library called SVGInject that does this for you. It uses the onload attribute to trigger the injection.
Here is a minimal example using SVGInject:
<html>
<head>
<script src="svg-inject.min.js"></script>
</head>
<body>
<img src="image.svg" onload="SVGInject(this)" />
</body>
</html>
After the image is loaded the onload="SVGInject(this) will trigger the injection and the <img> element will be replaced by the contents of the SVG file provided in the src attribute.
It solves several issues with SVG injection:
SVGs can be hidden until injection has finished. This is important if a style is already applied during load time, which would otherwise cause a brief "unstyled content flash".
The <img> elements inject themselves automatically. If you add SVGs dynamically, you don't have to worry about calling the injection function again.
A random string is added to each ID in the SVG to avoid having the same ID multiple times in the document if an SVG is injected more than once.
SVGInject is plain Javascript and works with all browsers that support SVG.
Disclaimer: I am the co-author of SVGInject
Edit your SVG file, add fill="currentColor" to svg tag and make sure to remove any other fill property from the file.
For example:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 139.435269383854" id="img" fill="currentColor">...
</svg>
Note that currentColor is a keyword (not a fixed color in use).
After that, you can change the color using CSS, by setting the color property of the element or from it's parent.
Example:
.div-with-svg-inside {
color: red;
}
I forgot to say, you must insert the SVG this way:
<svg>
<use xlink:href='/assets/file.svg#img' href="/assets/file.svg#img"></use>
</svg>
if image is coming from some variable then
<svg>
<use [attr.xlink:href]="somevariable + '#img'" [attr.href]="somevariable + '#img'"></use>
</svg>
Note that `#img` is the id of the `svg` tag inside svg file. Also note `xlink:href` has been deprecated instead you should use `href` or use can use both to support older browser versions.
Another way of doing it: [https://css-tricks.com/cascading-svg-fill-color/][1]
[1]: https://css-tricks.com/cascading-svg-fill-color/
I suggest to select your color , and go to this pen
https://codepen.io/sosuke/pen/Pjoqqp
it will convert HEX to css filter eg:#64D7D6
equal
filter: invert(88%) sepia(21%) saturate(935%) hue-rotate(123deg) brightness(85%) contrast(97%);
the final snippet
.filterit{
width:270px;
filter: invert(88%) sepia(21%) saturate(935%) hue-rotate(123deg) brightness(85%) contrast(97%);
}
<img src="https://www.flaticon.com/svg/static/icons/svg/1389/1389029.svg"
class="filterit
/>
This answer is based on answer https://stackoverflow.com/a/24933495/3890888 but with a plain JavaScript version of the script used there.
You need to make the SVG to be an inline SVG. You can make use of this script, by adding a class svg to the image:
/*
* Replace all SVG images with inline SVG
*/
document.querySelectorAll('img.svg').forEach(function(img){
var imgID = img.id;
var imgClass = img.className;
var imgURL = img.src;
fetch(imgURL).then(function(response) {
return response.text();
}).then(function(text){
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(text, "text/xml");
// Get the SVG tag, ignore the rest
var svg = xmlDoc.getElementsByTagName('svg')[0];
// Add replaced image's ID to the new SVG
if(typeof imgID !== 'undefined') {
svg.setAttribute('id', imgID);
}
// Add replaced image's classes to the new SVG
if(typeof imgClass !== 'undefined') {
svg.setAttribute('class', imgClass+' replaced-svg');
}
// Remove any invalid XML tags as per http://validator.w3.org
svg.removeAttribute('xmlns:a');
// Check if the viewport is set, if the viewport is not set the SVG wont't scale.
if(!svg.getAttribute('viewBox') && svg.getAttribute('height') && svg.getAttribute('width')) {
svg.setAttribute('viewBox', '0 0 ' + svg.getAttribute('height') + ' ' + svg.getAttribute('width'))
}
// Replace image with new SVG
img.parentNode.replaceChild(svg, img);
});
});
And then, now if you do:
.logo-img path {
fill: #000;
}
Or may be:
.logo-img path {
background-color: #000;
}
JSFiddle: http://jsfiddle.net/erxu0dzz/1/
Use filters to transform to any color.
I recently found this solution, and hope somebody might be able to use it.
Since the solution uses filters, it can be used with any type of image. Not just svg.
If you have a single-color image that you just want to change the color of, you can do this with the help of some filters. It works on multicolor images as well of course, but you can't target a specific color. Only the whole image.
The filters came from the script proposed in How to transform black into any given color using only CSS filters
If you want to change white to any color, you can adjust the invert value in each filter.
.startAsBlack{
display: inline-block;
width: 50px;
height: 50px;
background: black;
}
.black-green{
filter: invert(43%) sepia(96%) saturate(1237%) hue-rotate(88deg) brightness(128%) contrast(119%);
}
.black-red{
filter: invert(37%) sepia(93%) saturate(7471%) hue-rotate(356deg) brightness(91%) contrast(135%);
}
.black-blue{
filter: invert(12%) sepia(83%) saturate(5841%) hue-rotate(244deg) brightness(87%) contrast(153%);
}
.black-purple{
filter: invert(18%) sepia(98%) saturate(2657%) hue-rotate(289deg) brightness(121%) contrast(140%);
}
Black to any color: <br/>
<div class="startAsBlack black-green"></div>
<div class="startAsBlack black-red"></div>
<div class="startAsBlack black-blue"></div>
<div class="startAsBlack black-purple"></div>
Why not create a webfont with your svg image or images, import the webfont in the css and then just change the color of the glyph using the css color attribute?
No javascript needed
Simple..
You can use this code:
<svg class="logo">
<use xlink:href="../../static/icons/logo.svg#Capa_1"></use>
</svg>
First specify the path of svg and then write it's ID, In this case "Capa_1". You can get the ID of svg by opening it in any editor.
In css:
.logo {
fill: red;
}
The answer from #Praveen is solid.
I couldn't get it to respond in my work, so I made a jquery hover function for it.
CSS
.svg path {
transition:0.3s all !important;
}
JS / JQuery
// code from above wrapped into a function
replaceSVG();
// hover function
// hover over an element, and find the SVG that you want to change
$('.element').hover(function() {
var el = $(this);
var svg = el.find('svg path');
svg.attr('fill', '#CCC');
}, function() {
var el = $(this);
var svg = el.find('svg path');
svg.attr('fill', '#3A3A3A');
});
If you are just switching the image between the real color and the black-and-white, you can set one selector as:
{filter:none;}
and another as:
{filter:grayscale(100%);}
To expand on #gringo answer, the Javascript method described in other answers works, but requires the user to download unnecessary image files, and IMO, it bloats your code.
I think a better approach would be to to migrate all 1-color vector graphics to a webfont file. I've used Fort Awesome in the past, and it works great to combine your custom icons/images in SVG format, along with any 3rd party icons you may be using (Font Awesome, Bootstrap icons, etc.) into a single webfont file the user has to download. You can also customize it, so you only include the 3rd party icons you're using. This reduces the number of requests the page has to make, and you're overall page weight, especially if you're already including any 3rd party icons libraries.
If you prefer a more dev oriented option, you could Google "npm svg webfont", and use one of the node modules that's most appropriate for your environment.
Once, you've done either of those two options, then you could easily change the color via CSS, and most likely, you've sped up your site in the process.
Since SVG is basically code, you need just contents. I used PHP to obtain content, but you can use whatever you want.
<?php
$content = file_get_contents($pathToSVG);
?>
Then, I've printed content "as is" inside a div container
<div class="fill-class"><?php echo $content;?></div>
To finnaly set rule to container's SVG childs on CSS
.fill-class > svg {
fill: orange;
}
I got this results with a material icon SVG:
Mozilla Firefox 59.0.2 (64-bit) Linux
Google Chrome66.0.3359.181 (Build oficial) (64 bits) Linux
Opera 53.0.2907.37 Linux
The main problem in your case is that you are importing the svg from an <img> tag which will hide the SVG structure.
You need to use the <svg> tag in conjunction with the <use> to get the desired effect. To make it work, you need to give an id to the path you want to use in the SVG file <path id='myName'...> to then be able to retrieve them from the <use xlink:href="#myName"/> tag.
Try the snipped below.
.icon {
display: inline-block;
width: 2em;
height: 2em;
transition: .5s;
fill: currentColor;
stroke-width: 5;
}
.icon:hover {
fill: rgba(255,255,255,0);
stroke: black;
stroke-width: 2;
}
.red {
color: red;
}
.blue {
color: blue;
}
<svg width="0" height="0">
<defs>
<path id="home" d="M100 59.375l-18.75-18.75v-28.125h-12.5v15.625l-18.75-18.75-50 50v3.125h12.5v31.25h31.25v-18.75h12.5v18.75h31.25v-31.25h12.5z"/>
</svg>
<span class="icon red">
<svg viewbox="0 0 100 100">
<use xlink:href="#home"/>
</svg>
</span>
<span class="icon blue">
<svg viewbox="0 0 100 100">
<use xlink:href="#home"/>
</svg>
</span>
Note that you can put any URL before the fragment # if you want to load the SVG from an external source (and not embed it into your HTML). Also, usually you do not specify the fill into the CSS. It's better to consider using fill:"currentColor" within the SVG itself. The corresponding element's CSS color value will then be used in place.
This might be helpful for people using PHP in combination with .svg images that they want to manipulate with CSS.
You can't overwrite properties inside a img tag with CSS. But when the svg source code is embedded in the HTML you surely can. I like to resolve this issue with a require_once function where I include a .svg.php file. It's like importing an image but you can still overwrite styles with CSS!
First include the svg file:
<?php require_once( '/assets/images/my-icon.svg.php' ); ?>
And it includes this icon for example:
<svg xmlns="http://www.w3.org/2000/svg" width="20.666" height="59.084" viewBox="0 0 20.666 59.084"><g transform="translate(-639.749 -3139)"><path d="M648.536,3173.876c0-2.875-1.725-3.8-3.471-3.8-1.683,0-3.49.9-3.49,3.8,0,3,1.786,3.8,3.49,3.8C646.811,3177.676,648.536,3176.769,648.536,3173.876Zm-3.471,2.341c-.883,0-1.437-.513-1.437-2.341,0-1.971.615-2.381,1.437-2.381.862,0,1.438.349,1.438,2.381,0,1.907-.616,2.339-1.438,2.339Z" fill="#142312"/><path d="M653.471,3170.076a1.565,1.565,0,0,0-1.416.9l-6.558,13.888h1.2a1.565,1.565,0,0,0,1.416-.9l6.559-13.887Z" fill="#142312"/><path d="M655.107,3177.263c-1.684,0-3.471.9-3.471,3.8,0,3,1.766,3.8,3.471,3.8,1.745,0,3.49-.9,3.49-3.8C658.6,3178.186,656.851,3177.263,655.107,3177.263Zm0,6.139c-.884,0-1.438-.514-1.438-2.34,0-1.972.617-2.381,1.438-2.381.862,0,1.437.349,1.437,2.381,0,1.909-.616,2.34-1.437,2.34Z" fill="#142312"/><path d="M656.263,3159.023l-1.49-14.063a1.35,1.35,0,0,0,.329-.293,1.319,1.319,0,0,0,.268-1.123l-.753-3.49a1.328,1.328,0,0,0-1.306-1.054h-6.448a1.336,1.336,0,0,0-1.311,1.068l-.71,3.493a1.344,1.344,0,0,0,.276,1.112,1.532,1.532,0,0,0,.283.262l-1.489,14.087c-1.7,1.727-4.153,4.871-4.153,8.638v28.924a1.339,1.339,0,0,0,1.168,1.49,1.357,1.357,0,0,0,.17.01h17.981a1.366,1.366,0,0,0,1.337-1.366v-29.059C660.414,3163.893,657.963,3160.749,656.263,3159.023Zm-8.307-17.349h4.274l.176.815H647.79Zm9.785,43.634v10.1H642.434v-17.253a4.728,4.728,0,0,1-2.028-4.284,4.661,4.661,0,0,1,2.028-4.215v-2c0-3.162,2.581-5.986,3.687-7.059a1.356,1.356,0,0,0,.4-.819l1.542-14.614H652.1l1.545,14.618a1.362,1.362,0,0,0,.4.819c1.109,1.072,3.688,3.9,3.688,7.059v9.153a5.457,5.457,0,0,1,0,8.5Z" fill="#142312"/></g></svg>
Now we can easily change the fill color like this with CSS:
svg path {
fill: blue;
}
I first tried to solve this problem with file_get_contents() but the solution above is much faster.
open the svg icon in your code editor and add a class after the path tag:
<path class'colorToChange' ...
You can add class to svg and change the color like this:
codepen
Know this is an old question but recently we came across the same issue, and we solved it from the server side. This is a php specific answer but I am positive that other envs have something similar.
instead of using the img tag you render the svg as svg from the get-go.
public static function print_svg($file){
$iconfile = new \DOMDocument();
$iconfile->load($file);
$tag = $iconfile->saveHTML($iconfile->getElementsByTagName('svg')[0]);
return $tag;
}
now when you render the file you will get complete inline svg
For me, my svgs looked different when having them as img and svg. So my solution converts the img to csv, changes styles internally and back to img (although it requires a bit more work), I believe "blob" also has better compatibility than the upvoted answer using "mask".
let img = yourimgs[0];
if (img.src.includes(".svg")) {
var ajax = new XMLHttpRequest();
ajax.open("GET", img.src, true);
ajax.send();
ajax.onload = function (e) {
svg = e.target.responseText;
svgText = "";
//change your svg-string as youd like, for example
// replacing the hex color between "{fill:" and ";"
idx = svg.indexOf("{fill:");
substr = svg.substr(idx + 6);
str1 = svg.substr(0, idx + 6);
str2 = substr.substr(substr.indexOf(";"));
svgText = str1 + "#ff0000" + str2;
let blob = new Blob([svgText], { type: "image/svg+xml" });
let url = URL.createObjectURL(blob);
let image = document.createElement("img");
image.src = url;
image.addEventListener("load", () => URL.revokeObjectURL(url), {
once: true,
});
img.replaceWith(image);
};
}
Simple JS
Use following short function ImgToSvg which swap img to svg (including class list)
<img src="logo.svg" onload="ImgToSvg(this)" class="logo-img"/>
const ImgToSvg= async (img) => {
const s = document.createElement('div');
s.innerHTML = await (await fetch(img.src)).text();
s.firstChild.classList = img.classList;
img.replaceWith(s.firstChild)
}
.logo-img {
fill: yellow;
}
<img onload="ImgToSvg(this)" class="logo-img" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSIyIiB5PSIyIiB3aWR0aD0iMjk2IiBoZWlnaHQ9IjI5NiIgc3R5bGU9InN0cm9rZTojNTU1NTU1O3N0cm9rZS13aWR0aDoyIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtc2l6ZT0iMTgiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGFsaWdubWVudC1iYXNlbGluZT0ibWlkZGxlIiBmb250LWZhbWlseT0ibW9ub3NwYWNlLCBzYW5zLXNlcmlmIiBmaWxsPSIjNTU1NTU1Ij4zMDAmIzIxNTszMDA8L3RleHQ+PC9zdmc+" />
<!-- in this snippet I use dataURI in img src to avoid CORS problems witch reading svg data from external source by js -->
This is improvement of Waruyama answer by providing short js function
I wanted to change specific paths and/or colors only and even colorize paths differently. Also, in my case some CSS was applied to the IMG-tag directly, hence I wanted to let it be original IMG-element to not mess around with positioning and alignment.
Thanks to inspiration from this answer: https://stackoverflow.com/a/43015413/1444589, this is what worked for me:
let img = document.querySelector('img[class^="YourClassName"]');
let imgURL = img.src;
fetch(imgURL)
.then(response => response.text())
.then(text => {
let parser = new DOMParser();
let xmlDoc = parser.parseFromString(text, 'text/xml');
let svg = xmlDoc.getElementsByTagName('svg')[0];
let paths = xmlDoc.getElementsByTagName('path');
// access individual path elements directly
let leftShape = paths[0];
leftShape.setAttribute('fill', '#4F4F4F');
// or find specific color
const pathsArr = Array.from(paths);
let skirtShape = pathsArr.find(path => path.getAttribute('fill') === '#F038A5');
skirtShape.setAttribute('fill', '#0078D6');
// Replace old SVG with colorized SVG
// positioning and alignment is left untouched
let base64Str = btoa(new XMLSerializer().serializeToString(svg));
img.src = 'data:image/svg+xml;base64, ' + base64Str;
});
Why not just using CSS's filter property to manipulate the color on :hover or whatever other state? I found it works over SVG images into img tags. At least, it's almost fully supported in 2020. It seams to me the simpliest solution. The only caveat is having to tweak the filter properties in order to find the target color. But you have also this very useful tool.
for that matters you have to use your SVG as an inline HTML.
say here's your logo.svg code (when you open it on textEditor):
Logo.SVG
<svg width="139" height="100" xmlns="http://www.w3.org/2000/svg">
<!-- Note that I've Added Class Attribute 'logo-img' Here -->
<g transform="translate(-22 -45)" fill="none" fill-rule="evenodd">
<path
d="M158.023 48.118a7.625 7.625 0 01-.266 10.78l-88.11 83.875a7.625 7.625 0 01-10.995-.5l-33.89-38.712a7.625 7.625 0 0111.475-10.045l28.653 32.73 82.353-78.394a7.625 7.625 0 0110.78.266z"
fill="#00000" />
</g>
</svg>
add your desired Class/ID to it (i've added 'logo-img'):
Edited Svg
<svg class="logo-img" width="139" height="100" xmlns="http://www.w3.org/2000/svg">
<!-- Note that I've Added Class Attribute 'logo-img' Here -->
...
</svg>
Now apply Your Css Rules:
CSS
.logo-img path {
fill: #000;
}
Pro
With this way you can animate on user's actions (hover, selected,...)
Con
Your HTML File would be a mess.
Heres a Stack Snippet
<style>
body {
display: flex;
justify-content: center;
}
.logo-img path {
transition: .5s all linear;
}
.logo-img path {
fill: coral;
}
.logo-img:hover path{
fill: darkblue;
}
</style>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<svg class="logo-img" width="139" height="100" xmlns="http://www.w3.org/2000/svg">
<!-- Note that I've Added Class Attribute 'logo-img' Here -->
<g transform="translate(-22 -45)" fill="none" fill-rule="evenodd">
<path
d="M158.023 48.118a7.625 7.625 0 01-.266 10.78l-88.11 83.875a7.625 7.625 0 01-10.995-.5l-33.89-38.712a7.625 7.625 0 0111.475-10.045l28.653 32.73 82.353-78.394a7.625 7.625 0 0110.78.266z"
fill="#00000" />
</g>
</svg>
</body>
</html>
If your shape(s) are always one solid color and you have more than a couple, you can use Fontello and make a custom icon font with a whole series of your own custom SVG shapes. Then you can set/animate the size and color of all of them with CSS alone.
For all the possible use cases for this question, this is an essential paradigm to consider. I've used it in many projects. In any case, if you haven't heard of Fontello, you need to find out about it. If you know of a similar solution that is better, I would love to know.
Possible downfalls:
Icon/shape fonts are known to mess with screen readers, so that may take some handling.
Fontello can be finicky with importing shapes, and it may take some trial and error with authoring and exporting them.
Avoid any and all grouping, and use only single non-nested compound shapes.
Directly to svg fill css will not work you can use as below
<style>
svg path {
fill: red;
}
</style>
<svg xmlns="http://www.w3.org/2000/svg" width="20.666" height="59.084" viewBox="0 0 20.666 59.084"><g transform="translate(-639.749 -3139)"><path d="M648.536,3173.876c0-2.875-1.725-3.8-3.471-3.8-1.683,0-3.49.9-3.49,3.8,0,3,1.786,3.8,3.49,3.8C646.811,3177.676,648.536,3176.769,648.536,3173.876Zm-3.471,2.341c-.883,0-1.437-.513-1.437-2.341,0-1.971.615-2.381,1.437-2.381.862,0,1.438.349,1.438,2.381,0,1.907-.616,2.339-1.438,2.339Z" fill="#142312"/><path d="M653.471,3170.076a1.565,1.565,0,0,0-1.416.9l-6.558,13.888h1.2a1.565,1.565,0,0,0,1.416-.9l6.559-13.887Z" fill="#142312"/><path d="M655.107,3177.263c-1.684,0-3.471.9-3.471,3.8,0,3,1.766,3.8,3.471,3.8,1.745,0,3.49-.9,3.49-3.8C658.6,3178.186,656.851,3177.263,655.107,3177.263Zm0,6.139c-.884,0-1.438-.514-1.438-2.34,0-1.972.617-2.381,1.438-2.381.862,0,1.437.349,1.437,2.381,0,1.909-.616,2.34-1.437,2.34Z" fill="#142312"/><path d="M656.263,3159.023l-1.49-14.063a1.35,1.35,0,0,0,.329-.293,1.319,1.319,0,0,0,.268-1.123l-.753-3.49a1.328,1.328,0,0,0-1.306-1.054h-6.448a1.336,1.336,0,0,0-1.311,1.068l-.71,3.493a1.344,1.344,0,0,0,.276,1.112,1.532,1.532,0,0,0,.283.262l-1.489,14.087c-1.7,1.727-4.153,4.871-4.153,8.638v28.924a1.339,1.339,0,0,0,1.168,1.49,1.357,1.357,0,0,0,.17.01h17.981a1.366,1.366,0,0,0,1.337-1.366v-29.059C660.414,3163.893,657.963,3160.749,656.263,3159.023Zm-8.307-17.349h4.274l.176.815H647.79Zm9.785,43.634v10.1H642.434v-17.253a4.728,4.728,0,0,1-2.028-4.284,4.661,4.661,0,0,1,2.028-4.215v-2c0-3.162,2.581-5.986,3.687-7.059a1.356,1.356,0,0,0,.4-.819l1.542-14.614H652.1l1.545,14.618a1.362,1.362,0,0,0,.4.819c1.109,1.072,3.688,3.9,3.688,7.059v9.153a5.457,5.457,0,0,1,0,8.5Z" fill="#142312"/></g></svg>
This worked for me

rect's stroke doesn't follow the rect's radius [duplicate]

Currently building a browser-based SVG application. Within this app, various shapes can be styled and positioned by the user, including rectangles.
When I apply a stroke-width to an SVG rect element of say 1px, the stroke is applied to the rect’s offset and inset in different ways by different browsers. This is proving to be troublesome, especially when I try to calculate the outer width and visual position of a rectangle and position it next to other elements.
For example:
Firefox adds 1px inset (bottom and left), and 1px offset (top and right)
Chrome adds 1px inset (top and left), and 1px offset (bottom and right)
My only solution so far would be to draw the actual borders myself (probably with the path tool) and position the borders behind the stroked element. But this solution is an unpleasant workaround, and I’d prefer not to go down this road if possible.
So my question is, can you control how an SVG’s stroke-width is drawn on elements?
No, you cannot specify whether the stroke is drawn inside or outside an element. I made a proposal to the SVG working group for this functionality in 2003, but it received no support (or discussion).
As I noted in the proposal,
you can achieve the same visual result as "inside" by doubling your stroke width and then using a clipping path to clip the object to itself, and
you can achieve the same visual result as 'outside' by doubling the stroke width and then overlaying a no-stroke copy of the object on top of itself.
Edit: This answer may be wrong in the future. It should be possible to achieve these results using SVG Vector Effects, by combining veStrokePath with veIntersect (for 'inside') or with veExclude (for 'outside). However, Vector Effects are still a working draft module with no implementations that I can yet find.
Edit 2: The SVG 2 draft specification includes a stroke-alignment property (with center|inside|outside possible values). This property may make it into UAs eventually.
Edit 3: Amusingly and dissapointingly, the SVG working group has removed stroke-alignment from SVG 2. You can see some of the concerns described after the prose here.
UPDATE: The stroke-alignment attribute was on April 1st, 2015 moved to a completely new spec called SVG Strokes.
As of the SVG 2.0 Editor’s Draft of February 26th, 2015 (and possibly since February 13th), the stroke-alignment property is present with the values inner, center (default) and outer.
It seems to work the same way as the stroke-location property proposed by #Phrogz and the later stroke-position suggestion. This property has been planned since at least 2011, but apart from an annotation that said
SVG 2 shall include a way to specify stroke position
, it has never been detailed in the spec as it was deferred - until now, it seems.
No browser support this property, or, as far as I know, any of the new SVG 2 features, yet, but hopefully they will soon as the spec matures. This has been a property I personally have been urging to have, and I'm really happy that it's finally there in the spec.
There seems to be some issues as to how to the property should behave on open paths as well as loops. These issues will, most probably, prolong implementations across browsers. However, I will update this answer with new information as browsers begin to support this property.
I found an easy way, which has a few restrictions, but worked for me:
define the shape in defs
define a clip path referencing the shape
use it and double the stroke with as the outside is clipped
Here a working example:
<svg width="240" height="240" viewBox="0 0 1024 1024">
<defs>
<path id="ld" d="M256,0 L0,512 L384,512 L128,1024 L1024,384 L640,384 L896,0 L256,0 Z"/>
<clipPath id="clip">
<use xlink:href="#ld"/>
</clipPath>
</defs>
<g>
<use xlink:href="#ld" stroke="#0081C6" stroke-width="160" fill="#00D2B8" clip-path="url(#clip)"/>
</g>
</svg>
You can use CSS to style the order of stroke and fills. That is, stroke first and then fill second, and get the desired effect.
MDN on paint-order: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/paint-order
CSS code:
paint-order: stroke;
Here's a function that will calculate how many pixels you need to add - using the given stroke - to the top, right, bottom and left, all based on the browser:
var getStrokeOffsets = function(stroke){
var strokeFloor = Math.floor(stroke / 2), // max offset
strokeCeil = Math.ceil(stroke / 2); // min offset
if($.browser.mozilla){ // Mozilla offsets
return {
bottom: strokeFloor,
left: strokeFloor,
top: strokeCeil,
right: strokeCeil
};
}else if($.browser.webkit){ // WebKit offsets
return {
bottom: strokeCeil,
left: strokeFloor,
top: strokeFloor,
right: strokeCeil
};
}else{ // default offsets
return {
bottom: strokeCeil,
left: strokeCeil,
top: strokeCeil,
right: strokeCeil
};
}
};
As people above have noted you'll either have to recalculate an offset to the stroke's path coordinates or double its width and then mask one side or the other, because not only does SVG not natively support Illustrator's stroke alignment, but PostScript doesn't either.
The specification for strokes in Adobe's PostScript Manual 2nd edition states:
"4.5.1 Stroking:
The stroke operator draws a line of some thickness along the current path. For each straight or curved segment in the path, stroke draws a line that is centered on the segment with sides parallel to the segment." (emphasis theirs)
The rest of the specification has no attributes for offsetting the line's position. When Illustrator lets you align inside or outside, it's recalculating the actual path's offset (because it's still computationally cheaper than overprinting then masking). The path coordinates in the .ai document are reference, not what gets rastered or exported to a final format.
Because Inkscape's native format is spec SVG, it can't offer a feature the spec lacks.
Here is a work around for inner bordered rect using symbol and use.
Example: https://jsbin.com/yopemiwame/edit?html,output
SVG:
<svg>
<symbol id="inner-border-rect">
<rect class="inner-border" width="100%" height="100%" style="fill:rgb(0,255,255);stroke-width:10;stroke:rgb(0,0,0)">
</symbol>
...
<use xlink:href="#inner-border-rect" x="?" y="?" width="?" height="?">
</svg>
Note: Make sure to replace the ? in use with real values.
Background: The reason why this works is because symbol establishes a new viewport by replacing symbol with svg and creating an element in the shadow DOM. This svg of the shadow DOM is then linked into your current SVG element. Note that svgs can be nested and every svg creates a new viewport, which clips everything that overlaps, including the overlapping border. For a much more detailed overview of whats going on read this fantastic article by Sara Soueidan.
I don’t know how helpful will that be but in my case I just created another circle with border only and placed it “inside” the other shape.
This worked for me:
.btn {
border: 1px solid black;
box-shadow: inset 0 0 0 1px black;
}
A (dirty) possible solution is by using patterns,
here is an example with an inside stroked triangle :
https://jsfiddle.net/qr3p7php/5/
<style>
#triangle1{
fill: #0F0;
fill-opacity: 0.3;
stroke: #000;
stroke-opacity: 0.5;
stroke-width: 20;
}
#triangle2{
stroke: #f00;
stroke-opacity: 1;
stroke-width: 1;
}
</style>
<svg height="210" width="400" >
<pattern id="fagl" patternUnits="objectBoundingBox" width="2" height="1" x="-50%">
<path id="triangle1" d="M150 0 L75 200 L225 200 Z">
</pattern>
<path id="triangle2" d="M150 0 L75 200 L225 200 Z" fill="url(#fagl)"/>
</svg>
The solution from Xavier Ho of doubling the width of the stroke and changing the paint-order is brilliant, although only works if the fill is a solid color, with no transparency.
I have developed other approach, more complicated but works for any fill. It also works in ellipses or paths (with the later there are some corner cases with strange behaviour, for example open paths that crosses theirselves, but not much).
The trick is to display the shape in two layers. One without stroke (only fill), and another one only with stroke at double width (transparent fill) and passed through a mask that shows the whole shape, but hides the original shape without stroke.
<svg width="240" height="240" viewBox="0 0 1024 1024">
<defs>
<path id="ld" d="M256,0 L0,512 L384,512 L128,1024 L1024,384 L640,384 L896,0 L256,0 Z"/>
<mask id="mask">
<use xlink:href="#ld" stroke="#FFFFFF" stroke-width="160" fill="#FFFFFF"/>
<use xlink:href="#ld" fill="#000000"/>
</mask>
</defs>
<g>
<use xlink:href="#ld" fill="#00D2B8"/>
<use xlink:href="#ld" stroke="#0081C6" stroke-width="160" fill="red" mask="url(#mask)"/>
</g>
</svg>
The easiest way I found is to add clip-path into circle
Add clip-path="circle()"
<circle id="circle" clip-path="circle()" cx="100" cy="100" r="100" fill="none" stroke="currentColor" stroke-width="5" />
Then the stroke-width="5" will magically become inner 5px stroke with absolute 100px radius.
Update 2023: The current draft renamed the attribute to stroke-align
Browser Support 2023:
See caniuse
This CSS property is not supported in any modern browser, nor are
there any known plans to support it.
Polyfill-like helper function
Based on the previous approaches to combine paint-order, mask and clip-path.
(As suggested by #Xavier Ho
#Jorg Janke)
//emulateStrokeAlign();
function emulateStrokeAlign() {
let supportsSvgStrokeAlign = CSS.supports("stroke-align", "outer") ?
true :
CSS.supports("stroke-alignment", "outer") ?
true :
false;
console.log("supportsSvgStrokeAlign", supportsSvgStrokeAlign);
if (!supportsSvgStrokeAlign) {
let ns = "http://www.w3.org/2000/svg";
let strokeAlignmentEls = document.querySelectorAll(
"*[stroke-alignment], *[stroke-align]"
);
strokeAlignmentEls.forEach((el, s) => {
let svg = el.closest("svg");
// set auto ids to prevent non-unique mask ids
let svgID = svg.id ? svg.id : "svg_" + s;
svg.id = svgID;
//create <defs> if not previously appended
let defs = svg.querySelector("defs");
if (!defs) {
defs = document.createElementNS(ns, "defs");
svg.insertBefore(defs, svg.children[0]);
}
let style = window.getComputedStyle(el);
let strokeWidth = parseFloat(style.strokeWidth);
let strokeAlignment = el.getAttribute("stroke-alignment") ?
el.getAttribute("stroke-alignment") :
el.getAttribute("stroke-align");
el.removeAttribute("stroke-align");
el.removeAttribute("stroke-alignment");
el.setAttribute("data-stroke-align", strokeAlignment);
let maskClipId = `mask-${svgID}-${s}`;
if (strokeAlignment === "outer") {
// create mask
let mask = document.createElementNS(ns, "mask");
mask.id = maskClipId;
let maskEl = el.cloneNode();
mask.appendChild(maskEl);
defs.appendChild(mask);
maskEl.setAttribute("fill", "#000");
mask.setAttribute("maskUnits", "userSpaceOnUse");
maskEl.setAttribute("stroke", "#fff");
maskEl.removeAttribute("stroke-opacity");
maskEl.removeAttribute("id");
maskEl.setAttribute("paint-order", "stroke");
maskEl.style.strokeWidth = strokeWidth * 2;
// clone stroke
let cloneStroke = el.cloneNode();
cloneStroke.style.fill = "none";
cloneStroke.style.strokeWidth = strokeWidth * 2;
cloneStroke.removeAttribute("id");
cloneStroke.removeAttribute("stroke-alignment");
cloneStroke.classList.add("cloneStrokeOuter");
cloneStroke.setAttribute("mask", `url(#${maskClipId})`);
el.parentNode.insertBefore(cloneStroke, el.nextElementSibling);
//remove stroke from original element
el.style.stroke = "none";
}
if (strokeAlignment === "inner") {
//create clipPath
let clipPathEl = el.cloneNode();
let clipPath = document.createElementNS(ns, "clipPath");
clipPath.id = maskClipId;
defs.appendChild(clipPath);
clipPathEl.removeAttribute("id");
clipPath.appendChild(clipPathEl);
el.setAttribute("clip-path", `url(#${maskClipId})`);
el.style.strokeWidth = strokeWidth * 2;
}
});
}
}
body {
margin: 2em;
}
svg {
width: 100%;
height: auto;
overflow: visible;
border: 1px solid #ccc;
}
body {
margin: 2em;
}
svg {
height: 20em;
overflow: visible;
border: 1px solid #ccc;
}
<p><button onclick="emulateStrokeAlign()">Emulate stroke align</button></p>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 380 120">
<g id="myGroup" style="fill:rgb(45, 130, 255); stroke:#000; stroke-width:10; stroke-opacity:1;">
<rect id="el1" stroke-alignment="outer" x="10" y="10" width="100" height="100" />
<rect id="el2" x="140" y="10" width="100" height="100" />
<rect id="el3" stroke-alignment="inner" x="270" y="10" width="100" height="100" />
</g>
</svg>
<svg viewBox="0 0 12 6" xmlns="http://www.w3.org/2000/svg" stroke-width="0.5">
<path d="M1,5 a2,2 0,0,0 2,-3 a3,3 0 0 1 2,3.5z" fill="blue" stroke-align="outer" stroke="red" stroke-opacity="0.5" stroke-linecap="butt" />
<path d="M7,5 a2,2 0,0,0 2,-3 a3,3 0 0 1 2,3.5z" fill="blue" stroke-align="inner" stroke="red" stroke-opacity="0.5" />
</svg>
Hardcoded offset via paper.js offset glenzli's plugin
This approach will actually grow/shrink your <path> elements to get the desired stroke position (using the default middle stroke-alignment).
const canvas = document.createElement("canvas");
canvas.style.display='none';
document.body.appendChild(canvas);
//const canvas = document.querySelector("canvas");
paper.setup(canvas);
let strokeEls = document.querySelectorAll("*[stroke-alignment]");
strokeEls.forEach((el,i) => {
let type = el.nodeName;
let style = window.getComputedStyle(el);
let strokeWidth = parseFloat(style.strokeWidth);
let strokeAlignment = el.getAttribute('stroke-alignment');
let offset = strokeAlignment==='outer' ? strokeWidth/2 : (strokeAlignment==='inner' ? strokeWidth / -2 : 0);
// convert primitive
if(type!=='path'){
el = convertPrimitiveToPath(el);
}
let d = el.getAttribute("d");
let polyPath = new paper.Path(el.getAttribute("d"));
let dOffset = offset ? PaperOffset.offset(polyPath, offset)
.exportSVG()
.getAttribute("d") : d;
el.setAttribute("d", dOffset);
});
body{
margin:2em;
}
svg{
width:100%;
overflow:visible;
border:1px solid #ccc;
}
<svg viewBox="0 0 12 6" xmlns="http://www.w3.org/2000/svg" stroke-width="0.5">
<path d="M1,5 a2,2 0,0,0 2,-3 a3,3 0 0 1 2,3.5" stroke="black" fill="none" stroke-linejoin="miter"/>
<path d="M1,5 a2,2 0,0,0 2,-3 a3,3 0 0 1 2,3.5" fill="none" stroke-linejoin="miter" stroke-alignment="outer" stroke="red" stroke-opacity="0.5" />
<path d="M7,5 a2,2 0,0,0 2,-3 a3,3 0 0 1 2,3.5" stroke="black" fill="none" stroke-linejoin="round" />
<path d="M7,5 a2,2 0,0,0 2,-3 a3,3 0 0 1 2,3.5" fill="none" stroke-linejoin="round" stroke-alignment="inner" stroke="red" stroke-opacity="0.5" />
</svg>
<script src="https://unpkg.com/paper#0.12.15/dist/paper-full.min.js"></script>
<script src="https://unpkg.com/paperjs-offset#1.0.8/dist/paperjs-offset.js"></script>
However, the library struggles with complex shapes.

SVG outer alignment of semi transparent stroke [duplicate]

Currently building a browser-based SVG application. Within this app, various shapes can be styled and positioned by the user, including rectangles.
When I apply a stroke-width to an SVG rect element of say 1px, the stroke is applied to the rect’s offset and inset in different ways by different browsers. This is proving to be troublesome, especially when I try to calculate the outer width and visual position of a rectangle and position it next to other elements.
For example:
Firefox adds 1px inset (bottom and left), and 1px offset (top and right)
Chrome adds 1px inset (top and left), and 1px offset (bottom and right)
My only solution so far would be to draw the actual borders myself (probably with the path tool) and position the borders behind the stroked element. But this solution is an unpleasant workaround, and I’d prefer not to go down this road if possible.
So my question is, can you control how an SVG’s stroke-width is drawn on elements?
No, you cannot specify whether the stroke is drawn inside or outside an element. I made a proposal to the SVG working group for this functionality in 2003, but it received no support (or discussion).
As I noted in the proposal,
you can achieve the same visual result as "inside" by doubling your stroke width and then using a clipping path to clip the object to itself, and
you can achieve the same visual result as 'outside' by doubling the stroke width and then overlaying a no-stroke copy of the object on top of itself.
Edit: This answer may be wrong in the future. It should be possible to achieve these results using SVG Vector Effects, by combining veStrokePath with veIntersect (for 'inside') or with veExclude (for 'outside). However, Vector Effects are still a working draft module with no implementations that I can yet find.
Edit 2: The SVG 2 draft specification includes a stroke-alignment property (with center|inside|outside possible values). This property may make it into UAs eventually.
Edit 3: Amusingly and dissapointingly, the SVG working group has removed stroke-alignment from SVG 2. You can see some of the concerns described after the prose here.
UPDATE: The stroke-alignment attribute was on April 1st, 2015 moved to a completely new spec called SVG Strokes.
As of the SVG 2.0 Editor’s Draft of February 26th, 2015 (and possibly since February 13th), the stroke-alignment property is present with the values inner, center (default) and outer.
It seems to work the same way as the stroke-location property proposed by #Phrogz and the later stroke-position suggestion. This property has been planned since at least 2011, but apart from an annotation that said
SVG 2 shall include a way to specify stroke position
, it has never been detailed in the spec as it was deferred - until now, it seems.
No browser support this property, or, as far as I know, any of the new SVG 2 features, yet, but hopefully they will soon as the spec matures. This has been a property I personally have been urging to have, and I'm really happy that it's finally there in the spec.
There seems to be some issues as to how to the property should behave on open paths as well as loops. These issues will, most probably, prolong implementations across browsers. However, I will update this answer with new information as browsers begin to support this property.
I found an easy way, which has a few restrictions, but worked for me:
define the shape in defs
define a clip path referencing the shape
use it and double the stroke with as the outside is clipped
Here a working example:
<svg width="240" height="240" viewBox="0 0 1024 1024">
<defs>
<path id="ld" d="M256,0 L0,512 L384,512 L128,1024 L1024,384 L640,384 L896,0 L256,0 Z"/>
<clipPath id="clip">
<use xlink:href="#ld"/>
</clipPath>
</defs>
<g>
<use xlink:href="#ld" stroke="#0081C6" stroke-width="160" fill="#00D2B8" clip-path="url(#clip)"/>
</g>
</svg>
You can use CSS to style the order of stroke and fills. That is, stroke first and then fill second, and get the desired effect.
MDN on paint-order: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/paint-order
CSS code:
paint-order: stroke;
Here's a function that will calculate how many pixels you need to add - using the given stroke - to the top, right, bottom and left, all based on the browser:
var getStrokeOffsets = function(stroke){
var strokeFloor = Math.floor(stroke / 2), // max offset
strokeCeil = Math.ceil(stroke / 2); // min offset
if($.browser.mozilla){ // Mozilla offsets
return {
bottom: strokeFloor,
left: strokeFloor,
top: strokeCeil,
right: strokeCeil
};
}else if($.browser.webkit){ // WebKit offsets
return {
bottom: strokeCeil,
left: strokeFloor,
top: strokeFloor,
right: strokeCeil
};
}else{ // default offsets
return {
bottom: strokeCeil,
left: strokeCeil,
top: strokeCeil,
right: strokeCeil
};
}
};
As people above have noted you'll either have to recalculate an offset to the stroke's path coordinates or double its width and then mask one side or the other, because not only does SVG not natively support Illustrator's stroke alignment, but PostScript doesn't either.
The specification for strokes in Adobe's PostScript Manual 2nd edition states:
"4.5.1 Stroking:
The stroke operator draws a line of some thickness along the current path. For each straight or curved segment in the path, stroke draws a line that is centered on the segment with sides parallel to the segment." (emphasis theirs)
The rest of the specification has no attributes for offsetting the line's position. When Illustrator lets you align inside or outside, it's recalculating the actual path's offset (because it's still computationally cheaper than overprinting then masking). The path coordinates in the .ai document are reference, not what gets rastered or exported to a final format.
Because Inkscape's native format is spec SVG, it can't offer a feature the spec lacks.
Here is a work around for inner bordered rect using symbol and use.
Example: https://jsbin.com/yopemiwame/edit?html,output
SVG:
<svg>
<symbol id="inner-border-rect">
<rect class="inner-border" width="100%" height="100%" style="fill:rgb(0,255,255);stroke-width:10;stroke:rgb(0,0,0)">
</symbol>
...
<use xlink:href="#inner-border-rect" x="?" y="?" width="?" height="?">
</svg>
Note: Make sure to replace the ? in use with real values.
Background: The reason why this works is because symbol establishes a new viewport by replacing symbol with svg and creating an element in the shadow DOM. This svg of the shadow DOM is then linked into your current SVG element. Note that svgs can be nested and every svg creates a new viewport, which clips everything that overlaps, including the overlapping border. For a much more detailed overview of whats going on read this fantastic article by Sara Soueidan.
I don’t know how helpful will that be but in my case I just created another circle with border only and placed it “inside” the other shape.
This worked for me:
.btn {
border: 1px solid black;
box-shadow: inset 0 0 0 1px black;
}
A (dirty) possible solution is by using patterns,
here is an example with an inside stroked triangle :
https://jsfiddle.net/qr3p7php/5/
<style>
#triangle1{
fill: #0F0;
fill-opacity: 0.3;
stroke: #000;
stroke-opacity: 0.5;
stroke-width: 20;
}
#triangle2{
stroke: #f00;
stroke-opacity: 1;
stroke-width: 1;
}
</style>
<svg height="210" width="400" >
<pattern id="fagl" patternUnits="objectBoundingBox" width="2" height="1" x="-50%">
<path id="triangle1" d="M150 0 L75 200 L225 200 Z">
</pattern>
<path id="triangle2" d="M150 0 L75 200 L225 200 Z" fill="url(#fagl)"/>
</svg>
The solution from Xavier Ho of doubling the width of the stroke and changing the paint-order is brilliant, although only works if the fill is a solid color, with no transparency.
I have developed other approach, more complicated but works for any fill. It also works in ellipses or paths (with the later there are some corner cases with strange behaviour, for example open paths that crosses theirselves, but not much).
The trick is to display the shape in two layers. One without stroke (only fill), and another one only with stroke at double width (transparent fill) and passed through a mask that shows the whole shape, but hides the original shape without stroke.
<svg width="240" height="240" viewBox="0 0 1024 1024">
<defs>
<path id="ld" d="M256,0 L0,512 L384,512 L128,1024 L1024,384 L640,384 L896,0 L256,0 Z"/>
<mask id="mask">
<use xlink:href="#ld" stroke="#FFFFFF" stroke-width="160" fill="#FFFFFF"/>
<use xlink:href="#ld" fill="#000000"/>
</mask>
</defs>
<g>
<use xlink:href="#ld" fill="#00D2B8"/>
<use xlink:href="#ld" stroke="#0081C6" stroke-width="160" fill="red" mask="url(#mask)"/>
</g>
</svg>
The easiest way I found is to add clip-path into circle
Add clip-path="circle()"
<circle id="circle" clip-path="circle()" cx="100" cy="100" r="100" fill="none" stroke="currentColor" stroke-width="5" />
Then the stroke-width="5" will magically become inner 5px stroke with absolute 100px radius.
Update 2023: The current draft renamed the attribute to stroke-align
Browser Support 2023:
See caniuse
This CSS property is not supported in any modern browser, nor are
there any known plans to support it.
Polyfill-like helper function
Based on the previous approaches to combine paint-order, mask and clip-path.
(As suggested by #Xavier Ho
#Jorg Janke)
//emulateStrokeAlign();
function emulateStrokeAlign() {
let supportsSvgStrokeAlign = CSS.supports("stroke-align", "outer") ?
true :
CSS.supports("stroke-alignment", "outer") ?
true :
false;
console.log("supportsSvgStrokeAlign", supportsSvgStrokeAlign);
if (!supportsSvgStrokeAlign) {
let ns = "http://www.w3.org/2000/svg";
let strokeAlignmentEls = document.querySelectorAll(
"*[stroke-alignment], *[stroke-align]"
);
strokeAlignmentEls.forEach((el, s) => {
let svg = el.closest("svg");
// set auto ids to prevent non-unique mask ids
let svgID = svg.id ? svg.id : "svg_" + s;
svg.id = svgID;
//create <defs> if not previously appended
let defs = svg.querySelector("defs");
if (!defs) {
defs = document.createElementNS(ns, "defs");
svg.insertBefore(defs, svg.children[0]);
}
let style = window.getComputedStyle(el);
let strokeWidth = parseFloat(style.strokeWidth);
let strokeAlignment = el.getAttribute("stroke-alignment") ?
el.getAttribute("stroke-alignment") :
el.getAttribute("stroke-align");
el.removeAttribute("stroke-align");
el.removeAttribute("stroke-alignment");
el.setAttribute("data-stroke-align", strokeAlignment);
let maskClipId = `mask-${svgID}-${s}`;
if (strokeAlignment === "outer") {
// create mask
let mask = document.createElementNS(ns, "mask");
mask.id = maskClipId;
let maskEl = el.cloneNode();
mask.appendChild(maskEl);
defs.appendChild(mask);
maskEl.setAttribute("fill", "#000");
mask.setAttribute("maskUnits", "userSpaceOnUse");
maskEl.setAttribute("stroke", "#fff");
maskEl.removeAttribute("stroke-opacity");
maskEl.removeAttribute("id");
maskEl.setAttribute("paint-order", "stroke");
maskEl.style.strokeWidth = strokeWidth * 2;
// clone stroke
let cloneStroke = el.cloneNode();
cloneStroke.style.fill = "none";
cloneStroke.style.strokeWidth = strokeWidth * 2;
cloneStroke.removeAttribute("id");
cloneStroke.removeAttribute("stroke-alignment");
cloneStroke.classList.add("cloneStrokeOuter");
cloneStroke.setAttribute("mask", `url(#${maskClipId})`);
el.parentNode.insertBefore(cloneStroke, el.nextElementSibling);
//remove stroke from original element
el.style.stroke = "none";
}
if (strokeAlignment === "inner") {
//create clipPath
let clipPathEl = el.cloneNode();
let clipPath = document.createElementNS(ns, "clipPath");
clipPath.id = maskClipId;
defs.appendChild(clipPath);
clipPathEl.removeAttribute("id");
clipPath.appendChild(clipPathEl);
el.setAttribute("clip-path", `url(#${maskClipId})`);
el.style.strokeWidth = strokeWidth * 2;
}
});
}
}
body {
margin: 2em;
}
svg {
width: 100%;
height: auto;
overflow: visible;
border: 1px solid #ccc;
}
body {
margin: 2em;
}
svg {
height: 20em;
overflow: visible;
border: 1px solid #ccc;
}
<p><button onclick="emulateStrokeAlign()">Emulate stroke align</button></p>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 380 120">
<g id="myGroup" style="fill:rgb(45, 130, 255); stroke:#000; stroke-width:10; stroke-opacity:1;">
<rect id="el1" stroke-alignment="outer" x="10" y="10" width="100" height="100" />
<rect id="el2" x="140" y="10" width="100" height="100" />
<rect id="el3" stroke-alignment="inner" x="270" y="10" width="100" height="100" />
</g>
</svg>
<svg viewBox="0 0 12 6" xmlns="http://www.w3.org/2000/svg" stroke-width="0.5">
<path d="M1,5 a2,2 0,0,0 2,-3 a3,3 0 0 1 2,3.5z" fill="blue" stroke-align="outer" stroke="red" stroke-opacity="0.5" stroke-linecap="butt" />
<path d="M7,5 a2,2 0,0,0 2,-3 a3,3 0 0 1 2,3.5z" fill="blue" stroke-align="inner" stroke="red" stroke-opacity="0.5" />
</svg>
Hardcoded offset via paper.js offset glenzli's plugin
This approach will actually grow/shrink your <path> elements to get the desired stroke position (using the default middle stroke-alignment).
const canvas = document.createElement("canvas");
canvas.style.display='none';
document.body.appendChild(canvas);
//const canvas = document.querySelector("canvas");
paper.setup(canvas);
let strokeEls = document.querySelectorAll("*[stroke-alignment]");
strokeEls.forEach((el,i) => {
let type = el.nodeName;
let style = window.getComputedStyle(el);
let strokeWidth = parseFloat(style.strokeWidth);
let strokeAlignment = el.getAttribute('stroke-alignment');
let offset = strokeAlignment==='outer' ? strokeWidth/2 : (strokeAlignment==='inner' ? strokeWidth / -2 : 0);
// convert primitive
if(type!=='path'){
el = convertPrimitiveToPath(el);
}
let d = el.getAttribute("d");
let polyPath = new paper.Path(el.getAttribute("d"));
let dOffset = offset ? PaperOffset.offset(polyPath, offset)
.exportSVG()
.getAttribute("d") : d;
el.setAttribute("d", dOffset);
});
body{
margin:2em;
}
svg{
width:100%;
overflow:visible;
border:1px solid #ccc;
}
<svg viewBox="0 0 12 6" xmlns="http://www.w3.org/2000/svg" stroke-width="0.5">
<path d="M1,5 a2,2 0,0,0 2,-3 a3,3 0 0 1 2,3.5" stroke="black" fill="none" stroke-linejoin="miter"/>
<path d="M1,5 a2,2 0,0,0 2,-3 a3,3 0 0 1 2,3.5" fill="none" stroke-linejoin="miter" stroke-alignment="outer" stroke="red" stroke-opacity="0.5" />
<path d="M7,5 a2,2 0,0,0 2,-3 a3,3 0 0 1 2,3.5" stroke="black" fill="none" stroke-linejoin="round" />
<path d="M7,5 a2,2 0,0,0 2,-3 a3,3 0 0 1 2,3.5" fill="none" stroke-linejoin="round" stroke-alignment="inner" stroke="red" stroke-opacity="0.5" />
</svg>
<script src="https://unpkg.com/paper#0.12.15/dist/paper-full.min.js"></script>
<script src="https://unpkg.com/paperjs-offset#1.0.8/dist/paperjs-offset.js"></script>
However, the library struggles with complex shapes.

How can I use and transform a canvas behind an svg mask in a cross-browser way?

I've been doing a lot of research into Canvas and SVG, as well as CSS3 3d transforms, and I want to use them all together, and I'm having difficulty getting it all working. I've got an SVG element containing an image-based mask as well as a foreignObject element containing some HTML I'd like to mask, particularly, a canvas. Depending on what I do with the canvas, I'm getting different results in different browsers, and usually not what I want. Applying typical canvas operations, such as rect(), and applying typical css transformations, such as rotate3d(), will, in most browsers, cause the SVG image mask to no longer be applied to the canvas, and the entire canvas will be visible, including the masked part.
Safari (desktop): If the canvas has ever been drawn on or transformed in any way (even, for example, rotate3d(1,1,0,0deg)), it will not be masked.
Safari (iOS 7): If the canvas has ever been drawn on or transformed in any way, it will not be masked.
Chrome (desktop): Here, we can draw to the canvas and it will still be masked, but if it has been transformed, it will not be masked.
Chrome (iOS 7): If the canvas has been drawn on or transformed at all, it will not be masked.
Firefox: No problems here; it will still be masked even if it has been drawn on and transformed.
IE and others: I haven't checked yet.
You can see this illustrated by applying the following code, which is also hosted here, where I set up an example page: http://kage23.com/masktest.html
<svg width="748" height="421" baseProfile="full" version="1.2">
<defs>
<mask id="myMask" transform="scale(1)">
<image width="100%" height="421" xlink:href="http://i.imgur.com/ORtP2fW.png" />
</mask>
</defs>
<foreignObject id="foreignObject" width="100%" height="421">
<div style="background-color: green; width: 100%; height: 421px;">
<p id="myParagraph" style="padding-top: 100px; padding-left: 200px;">Some text or whatever.</p>
<canvas id="effectCanvas" width="500px" height="100px" style="background-color: red;"></canvas>
</div>
</foreignObject>
</svg>
<button onclick="applyMask()">Apply mask</button>
<button onClick="drawToCanvas()">Draw to canvas</button>
<button onClick="rotateCanvas()">Apply rotation to canvas</button>
<script>
var applyMask = function ()
{
var element = document.getElementById('foreignObject');
element.style.mask = 'url(#myMask)';
};
var drawToCanvas = function ()
{
var canvas = document.getElementById('effectCanvas');
var ctx = canvas.getContext('2d');
ctx.fillStyle = "rgb(200,0,0)";
ctx.fillRect (10, 10, 55, 50);
};
var rotateCanvas = function ()
{
var canvas = document.getElementById('effectCanvas');
canvas.style.webkitTransform = 'rotate3d(1,1,1,45deg)';
canvas.style.MozTransform = 'rotate3d(1,1,1,45deg)';
};
</script>
Is there a better way to do this? I'm not committed to using an SVG mask necessarily; I just need to be able to mask a transforming canvas that is also being actively drawn on. Originally, I was using -webkit-mask-box-image, but that's not supported very widely at all.

Scale path from center

I have the following SVG graphic:
<svg version="1.1" id="diagram" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="375px" height="150px">
<path d="M45,11.5H33.333c0.735-1.159,1.167-2.528,1.167-4C34.5,3.364,31.136,0,27,0s-7.5,3.364-7.5,7.5c0,1.472,0.432,2.841,1.167,4H9l-9,32h54L45,11.5z M22.5,7.5C22.5,5.019,24.519,3,27,3s4.5,2.019,4.5,4.5c0,1.752-1.017,3.257-2.481,4h-4.037 C23.517,10.757,22.5,9.252,22.5,7.5z" id="control"/>
</svg>
I want to programmatically change the scale of this object, but I want it to scale from the center point.
I've tried wrapping it around a <g> tag, like so
<g transform="translate(0,0)">
<path x="0" y="0" id="control" transform="scale(2)">...</path>
</g>
But this doesn't seem to work. It seems that scaling a path requires manipulation of the path's matrix, which seems horrifically difficult. Annoyingly, it's easy to scale using additive="sum" property but in this instance, I am not using a transform animation.
Can anyone help me out?
Edit: Managed to get this working nicely, for anyone who is stuck on the same thing, here is a nice way of doing it programmatically:
var elem = document.getElementById("control");
var bBox = elem.getBBox();
var scaleX = 2;
var scaleY = 2;
$(elem).attr("transform", `scale(${scaleX}, ${scaleY}) translate(${-bBox.width/2},${-bBox.height/2})`);
If you know the coordinates of the center point, then you can combine a translate and scale in one transformation. The translation is calculated as: (1 - scale) * currentPosition.
If the center is (10, 20) and you are scaling by 3 then translate by (1 - 3)*10, (1 - 3)*20 = (-20, -40):
<g transform="translate(-20, -40) scale(3)">
<path d="M45,11.5H33.333c0.735-1.159,1.167-2.528,1.167-4C34.5,3.364,31.136,0,27,0s-7.5,3.364-7.5,7.5c0,1.472,0.432,2.841,1.167,4H9l-9,32h54L45,11.5z M22.5,7.5C22.5,5.019,24.519,3,27,3s4.5,2.019,4.5,4.5c0,1.752-1.017,3.257-2.481,4h-4.037 C23.517,10.757,22.5,9.252,22.5,7.5z" id="control"/>
</g>
The transformations are applied in reverse order from the one they are declared, so in the example, above, the scale is performed first and then the translate. Scaling affects the coordinates so the translation here is in scaled coordinates.
You can calculate the center point programmatically using element.getBBox().
You can alter the origin to center:
.scaled-path-svg {
svg {
path {
transform-origin: center;
transform: scale(1.1);
}
}
}
The answer provided by aetheria earlier is great. There is another thing to take care of as well -- stroke-width, so that the outline stays of the same width while the object scales. Usage:
stroke-width: (1/scaling-factor)
So, if your scaling is by say 2, then:
stroke-width: (0.5)
NOTE: You shouldn't missout the transform: translate(...) scale(2) as mentioned by aetheria.