I have a SVG (a cross) which changes the color of the lines based on the hash given to the SVG url using JavaScript.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<line x1="0" y1="0" x2="100%" y2="100%" stroke="black" stroke-width="1" />
<line x1="100%" y1="0" x2="0" y2="100%" stroke="black" stroke-width="1" />
<script>
if (window.location.hash) {
document.getElementsByTagName('line')[0].setAttribute('stroke', location.hash);
document.getElementsByTagName('line')[1].setAttribute('stroke', location.hash);
}
</script>
</svg>
This works perfectly fine as an <object> element (<object type="image/svg+xml" data="img/svg/cross.svg#ff0000"></object>), but fails as an img or css background-image.
How can I make this work as a CSS background-image?
Dynamic behavior in SVG that is used as an HTML image is disabled for security reasons. The reason is quite obvious - you can use an SVG image from a different domain and wouldn't really want it to run JavaScript code in the context of your document. So SVG used as an HTML image is essentially always static. There are some more details on http://www.schepers.cc/svg/blendups/embedding.html (thanks #RobertLongson for this link).
There is a work-around in Firefox: if you have inline SVG code (can be hidden) you can use a filter from that SVG code using the filter CSS property. Depending on what you are trying to achieve this can be a rather powerful tool. According to MDN Chrome and Safari should also support this but I'm not certain that they do.
I have found a solution myself that works for me.
I'm also using Sass, and with it I have found a base64 encode plugin.
With it, I can write svg in my CSS which is then encoded to base64. And I can also use variables.
The SASS code now looks like this:
background: url('data:image/svg+xml;base64,'
+ base64Encode('<svg xmlns="http://www.w3.org/2000/svg"><line x1="0" y1="0" x2="100%" y2="100%" stroke="#{$color-line}" stroke-width="1" /><line x1="100%" y1="0" x2="0" y2="100%" stroke="#{$color-line}" stroke-width="1" /></svg>'));
The base64 plugin is found here:
URL- or Base64- encode strings in Compass/SASS
Related
Note: I can't use JavaScript, because this is for a CSS Zen Garden sort of challenge. Please do not suggest a JS library.
I have 2 ideas that I'm not making headway on:
Use a SVG filter to just pixelate the dang image; I've been playing with <feMorphology operator="erode"/> and punching the contrast up after, but it looks bad.
Filter the image to be smaller, then scale it up using CSS and image-rendering to be all blocky. The hard part is Step A; I can't find any filter operations that scale the input.
Am I missing something? How can I get a "pixelated" effect using an SVG filter?
You can pixelate images if you have the right "magic" displacementMap. Feel free to use the one referenced below (courtesy of Zoltan Fegyver).
Update: Changed the sample code to inline the displacementmap image as a data: URI (thanks for the code IllidanS4.)
The original answer had the displacementMap image hosted on a different domain. This used to work - but browsers implemented the new Filters security measures that disallow this. For production code today, you need the displacement map image served from the same domain as the source graphic's file or you need to inline the displacementMap.
Update 2:
You may have to tweak the size of feImage and feGaussianBlur to avoid bugs in feTile that adds artifacts. For example - this seems to work better:
<feGaussianBlur stdDeviation="8" in="SourceGraphic" result="smoothed" />
<feImage width="15.4" height="15.4"
<svg x="0px" y="0px" width="810px" height="600px" viewBox="0 0 810 600" color-interpolation-filters="sRGB">
<defs>
<filter id="pixelate" x="0%" y="0%" width="100%" height="100%">
<!--Thanks to Zoltan Fegyver for figuring out pixelation and producing the awesome pixelation map. -->
<feGaussianBlur stdDeviation="2" in="SourceGraphic" result="smoothed" />
<feImage width="15" height="15" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAWSURBVAgdY1ywgOEDAwKxgJhIgFQ+AP/vCNK2s+8LAAAAAElFTkSuQmCC" result="displacement-map" />
<feTile in="displacement-map" result="pixelate-map" />
<feDisplacementMap in="smoothed" in2="pixelate-map" xChannelSelector="R" yChannelSelector="G" scale="50" result="pre-final"/>
<feComposite operator="in" in2="SourceGraphic"/>
</filter>
</defs>
<image filter="url(#pixelate)" width="810" height="600" preserveAspectRatio="xMidYMid meet" xlink:href="http://uploads2.wikiart.org/images/vincent-van-gogh/the-starry-night-1889(1).jpg"/>
</svg>
The filter in Michael Mullany's answer didn't work for me, instead I found this filter by Taylor Hunt:
<svg>
<filter id="pixelate" x="0" y="0">
<feFlood x="4" y="4" height="2" width="2"/>
<feComposite width="10" height="10"/>
<feTile result="a"/>
<feComposite in="SourceGraphic" in2="a" operator="in"/>
<feMorphology operator="dilate" radius="5"/>
</filter>
</svg>
(use it in the same way as the other filter: By giving an image the attribute filter="url(#pixelate)")
In action in this CodePen: https://codepen.io/tigt/pen/aZYqrg
However, both these filters seem unable to handle SVGs where the drawing doesn't take up the entire viewBox.
I am working with d3.js library and need to draw some svg elements inside another child svg element. For example, I have a container element in the svg, which is a "rect". I want too draw some lines inside that "rect". But I am having issue viewing those lines.
If I add those line to the main svg container, it works fine. But the lines are not visible when I am adding those to the "rect". I guess, coordinate is the issue.
So, can anyone let me know, how the coordinate should be calculated while drawing inside a child element? Is it automatically offset by it's parent coordinate? or the "rect" will have it's own coordinate system?
<line> is not rendered inside a <rect>.
If you look at https://www.w3.org/TR/SVG/shapes.html#RectElement, only animation and descriptive elements are allowed as contents of <rect>
You might want to restructure your code to use the g element as a container element for grouping together related graphics elements (such as <rect> and <line>).
If you need to position the grouping element, you can use a transform.
<svg width="1000" height="500">
<g transform="translate(10,30)">
<rect x="0" y="0" width="100" height="50" style="fill:black;"></rect>
<line x1="20" y1="0" x2="20" y2="20" stroke="white" stroke-width="2"></line>
<line x1="40" y1="0" x2="40" y2="20" stroke="white" stroke-width="2"></line>
<line x1="60" y1="0" x2="60" y2="20" stroke="white" stroke-width="2"></line>
<line x1="80" y1="0" x2="80" y2="20" stroke="white" stroke-width="2"></line>
</g>
</svg>
https://jsfiddle.net/ksav/wq6mvv9v/
I have a site with a series of elements that, when clicked on, add inline svg code to that element. The svg essentially animates an "iris wipe" to tunr the element white. Code from a separate html document is the loaded into a series of divs. When all the images are done loading, I want to append a mask to the svg code to iris wipe it back to how it was.
I am using waitForImages.js to check when the images are done loading. This is working successfully. The mask is also being added to the svg correctly. However, the mask animates.
Here is the initial code for adding the svg:
$("#selProject").append('<svg id="circleCont" x="0px" y="0px" viewBox="0 0 360 360" enable-background="new 0 0 360 360"><circle class="circ" cx="50%" cy="50%" r="0.01" stroke="#FFFFFF" stroke-width="0.5" fill="none"><animate attributeName="r" from="0.01" to="100%" dur="0.2" begin="0s" fill="freeze"/><animate attributeName="stroke-width" from="0.5" to="100" dur="0.2" begin="0s" fill="freeze"/></circle><circle class="circ" cx="50%" cy="50%" r="0.01" stroke="#FFFFFF" stroke-width="0.5" fill="none"><animate attributeName="r" from="0.01" to="100%" dur="0.2" begin="0.1s" fill="freeze"/><animate attributeName="stroke-width" from="0.5" to="200" dur="0.2" begin="0.1s" fill="freeze"/></circle><circle class="circ" cx="50%" cy="50%" r="0.01" fill="#FFFFFF" mask="url(#mask1)"><animate attributeName="r" from="0.01" to="100%" dur="0.3" begin="0.2s" fill="freeze"/></circle></svg>')
This is probably not the cleanest way to do it, but is the way I knew how.
Later, after some other code / loading the other html document using ajax
$("#selProject").waitForImages(function() {
$("#projectPageInfo").waitForImages(function() {
$("svg").append('<mask id="mask1"><rect fill="white" width="100%" height="100%" /><circle id="circmask" class="circ" cx="50%" cy="50%" r="0.01" fill="#000000"><animate attributeName="r" from="0.01" to="100%" dur="0.3" begin="0s" fill="freeze"/></circle></mask>');
});
});
The mask worked correctly when it was apart of the initial svg code being added, and animated as it should have. However, I needed it not to happen until the images have loaded, and now, despite the mask being added to the svg successfully, it does not animate. What am I missing?
I suspect it's the typical jQuery problem. JQuery cannot be relied on to do the right thing with SVG elements. jQuery is designed to work with HTML, not SVG whose elements are in a different namespace.
The first append works because the browser knows what to do with the <svg> element, and does the right thing. However the second append fails because the <mask> element will be created in the default (ie. HTML) namespace rather than the SVG one.
If you look at the DOM properties of the appended <mask> element in your browser's dev tools, you will probably find that it has the wrong (ie. not SVG) namespace.
As a solution, I would try adding the <mask> back in to the original SVG and only set the mask attribute when you want the mask to be used. In other words, remove:
mask="url(#mask1)"
then when you want the mask to be applied:
document.getElementById("id-of-masked-circle").setAttribute("mask", "url(#mask1)");
Looking for ideas on how to animate what looks like a laser drawing out a word in a cursive font using SVG. The animation can be done with SMIL or JavaScript I don't care - though I think it would be easier with SMIL.
I am pretty sure if I could just get the letters represented as a path I could figure out how to animate a line from a fixed point to the word path - even if the path is non-continuous.
Any ideas?
EDIT
My demo was very basic, essentially I wrote animate functions for each letter and arranged their timing. Here is the letter X for example:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="none">
<rect x="0" y="0" width="100%" height="100%" fill="black"/>
<path id="word" stroke="red" d="M10 10 L40 40 M40 10 L10 40" />
<line x1="10" y1="10" x2="25" y2="50" stroke="blue" stroke-width="0.5">
<animate attributeName="x1" begin="0s" dur="1s" values="10; 40;" />
<animate attributeName="y1" begin="0s" dur="1s" values="10; 40;" />
<animate attributeName="x1" begin="1s" dur="1s" values="40; 10;" />
<animate attributeName="y1" begin="1s" dur="1s" values="10; 40;" />
<set attributeName="visibility" to="hidden" begin="2s" />
</line>
</svg>
I am sure we can all agree that this is not an ideal long term solution... I thought it would be relatively easy to animate one end of a LINE along a path but I am having problems just getting the path...
Extract the paths from the glyphs in question, then apply a dash-array animation as seen in this example on each of the paths.
From a high level perspective, I would think you would want to do something like render the font to a canvas, then use the pixel information to generate the animation sequence. A simple algorithm could just trace from left to right, it would be a good deal harder to figure out a single stroke path, but that is doable as well.
You don't mention any idea of what platform or any time constraints, so its hard to get much closer than that.
One possibility... SVG Fonts are, I understand, stored as a sequence of SVG commands used to draw individual characters. The vector-based nature of drawing in SVG would seem like it would be amenable to 'tracing out' characters in realtime; you might be able to make a conversion utility to pre-convert SVG fonts to simple paths.
How do I make the text on a path (see screenshot) extend so that it follows the entire textPath?
I have tried using the method attribute value stretch but it doesn't work like I expect - it doesn't stretch the text along the path.
Is there a way to make this work in all browsers?
The way to spread out the text over the entire textPath is to use the textLength attribute. Also see this other question for how to compute a good value for textLength. Here's how to do it:
<svg viewBox="0 0 500 300" version="1.1">
<defs>
<path id="s3" d="M 10,90 Q 100,15 200,70" />
</defs>
<text font-size="20">
<textPath xlink:href="#s3" textLength="205">
Short text
</textPath>
</text>
<use xlink:href="#s3" fill="none" stroke="black" stroke-width="1"/>
</svg>
Viewable example: http://jsfiddle.net/zkZ2n/
Here's the bugreport for Firefox not supporting textLength: https://bugzilla.mozilla.org/show_bug.cgi?id=569722