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 already asked this question here and got an answer, which worked perfectly for me until I've run into a case with intersecting rectangulars
Click only through holes in svg mask
Is there any simple solution to got area around rectangulars clipped out but not area in intersections? It would be perfect to have something like globalCompositeOperation='destination-out' and I've started to think of how to use canvas instead of svg for my problem, but pointer-events which I use for svg are not yet designed to work correct with canvas as far as I know.
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 90 100 v 240 h 140 v -240 z
M 200 290 v 80 h 80 v -80 z" />
</clipPath>
</defs>
<rect y="0" x="0" height="400" width="400" class="over" />
</svg>
In my view, this is a problem of convenience. You would like to be able to use separate paths and treat them the same way whether they interact or not. Unfortunately, because of some limitations in the system, I do not think it is possible to achieve this convenience. However, your quetion itself is easy to answer.
First, a word about what is going on in your example. Your clipPath has a large "outer" path that is drawn clockwise (right, down, left, up). Then you have two "inner" paths drawn counterclockwise (down, right, up, left). The clip works according to what is considered inside and outside the path. The problem is that the intersection is considered outside (see link).
The straightforward solution is to use a single path for every shape, even if it is not a simple rectangle:
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 90 100 v 240 h 110 v 30 h 80 v -80 h -50 v -190 z" />
</clipPath>
</defs>
<rect y="0" x="0" height="400" width="400" class="over" />
</svg>
I was following this tutorial: https://www.youtube.com/watch?v=y0Ic8QcvyK8 and i decided i wanted to pause the animation with the click of a button. I have tried and tried and tried. Searched through w3schools, searched here, searched wherever google sent me too... maybe im just dumb and cant figure it out alone ....
This is the html im using
<!DOCTYPE html>
<html lang="en" onscroll="scroller()">
<head>
<title></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="layout.css">
<script type="text/javascript" src="myscripts.js"> </script>
<script type="text/javascript" src="svg.js"> </script>
</head>
<body>
<button id="b1" class="b3" onclick="change()"></button>
<div class="wrapper">
<svg id="tudo" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1350 620" style="enable-background:new 0 0 1350 620;" xml:space="preserve">
<style type="text/css">
</style>
<g>
<rect x="14.41" y="12.48" class="st0" width="1414.59" height="596.52"/>
<path id="p1" class="st1" d="M22.61,207.89l37.94-26.66c10.4,16.7,25.49,24.32,45.85,24.32c22.27,0,37.21-9.08,37.21-21.68
c0-17.29-28.27-30.91-53.03-43.21c-26.51-13.18-55.66-27.69-55.66-65.33c0-40.87,34.57-67.82,77.93-67.82
c32.23,0,58.89,13.33,69.58,41.89l-35.01,24.46c-7.03-14.06-20.65-19.78-33.84-19.78c-17.72,0-30.91,10.25-30.91,22.41
c0,14.06,17.87,18.9,44.97,31.64c39.11,18.46,63.72,38.38,63.72,74.71c0,43.07-35.16,70.46-85.69,70.46
C67,253.3,38.43,237.19,22.61,207.89z"/>
<path class="st1" d="M220.08,12.48h82.91c45.7,0,83.06,35.89,83.06,79.83c0,43.8-37.35,79.54-83.06,79.54h-35.45v76.76h-47.46
V12.48z M338.58,91.87c0-17.87-15.97-32.37-35.6-32.37h-35.45v65.33l35.45,0.15C322.61,125.13,338.58,110.18,338.58,91.87z"/>
<path class="st1" d="M402.45,130.84c0-67.82,55.22-123.19,122.75-123.19s122.9,55.37,122.9,123.19
c0,67.24-55.37,122.31-123.05,122.31C457.67,253.15,402.45,198.08,402.45,130.84z M600.06,130.55c0-41.31-33.69-75-75-75
c-41.16,0-74.71,33.69-74.71,75c0,41.02,33.54,74.56,74.71,74.56C566.36,205.11,600.06,171.56,600.06,130.55z"/>
<path class="st1" d="M816.56,59.5h-58.01v189.11h-47.61V59.5h-58.15V12.48h163.77V59.5z"/>
<path class="st1" d="M920.86,130.84c0-67.82,55.22-123.19,122.75-123.19s122.9,55.37,122.9,123.19
c0,67.24-55.37,122.31-123.05,122.31C976.08,253.15,920.86,198.08,920.86,130.84z M1118.46,130.55c0-41.31-33.69-75-75-75
c-41.16,0-74.71,33.69-74.71,75c0,41.02,33.54,74.56,74.71,74.56C1084.77,205.11,1118.46,171.56,1118.46,130.55z"/>
<path class="st1" d="M1244.73,59.21v47.61h81.45v47.31h-81.45v94.78h-47.46V11.89h142.38v47.31H1244.73z"/>
<path class="st1" d="M197.81,536.98c0,34.86-25.05,71.63-73.68,71.63H34.92V372.48h64.45c41.89,0,68.55,30.76,68.55,62.55
c0,25.05-16.55,40.58-22.27,40.58C168.37,475.6,197.81,499.04,197.81,536.98z M82.23,418.62v46.14h19.19
c11.28,0,22.27-9.67,22.27-23.44c0-13.33-10.4-22.71-21.39-22.71H82.23z M151.52,533.61c0-13.62-9.38-27.25-31.93-27.25H82.23
v55.96h37.5C135.11,562.32,151.52,551.78,151.52,533.61z"/>
<path class="st1" d="M347.22,608.61l-75.15-111.62v111.62h-47.46l-0.15-236.13h77.05c44.09,0,80.27,33.98,80.27,76.46
c0,30.32-18.31,56.84-43.65,65.77l67.09,93.9H347.22z M271.93,478.83l30.47,0.15c16.11,0.15,30.47-12.89,30.47-29.74
s-14.5-29.74-30.47-29.74h-30.47V478.83z"/>
<path class="st1" d="M571.2,575.65h-94.48l-15.09,32.96h-51.27l112.79-241.99h1.46l112.94,241.99h-51.42L571.2,575.65z
M552.74,535.22l-28.71-63.13l-28.86,63.13H552.74z"/>
<path class="st1" d="M850.98,371.89v241.11h-2.2L708.6,476.63v132.28h-47.46V368.08h2.34l139.89,136.08V371.89H850.98z"/>
<path class="st1" d="M1083.6,490.84c0,73.24-46.58,117.77-122.9,117.77H892V372.63l68.7-0.15
C1037.02,372.33,1083.6,417.16,1083.6,490.84z M1035.7,490.69c0-44.24-28.42-71.19-75.15-71.19h-21.24v141.8h21.68
C1007.43,561.3,1035.7,534.64,1035.7,490.69z"/>
<path class="st1" d="M1102.06,567.89l37.94-26.66c10.4,16.7,25.49,24.32,45.85,24.32c22.27,0,37.21-9.08,37.21-21.68
c0-17.29-28.27-30.91-53.03-43.21c-26.51-13.18-55.66-27.69-55.66-65.33c0-40.87,34.57-67.82,77.93-67.82
c32.23,0,58.89,13.33,69.58,41.89l-35.01,24.46c-7.03-14.06-20.65-19.78-33.84-19.78c-17.72,0-30.91,10.25-30.91,22.41
c0,14.06,17.87,18.9,44.97,31.64c39.11,18.46,63.72,38.38,63.72,74.71c0,43.07-35.16,70.46-85.69,70.46
C1146.44,613.3,1117.88,597.19,1102.06,567.89z"/>
</g>
</svg>
</div>
</body>
</html>
MY CSS
margin: 0 ;
padding: 0;
width: 100%;
}
body {
margin: 0;
padding: 0;
width: 100%;
text-align: center ;
height: 100vh;
display: table ;
background-color: black;
}
.wrapper {
position: absolute;
top: 45%;
left: 50%;
transform: translate(-50%, -50%);
width: 30%;
}
.st0{
fill:none;
}
.st1{
fill:none;
stroke:;
stroke-width:3;
stroke-miterlimit:10;
}
path {
stroke: white ;
fill: #fff;
stroke-dasharray: 300;
opacity: 10;
animation: animate 3s cubic-bezier(0,0.23,1,.1) infinite;
}
#keyframes animate {
0% {
opacity: 0;
fill: none;
stroke-dashoffset: 300;
}
30% {
opacity: 10;
fill: none ;
stroke-dashoffset: 300;
}
90% {
opacity: 50;
/*fill: rgba(255,255,255,1);*/
}
100% {
opacity: 10;
/*fill: rgba(255,255,255,1);*/
}
}
and my JS
function change(){
var button = document.getElementById('b1'),
estado = document.getElementsByClassName("st1");
button.onclick = function() {
estado.style.animationPlayState = "paused";
}
}
Much appreciated !
There are a few things that you got wrong with your JS.
By binding the change() function on the button in your HTML, you're executing it every time the user clicks it, but the function itself binds a click event to your button. Since you're already binding the event in the JS (which is the best practice, by the way), you need to remove the change() function from your HTML and just call it directly, so the click event will be bound only once.
getElementsByClassName, as the plural in the name suggests, doesn't return a single element but rather a collection of elements, which means that you have to loop through the collection to access the style attribute of each element.
I have been struggling for days about this but it seems that I will not solve this on my own. I hope someone can help...or just tell me it is not possible at all and I will find another way :)
Here is a simplified version of my problem:
.left {
fill: yellow;
pointer-events: visible;
}
.left:hover {
opacity: 0.3;
}
.middle {
fill: red;
pointer-events: visible;
}
.middle:hover {
opacity: 0.8;
pointer-events: visible;
}
.right {
fill: blue;
}
.right:hover {
opacity: 0.6;
pointer-events: visible;
}
<svg class="test" width="500px" height="500px">
<g name="Layer" class="group">
<ellipse class="left" cx="120" cy="160" rx="80" ry="81" />
<ellipse class="right" cx="342" cy="271" rx="93" ry="97" />
<ellipse class="middle" cx="223" cy="176" rx="115" ry="153" />
</g>
</svg>
When hovering over an ellipse, its opacity is modified. This is OK.
What I would like to achieve is when hovering over an intersection of two ellipses, the two defined :hover of the concerned ellipses are triggered. Currently, when the mouse pointer is over the red ellipse AND the blue ellipse (in the intersection), only the red one is concerned by the hover.
I cannot group them because:
All 3 ellipses will be considered as hovered all the time
The :hover effect differs
I thought the whole point of 'pointer-events' was to deal with multiple overlapping shapes at a time but I have been trying to use that property in every possible way, without success.
I am using Reactjs so any possible hint on a Javascript solution would help.
I love the solution #Connum came with but I think it can be simplified:
let ellipses = document.querySelectorAll("ellipse")
function getAllElementsFromPoint(rootEl, x, y) {
var item = document.elementFromPoint(x, y);
//in this case is tagName == "ellipse" but you can find something else in commun, like a class - for example.
while (item && item.tagName == "ellipse") {
item.classList.add("hover")
item.style.pointerEvents = "none";
item = document.elementFromPoint(x, y);
}
}
var svg = document.querySelector('svg.test');
svg.addEventListener('mousemove', function(ev) {
// first add pointer-events:all and remove the class .hover from all elements
ellipses.forEach(e=> {
e.style.pointerEvents = "all";
e.classList.remove('hover');
});
// then get all elements at the mouse position
// and add the class "hover" to them
getAllElementsFromPoint(svg, ev.clientX, ev.clientY)
});
.left {
fill: yellow;
}
.left.hover {
opacity: 0.3;
}
.middle {
fill: red;
}
.middle.hover {
opacity: 0.8;
}
.right {
fill: blue;
}
.right.hover {
opacity: 0.6;
}
svg {
border: 1px solid;
}
<svg class="test" width="500px" height="500px">
<g name="Layer" class="group">
<ellipse class="left" cx="120" cy="160" rx="80" ry="81" />
<ellipse class="right" cx="342" cy="271" rx="93" ry="97" />
<ellipse class="middle" cx="223" cy="176" rx="115" ry="153" />
</g>
</svg>
Using getIntersectionList() as demonstrated in this very similar question is probably the cleanest and most performant solution. However, it is not yet supported by Firefox, so I came up with a solution based on a slightly adapted function taken from this answer to another question.
But caution: This is probably very performance-hungry due to the combination of the mousemove event with two forEach loops iterating over DOM elements, combined with the re-rendering that might be caused due to hiding/showing the elements for a minimal amount of time, depending on how the client will handle and optimize this. So this will possibly cause very poor performance on weaker devices. Having said that, it seems to work in all major browsers (tested in Firefox, Chrome and Edge; I haven't tried IE though).
In the comments to the answer in the second link I provided, there's a suggestion for another function using CSS' pointer-events instead of hiding the elements. One would have to compare the performance of those two approaches to decide which one to use best.
function getAllElementsFromPoint(rootEl, x, y) {
var elements = [];
var display = [];
var item = document.elementFromPoint(x, y);
while (item && item !== document.body && item !== window && item !== document && item !== document.documentElement && item !== rootEl) {
elements.push(item);
display.push(item.style.display);
item.style.display = "none";
item = document.elementFromPoint(x, y);
}
// restore display property
for (var i = 0; i < elements.length; i++) {
elements[i].style.display = display[i];
}
return elements;
}
var svg = document.querySelector('svg.test');
svg.addEventListener('mousemove', function(ev) {
// first remove the class .hover from all elements
svg.querySelectorAll('*').forEach(function(subEl) {
subEl.classList.remove('hover');
});
// then get all elements at the mouse position
// and add the class "hover" to them
getAllElementsFromPoint(svg, ev.clientX, ev.clientY).forEach(function(hoveredEl) {
hoveredEl.classList.add('hover');
})
});
.left {
fill: yellow;
pointer-events: visible;
}
.left:hover,
.left.hover {
opacity: 0.3;
}
.middle {
fill: red;
pointer-events: visible;
}
.middle:hover,
.middle.hover {
opacity: 0.8;
pointer-events: visible;
}
.right {
fill: blue;
}
.right:hover,
.right.hover {
opacity: 0.6;
pointer-events: visible;
}
<svg class="test" width="500px" height="500px">
<g name="Layer" class="group">
<ellipse class="left" cx="120" cy="160" rx="80" ry="81" />
<ellipse class="right" cx="342" cy="271" rx="93" ry="97" />
<ellipse class="middle" cx="223" cy="176" rx="115" ry="153" />
</g>
</svg>
I am using the following code from Tympanus to generate a big downward pointing triangle. What I am trying to do is use the same technique to generate a big upward pointing triangle, basic the inverse. Does any one know how to tweak this code to accomplish that?
Your help is greatly appreciated.
Best Regards...
svg#bigTriangleColor {
pointer-events: none;
}
.container svg {
display: block;
}
svg:not(:root) {
overflow: hidden;
}
#bigTriangleColor path {
fill: #3498db;
stroke: #3498db;
stroke-width: 2;
}
<svg id="bigTriangleColor" xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100" viewBox="0 0 100 102" preserveAspectRatio="none">
<path d="M0 0 L50 100 L100 0 Z"></path>
</svg>
You can easily do that understanding the line commands in an SVG path.
What we have here:
<path d="M0 0 L50 100 L100 0 Z"></path>
Says:
Move to (0,0), make a line going to (50,100), make another line going to (100,0), close the path.
So, to invert the triangle, you just need:
<path d="M0 100 L50 0 L100 100 Z"></path>
Which basicaly says:
Move to (0,100), make a line going to (50,0), make another line going to (100,100), close the path.
Check the demo:
svg#bigTriangleColor {
pointer-events: none;
}
.container svg {
display: block;
}
svg:not(:root) {
overflow: hidden;
}
#bigTriangleColor path {
fill: #3498db;
stroke: #3498db;
stroke-width: 2;
}
<svg id="bigTriangleColor" xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100" viewBox="0 0 100 100" preserveAspectRatio="none">
<path d="M0 100 L50 2 L100 100 Z"></path>
</svg>
You could just draw it upside down using a transform.
translate moves it down (as it's now going to be drawn from the bottom to the top rather than top to bottom.
scale inverts it in the y direction
svg#bigTriangleColor {
pointer-events: none;
}
.container svg {
display: block;
}
svg:not(:root) {
overflow: hidden;
}
#bigTriangleColor path {
fill: #3498db;
stroke: #3498db;
stroke-width: 2;
}
<svg id="bigTriangleColor" xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100" viewBox="0 0 100 102" preserveAspectRatio="none">
<path transform="translate(0, 102) scale(1, -1)" d="M0 0 L50 100 L100 0 Z"></path>
</svg>