Click only through holes in svg mask - javascript

I have svg mask which determines holes in rectangular. Behind svg mask I have some clickable elements and I would like to pass events to them, but only through holes. I've experimented with pointer-events values, but I can only make either whole mask to pass events or whole mask to capture them. For one hole it can be simply done using clip-path, just determining outer part of the hole, but several holes make things more difficult. Is there any possibility to avoid using clip-path? I also tried pointer-events: visiblePainted and pointer-events: painted, but had no success.
.background {
width: 400px;
height: 400px;
background: red;
cursor: pointer;
}
.svg {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
<button class="background">
</button>
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg" class="svg">
<defs>
<mask id="mask">
<rect
x="0"
y="0"
width="400"
height="400"
fill="white"
/>
<rect
x="20"
y="20"
width="40"
height="40"
fill="black"
/>
<rect
x="290"
y="290"
width="40"
height="40"
fill="black"
/>
</mask>
</defs>
<rect
x="0"
y="0"
width="400"
height="400"
fill="black"
opacity="0.5"
mask="url(#mask)"
pointer-events="auto"
/>
</svg>

There are several aspects to this problem. First, you are right the behavior of masks and clip-paths is different in relation to hit-testing.
A clip path is a geometric boundary, and a given point is clearly either inside or outside that boundary; thus, pointer events must be captured normally over the rendered areas of a clipped element, but must not be captured over the clipped areas... By contrast, a mask is not a binary transition, but a pixel operation, and different behavior for fully transparent and almost-but-not-fully-transparent may be confusingly arbitrary; as a consequence, for elements with a mask applied, pointer events must still be captured even in areas where the mask goes to zero opacity.
Second, a clip-path is a geometric shape, but just like all paths, it might contain holes. Instead of three <rect>s, you can use one <path> with three subpaths, as long as the clip-rule makes sure the subpaths inside get cut out of the surrounding shape.
Third, if the pointer-events property is applied to an <svg> element in a HTML context, its behavior becomes...strange. Any other value than pointer-events: none on the <svg> element lead to the whole bounding box receiving events - a behavior proposed for HTML elements, but currently not part of any spec.
The solution here is to set pointer-events: none on the <svg> element, and then to reverse that with pointer-events: painted on the child <rect> element.
button, svg {
position:absolute;
width:400px;
height:400px
}
button {
background: #0000ff;
cursor: pointer;
}
button:hover {
background: #008800;
}
svg {
pointer-events: none;
}
.over {
fill: #000;
clip-path: url(#clip);
pointer-events: painted;
}
<button></button>
<svg xmlns="http://www.w3.org/2000/svg" height="400" width="400">
<defs>
<clipPath id="clip" clip-rule="evenodd">
<path d="M 20 20 h 360 v 360 h -360 z
M 40 40 v 40 h 40 v -40 z
M 200 290 v 40 h 40 v -40 z" />
</clipPath>
</defs>
<rect y="0" x="0" height="400" width="400" class="over" />
</svg>

Clip masks are useful for cropping parts out of complicated objects, but if you're just working with blocks of solid colour then maybe it would be just as easy to create shapes that already have holes in them.
I've added an example below. Does this help?
<svg width="400" heoght="200" viewBox="0 0 400 200">
<text x="100" y="100" text-anchor="middle"
alignment-baseline="middle" onclick="alert('Hello!')"
style="cursor:pointer">Click me</text>
<text x="300" y="100" text-anchor="middle"
alignment-baseline="middle" onclick="alert('Hello?')"
style="cursor:pointer">Not me</text>
<path d="M20 20 180 20 180 180 20 180ZM60 60 60 140 140
140 140 60Z" fill="#3a6" fill-opacity="0.7"
fill-rule="nonzero"/>
<path d="M220 20 380 20 380 180 220 180Z" fill="#f20"
fill-opacity="0.7"/>
</svg>

Related

Making inline SVG responsive in browsers without using javascript?

I've been reviewing articles like this one which are really good, but possibly dated, and I'm wondering if any progress has been made on a simple way to make inline svg responsive while maintaining the aspect ratio?
In other words if we have svg with a 1:1 aspect ratio inside a div container, and the container shrinks from 400x400px to 200x200 px, then the view box width and height parameters double.
I am confused about what you want. I don't know what you mean, in your question, when you talk about "view box width and height parameters double". Perhaps you are confused about how viewBox works.
As long as the SVG has a viewBox it should be responsive.
In the following example, I've put the SVG in a div container and animated the container size to simulate the page size changing.
#container {
width: 200px;
background: linen;
animation: scale 1s alternate infinite;
}
#keyframes scale {
from { width: 200px; }
to { width: 100px; }
}
<div id="container">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="gold"/>
<rect x="10" y="50" width="20" height="50" fill="gold"/>
<rect x="70" y="50" width="20" height="50" fill="gold"/>
<circle cx="35" cy="45" r="5"/>
<circle cx="65" cy="45" r="5"/>
</svg>
</div>
But it could just as easily be a container that is the width of the page. Try running the following snippet. Then click "Fullpage" and resize the browser window.:
#container {
width: 100%;
background: linen;
}
<div id="container">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="gold"/>
<rect x="10" y="50" width="20" height="50" fill="gold"/>
<rect x="70" y="50" width="20" height="50" fill="gold"/>
<circle cx="35" cy="45" r="5"/>
<circle cx="65" cy="45" r="5"/>
</svg>
</div>
If you don't need to change svg with css or modify it, I suggest you to use it as an img element. You create an img and put the path to your svg in the src. Here's what I mean: https://codepen.io/tlemaitre/pen/PBKGOo
I've recently had a similar problem, I have a container that needs to be preserveAspectRatio="none" to keep the shape fluid on different window sizes but the image inside gets gets distorted. I've not seen any solution to this problem with or without JS, so I don't think it's possible.

Hover events for a dashed line in an SVG

An SVG with a line (or path) which uses stroke-dasharray only seems to trigger CSS and JS hover events when the user hovers over the solid parts of the dashed line: https://codepen.io/anon/pen/YeXoZy
Is there a simple way to make both the JS and CSS events trigger when the solid or invisible parts of the line are hovered?
My current plan is to draw a second, invisible line following the same path and use it to detect mouse events. https://codepen.io/anon/pen/BYNgRR This seems heavy handed and I'm hoping there's a cleaner way I'm missing.
I'm not sure how to do it without the second "detector" line, but a less heavy handed way is at least possible without the JS.
Switch the order of the lines, then you can use the hover selector as usual for the dashed line, then use + in a selector for the detector line to change the properties of the line immediately following it:
https://codepen.io/RyanGoree/pen/LQVKBV
This can be solved somewhat by using a rect instead of line and using SVG transforms with patterns.
An example can be seen at this CodePen.
It essentially bubbles down to:
<svg height="210" width="500">
<defs>
<pattern id="pattern1"
width="10" height="10"
patternUnits="userSpaceOnUse"
patternTransform="rotate(0 60 60)">
<line stroke="green" stroke-width="12px" y2="10"/>
</pattern>
<pattern id="pattern2"
width="10" height="10"
patternUnits="userSpaceOnUse"
patternTransform="rotate(0 60 60)">
<line stroke="red" stroke-width="12px" y2="10" stroke="transparent"/>
</pattern>
</defs>
<g transform="rotate(45 60 60)">
<rect x="0" y="0" width="500" height="5"/>
</g>
</svg>
And the following CSS:
rect {
fill: url(#pattern1)
}
rect:hover {
fill: url(#pattern2)
}
This is an old topic I know. But found the answer on Css hover sometimes doesn't work on svg paths
If you want to trigger the event only on stroke/visible, use pointer-events: stroke; or pointer-events: visible; (only the stroke) and pointer-events: all; (on both)
Here is a code example:
<body>
<div id="donut-score" class="svg-item" style="display: block;">
<svg width="100%" height="100%" viewBox="0 0 40 40" class="donut">
<circle class="donut-hole" cx="20" cy="20" r="15.91549430918954" fill="#fff"></circle>
<circle class="donut-ring" cx="20" cy="20" r="15.91549430918954" fill="transparent" stroke-width="3.5"></circle>
<circle id="donut-score-part-1" class="donut-segment donut-segment-1" onmousemove="this.style.stroke ='orange';" onmouseout="this.style.stroke = '#ff8197';" cx="20" cy="20" r="15.91549430918954" fill="transparent" stroke-width="3.5" stroke-dasharray="20 80" stroke-dashoffset="25"></circle>
<g class="donut-text-item donut-text-item">
<text y="50%" transform="translate(0, 2)">
<tspan id="donut-score-text" x="50%" text-anchor="middle" class="donut-text">Score </tspan>
<tspan id="donut-score-aantal" x="50%" Y="65%" text-anchor="middle" class="donut-text">0 </tspan>
</text>
</g>
</svg>
</div>
</body>
ccs:
.donut-segment {
stroke: #ff8197;
}
.svg-item {
width:200px;
font-size: 16px;
margin: 0 auto;
}
.donut-text {
font-size: 0.35em;
line-height: 1;
transform: translateY(0.5em);
font-weight: bold;
}
If you add pointer-events: stroke; to the class .donut-segment then it only works on the stroke. If you use non or pointer-events: all it works on both. I tested it in codepen.

Am I handling SVG's correctly?

I have a square div with two triangles drawn via SVGs within that looks like this :
I then have a button that once the onClick event is triggered it will add a class called hidden just basically has : display: none; visibility: hidden; to hide the bottom triangle, then displays two more triangles like so :
My current code looks like :
SVG to draw my triangle.
<svg id="triangle" width="100%" height="100%">
<path d="M0,0 L680,0 L0,680 Z " id="top_triangle" class="o-top_main"></path>
</svg>
Then the image for me to be able to use a background image :
<svg style="position: absolute;">
<defs>
<pattern id="image_top" width="1" height="1" viewBox="0 0 100 100">
<image id="svg_img_top" xlink:href='http://41.media.tumblr.com/tumblr_m8r9k312PE1qc6mk8o1_500.jpg' width="100" height="100" preserveAspectRatio="none"></image>
</pattern>
</defs>
</svg>
I've been drawing the SVG path like such <path d="M0,0 L680,0 L0,680 Z ". I've tried drawing an SVG triangle in photoshop, exporting out as an SVG then getting the path in the code and pasting in but the dimensions are never correct so I end up editing the path to the point where i've redrawn it anyway.
Is there anyway way I can draw an SVG triangle in Photoshop where I can then paste the path into my site without needing to change the path to the point where i've redrawn it?
My end site kind of looks similar to this :
Lets take a look at your first image here:
Now usually you allways want to create an viewBox on any svg element.
Width and height are usually set in css, but there are lots of cases where setting it in inline is better.
Now lets see your drawing 2 triangles with paths so and then adding another:
My suggestion is drawing 3 triangles then changing the color:
Update:
now with image url instead of colors. (the svg is getting quite complicated now)
var bottom = document.getElementById("tri_tri");
bottom.addEventListener("click", function() {
this.style.fill = "yellow";
this.style.stroke = "yellow";
});
/**/
.triangleArt {
width: 250px;
}
#tri_one {
fill: firebrick;
}
#tri_two {
fill: url(#image_top);
stroke: url(#image_top);
}
#tri_tri {
fill: url(#image_top);
stroke: url(#image_top);
cursor: pointer;
}
<svg viewBox="0 0 100 100" class="triangleArt" xmlns="http://www.w3.org/2000/svg" xmlns:x="http://www.w3.org/1999/xlink">
<defs>
<pattern id="image_top" patternUnits="userSpaceOnUse" width="250" height="156">
<image id="svg_img_top" xlink:href='http://41.media.tumblr.com/tumblr_m8r9k312PE1qc6mk8o1_500.jpg' x="0" y="0" width="250" height="156"></image>
</pattern>
</defs>
<path id="tri_one" d="m0,100 100,-100, -100,0z" />
<path id="tri_two" d="m100,0 0,100 -50,-50z" />
<path id="tri_tri" d="m100,100 -50,-50 -50,50z" />
</svg>

Change opacity of an area based on mouse position

Is it possible to change the opacity of background but only underneath the cursor area (for example a white small circle)? I am thinking of it a bit like a basic heatmap but the heat doesn't stay - it just follows the cursor.
At the moment I have the following
HTML:
html {
background-color: #000;
width: 100%;
height: 100%;
}
JS:
$(document).mousemove(function(event){
var i = event.pageX.toPrecision(1) / 1000;
$("html").css('opacity', i)
});
Sorry this is probably a very basic starting point. Would I need to use canvas?
You can do that using svg
What i did :-
I placed two same images with same co ordinates,height and width and gave a circular clip-path to the one on top (which has full opacity) when mouse moves the position of the circle also changes
$('svg').on('mousemove',function(e){
$('.a').attr('cx',e.pageX).attr('cy',e.pageY)
})
.one{
opacity:.5;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg width="500" height="500">
<clippath id="clip" >
<circle cx="50" cy="50" r="50" class="a"/>
</clippath>
<image xlink:href="https://images.unsplash.com/photo-1474575981580-1ec7944df3b2?dpr=1&auto=format&fit=crop&w=1500&h=934&q=80&cs=tinysrgb&crop=&bg=" width="500" height="500" x="0" y="0" class="one"/>
<image xlink:href="https://images.unsplash.com/photo-1474575981580-1ec7944df3b2?dpr=1&auto=format&fit=crop&w=1500&h=934&q=80&cs=tinysrgb&crop=&bg=" width="500" height="500" x="0" y="0" clip-path="url(#clip)"/>
</svg>

Looking to combine CSS fill color and SVG pattern on an SVG element

I'd like to use the awesomeness of CSS to style SVG elements in combination of 2 things: fill color and texture. My textures are created using SVG patterns that have a stroke but no fill. But even though the pattern has no fill it still won't allow a CSS fill color to be visible through the strokes.
http://jsfiddle.net/9MTB6/
A segment from the fiddle:
.texture_diagonal{
fill: url(#texture_diagonal);
}
.cell_default{
fill: #cccccc;
}
.cell_selected{
stroke-width: 2px;
stroke: #FF0000;
}
<pattern id="texture_diagonal" x="0" y="0" width="25%" height="25%" patternUnits="objectBoundingBox">
<path d="M 0 0 l 32 32" style="stroke: black; fill: none;"/>
</pattern>
<rect x="98" y="115"
width="32" height="32"
class="texture_diagonal cell_default cell_selected"
/>
In the fiddle example, I show how I'd like to combine CSS classes so that the third row of rectangles can have both a 'texture' pattern and a fill color. It would be too tedious to define an SVG pattern for each combination (ex: 4 textures X 3 fill colors x 2 selected/unselected = 24 patterns needed!). So my question:
Can I make the pattern behave like a transparent PNG? (so the empty white portion of the pattern allows the fill color to show beneath)?
------- EDIT:
My last idea before I resort to Peter's solution:
<defs>
<pattern id="texture_diagonal" x="0" y="0" width="25%" height="25%" patternUnits="objectBoundingBox">
<path d="M 8 0 l 0 32" style="stroke: black; fill: none;"/>
<path class="myfill" d="M 0 0 l 0 32 l 32 0 l 0 -32 l -32 0"/>
</pattern>
</defs>
<rect x="20" y="20" width="32" height="32"
class="texture_diagonal cell_connected"/>
<rect x="59" y="20" width="32" height="32"
class="texture_diagonal cell_default"/>
Is there any way to use CSS combination of selectors to target the 'myfill' path when it's in different contexts (cell_connected versus cell_default)?
You can't do quite what you want because when you set the fill to the texture you overwrite the original fill. The only way I can see around this is to write two rects on top of each other and only texture the top one.
For example:
<rect x="20" y="115" width="32" height="32"
style="stroke: black;"
class="cell_default" />
<rect x="20" y="115" width="32" height="32"
style="stroke: black;"
class="texture_vertical" />
It's not ideal, but it works.

Categories