SVG clipPath on multiple Use elements at different locations - svg

Until recently the following SVG code rendered correctly in Firefox (v25), how ever it doesn't any more (v33). All the other browsers I have tested (Chrome 33, Safari 6, IE 10).
http://jsfiddle.net/9btoveeL/
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" height="1000px" width="1000px" y="0px" x="0px" version="1.1">
<defs>
<clipPath id="clip1">
<rect height="100" width="10" y="0" x="0"/>
</clipPath>
<clipPath id="clip2">
<rect height="100" width="10" y="0" x="10"/>
</clipPath>
<clipPath id="clip3">
<rect height="100" width="10" y="0" x="20"/>
</clipPath>
<symbol id="fill_texture">
<g>
<rect height="10" width="10" x="0" y="0" fill="#ff0000"/>
<rect height="10" width="10" x="3" y="5" fill="#0ff000"/>
<rect height="10" width="10" x="6" y="10" fill="#0000ff"/>
<rect height="10" width="10" x="9" y="15" fill="#ffff00"/>
<rect height="10" width="10" x="12" y="20" fill="#ff00ff"/>
<rect height="10" width="10" x="15" y="25" fill="#00ffff"/>
</g>
</symbol>
</defs>
<g id="columns">
<use id="unclipped" xlink:href="#fill_texture" width="100" height="100" x="0" y="0"/>
<use id="slot1" xlink:href="#fill_texture" clip-path="url(#clip1)" x="50" y="0"/>
<use id="slot2" xlink:href="#fill_texture" clip-path="url(#clip2)" x="100" y="0"/>
<use id="slot3" xlink:href="#fill_texture" clip-path="url(#clip3)" x="150" y="0"/>
</g>
</svg>
What I'm attempting to do is slice a prepared symbol into three parts and then use those three parts wherever I like. In Firefox 33 it seems it is applying the clip at it's original location (0,0 or 0,10 in my example) while the other browsers and previous versions of Firefox apply the clip starting from the top left corner of the use element it is applied to.
If each clip path must be moved to match the location of the use instead of being treated as relative to it, I can't see how a clip could ever be reused in multiple elements.

The position of a clipping path is by default calculated in the user coordinate system of the object to which it is applied (clipPathUnits="userSpaceOnUse").
(You could set it to clipPathUnits="objectBoundingBox", but then you would have to redefine all the lengths of shapes within the clipping paths to be relative to the height and width of the shape being clipped.)
There is a simple solution to always get the effect you want: transform the coordinate system for the use elements. Instead of positioning them using x and y attributes, position them with a transform="translate(x,y)" attribute. That way, the coordinate system used to position the clipping path will move with them.
Here's your fiddle updated to show the change. It's repeated below as a stack snippet, and should work as expected in all browsers.
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"
xml:space="preserve" version="1.1"
height="100px" width="400px" y="0px" x="0px" >
<defs>
<clipPath id="clip1" >
<rect height="100" width="10" y="0" x="0"/>
</clipPath>
<clipPath id="clip2">
<rect height="100" width="10" y="0" x="10"/>
</clipPath>
<clipPath id="clip3">
<rect height="100" width="10" y="0" x="20"/>
</clipPath>
<symbol id="fill_texture">
<g>
<rect height="10" width="10" x="0" y="0" fill="#ff0000"/>
<rect height="10" width="10" x="3" y="5" fill="#0ff000"/>
<rect height="10" width="10" x="6" y="10" fill="#0000ff"/>
<rect height="10" width="10" x="9" y="15" fill="#ffff00"/>
<rect height="10" width="10" x="12" y="20" fill="#ff00ff"/>
<rect height="10" width="10" x="15" y="25" fill="#00ffff"/>
</g>
</symbol>
</defs>
<g id="columns">
<use id="unclipped" xlink:href="#fill_texture"
width="100" height="100" x="0" y="0"/>
<use id="slot1" xlink:href="#fill_texture" clip-path="url(#clip1)"
transform="translate(50,0)"/>
<use id="slot2" xlink:href="#fill_texture" clip-path="url(#clip2)"
transform="translate(100,0)"/>
<use id="slot3" xlink:href="#fill_texture" clip-path="url(#clip3)"
transform="translate(150,0)"/>
</g>
</svg>
So what should happen when you use x and y instead of transform? It is hard to say, because there is an inconsistency in the specifications.
The Firefox (v33) implementation makes sense from a logical application of the clipping path rules.
When you <use> a <symbol>, you create a new coordinate space for the content within the symbol, but the use element is still in the parent coordinate space. And since it is the use element that is being clipped, that is the coordinate that matters for matching with the clipping path. A use element with an x coordinate of 50 or more will always be outside the clipping paths you give, which don't extend past x=30.
But then, why do the other browsers position the clipping path origin at the <use> element's (x,y) point instead of at the origin of its coordinate system? It's because of the way the specifications define how x and y should be implemented: as an additional transformation added to a grouping element that also has all the other attributes and styles (including clip-path) that you specify on <use>.
According to the specs, the following code:
<use xlink:href="#content" clip-path="url(#clip)"
x="50" y="100" width="50" height="100" />
is supposed to be rendered (assuming "content" is a <symbol>) the same as:
<g clip-path="url(#clip)" transform="translate(50,100)">
<svg width="50" height="100" <!--viewBox and other attributes from the symbol--> >
<!-- graphics from symbol#content go here -->
</svg>
</g>
and under that representation, the clipping path should be translated to match the transform attribute.
However, that is completely different than what would happen if you used an <image> or <rect> instead of <use>, but with the exact same x, y, width, height and clip-path attributes. For these other elements, x and y only define positions within the parent coordinate system, not transformations of the coordinate system, so they do change the origin of the clipping path. Which is why I would call this an unfortunate inconsistency in the specifications.

Related

how to set clipPath as a pattern using svg?

I created the pattern, then gave it to the circle inside the . clipPath I set to the image, but the pattern was not set for the image. How can I set a mask as a pattern for an image?
I was expecting to see a mask for the image in the form of a created pattern
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="600" height="600">
<pattern id="cube" x="0" y="10" width="20" height="20" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="10" height="10" />
<rect x="10" y="10" width="10" height="10" />
</pattern>
<clipPath id="msk1">
<circle fill="url(#cube)" cx="50%" cy="50%" width="100%" height="100%" r="200" />
</clipPath>
<image xlink:href="wave.jpg" height="100%" clip-path="url(#msk1)"/>
</svg>
If you want a mask, then use a <mask>, not a <clipPath>, which as its name implies will create a clipping area from a path. What you want is for the pixels to create the mask, and that's what a <mask> does.
svg { max-height: 100vh }
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 600 600">
<pattern fill="white" id="cube" x="0" y="10" width="20" height="20" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="10" height="10" />
<rect x="10" y="10" width="10" height="10" />
</pattern>
<mask id="msk1">
<circle fill="url(#cube)" cx="50%" cy="50%" width="100%" height="100%" r="200" />
</mask>
<image xlink:href="https://picsum.photos/400/400" height="100%" mask="url(#msk1)"/>
</svg>
(Note that I did set the rectangles of the pattern white, we could also have drawn a full white rectangle behind the black ones for the same effect).

(SVG) Transform origin of grouped path element is not getting applied

I want to define grouped shapes in the <def> section and display them via the <use> tag with the transformation-origin being the group's center. Following is a minimal example illustrating my issue:
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200">
<defs>
<g id="triangle">
<path fill="green" d="m 24 0 l 24 41.569 l -48 0 z" />
<circle cx="24" cy="24" r="1" fill="red" />
<rect width="48" height="41.569" fill="purple" fill-opacity="20%" />
</g>
</defs>
<rect width="100" height="100" />
<rect fill="gray" x="100" width="100" height="100" />
<rect width="100" height="100" />
<rect fill="gray" y="100" width="100" height="100" />
<rect width="100" height="100" x="100" y="100" />
<use href="#triangle" transform-origin="50% 50%" transform="translate(100 100)" />
</svg>
As it is my understanding, the triangle should be centered in the background. But in actuality, the top left corner of the group is centered.
I have tested my example against the MDN transform-origin one. The MDN one works well in the same environment (Edge Browser & VSCode). This lets me believe I am missing some side effects of the used tags or attributes
Observed behavior
Expected behavior
A scale transform has a centre point i.e. a point that doesn't change position.
So does a rotation - there's a centre of rotation.
A translate transform does not, every location moves. There's therefore no origin so transform-origin does nothing.

why does <g> not work in <clipPath> in svg?

The following code does not work:
<svg>
<defs>
<clipPath id="test">
<g>
<rect x="0" y="0" width="10" height="10"></rect>
</g>
</clipPath>
</defs>
<rect x="0" y="0" width="100" height="100" clip-path="url(#test)"></rect>
</svg>
But this does work:
<svg>
<defs>
<clipPath id="test">
<rect x="0" y="0" width="10" height="10"></rect>
</clipPath>
</defs>
<rect x="0" y="0" width="100" height="100" clip-path="url(#test)"></rect>
</svg>
In my project, I have some paths in the group tag and I want to reuse the group as a clipPath target and show the path at the same time. For example:
<svg>
<defs>
<g id="group">
<path d="..."></path>
<path d="..."></path>
<path d="..."></path>
<path d="..."></path>
</g>
<clipPath id="test">
<use xlink:href="#group" fill="#000"></use>
</clipPath>
</defs>
<!-- show the stroke of the group -->
<use xlink:href="#group" stroke="#000"></use>
<!-- at same time, clip the rect to make some animation as the background -->
<rect x="0" y="0" width="100" height="100" clip-path="url(#test)"></rect>
</svg>
Because the SVG specification says so
Content model:
Any number of the following elements, in any order:
    descriptive elements
    animation elements
    shape elements
    ‘text’
    ‘use’
Unfortunately <g> elements are not in that list.
Firefox used to support clipping <g> elements a long time ago till we noticed that we weren't a) acting per the specification above and b) compatible with other implementations so we restricted our clipPath implementation to be consistent. So if you got Chrome and Safari on board for a specification change we'd likely be OK with that.
Note that you can work around this by clipping a <use> element that points to a <g> element.

How to mask a portion of a stroked path in SVG?

I'm looking to mask a portion of a straight line in SVG and can really only figure out how to do it one way, but would rather do another because the line lengths will be dynamically generated and the mask portion won't.
Let me explain.
Assume I have a line that is <path d="M0,0 L0,100" stroke="blue" stroke-width="20"/>, I would like to mask with transparency the first 10 pixels, meaning just the d="M0,0 L0,10" portion.
I can do this, which produces the results I'd like:
<svg width="100" height="100">
<rect stroke="black" stroke-width="2" width="100" height="100" fill="yellow"/>
<svg x="10" y="0" width="200" height="200" >
<defs>
<rect x="0" y="0" width="20" height="10" stroke="none"/>
<mask id="chopmask" maskUnits="userSpaceOnUse">
<rect width="20" height="90" x="0" y="10" fill="white"/>
</mask>
</defs>
<path d="M0,0 L0,100" mask="url(#chopmask)" stroke="blue" stroke-width="20"/>
</svg>
</svg>
But the issue is that I can't seem to do the opposite with the rect in the mask, wherein I simply define the it as <rect width="20" height="10" x="0" y="0" fill="white"/> (notice only height and y are different).
Am I missing something on how do define a 10x20 rectangle and have it's mask simply hide a portion of a stroked path, or is this impossible?
If I understood the question correctly, then you need to have a mask in the form of a rectangle of fixed size 10Х20, which will be applied to the line with variable length.
In this case, you can try on a combined mask, one part of which will be opaque fill = "black" and the second part will be transparent fill = "white" and show the rest of the line.
<svg width="100" height="100">
<rect stroke="black" stroke-width="2" width="100" height="100" fill="yellow"/>
<svg x="10" y="0" width="200" height="200" >
<defs>
<mask id="chopmask" maskUnits="userSpaceOnUse">
<rect width="20" height="100" x="0" y="0" fill="white"/>
<rect width="20" height="10" x="0" y="0" fill="black"/>
</mask>
</defs>
<path d="M0,0 L0,100" mask="url(#chopmask)" stroke="blue" stroke-width="20"/>
</svg>
</svg>
An example of animating the line masking process with a rectangle 10 x 20px
<svg width="100" height="100">
<rect stroke="black" stroke-width="2" width="100" height="100" fill="yellow"/>
<svg x="10" y="0" width="200" height="200" >
<defs>
<rect x="0" y="0" width="20" height="10" stroke="none"/>
<mask id="chopmask" maskUnits="userSpaceOnUse">
<rect width="20" height="100" x="0" y="0" fill="white"/>
<rect width="20" height="10" x="0" y="0" fill="black">
<animate attributeName="y" dur="2s" values="-10;0" fill="freeze" />
</rect>
</mask>
</defs>
<path d="M0,0 L0,100" mask="url(#chopmask)" stroke="blue" stroke-width="20"/>
</svg>
</svg>
Works for me ... am I misunderstanding what you're trying to do?
<svg width="100" height="100">
<rect stroke="black" stroke-width="2" width="100" height="100" fill="yellow"/>
<svg x="10" y="0" width="200" height="200" >
<defs>
<rect x="0" y="0" width="20" height="10" stroke="none"/>
<mask id="chopmask" maskUnits="userSpaceOnUse">
<rect width="20" height="10" x="0" y="0" fill="white"/>
</mask>
</defs>
<path d="M0,0 L0,100" mask="url(#chopmask)" stroke="blue" stroke-width="20"/>
</svg>
</svg>

Cut off half of the SVG's text element

How can I make this
to look like this
So I want to halve the text element. I don't want to hide half of the text outside of SVG. Hiding it outside of g would be ok, but haven't found solution.
<svg width="500" height="500">
<g transform="translate(50,50)">
<rect width="80" height="50" style="fill:rgb(0,0,255);"/>
<text font-size="40" x="0" y="15" fill="black">SVG</text>
</g>
</svg>
JSFIDDLE:
http://jsfiddle.net/64nkLcdy/
Use the clip-path property :
<svg width="500" height="500">
<defs>
<clipPath id="myClip">
<rect width="80" height="50" />
</clipPath>
</defs>
<g transform="translate(50,50)">
<rect width="80" height="50" style="fill:rgb(0,0,255);" />
<text font-size="40" x="0" y="15" fill="black" clip-path="url(#myClip)">SVG</text>
</g>
</svg>
Use an <svg> element rather than a <g> as the svg element will clip its contents by default. The overflow property controls clipping i.e overflow="visible" doesn't clip but overflow="hidden" does.
<svg width="500" height="500">
<svg transform="translate(50,50)" width="80" height="50" overflow="hidden">
<rect width="80" height="50" style="fill:rgb(0,0,255);"/>
<text font-size="40" x="0" y="15" fill="black">SVG</text>
</svg>
</svg>

Resources