using document.elementFromPoint to get nearby path - javascript

For my website, I want the users to be able to click on various svg elements.
I've coded up a quick function to let users click on enclosed shapes. I basically just run document.elementFromPoint(x, y) on them. This works great for any enclosed shape, since they have clickable area. Open paths, however, are a pain to click because they're only 1-2 pixels in size.
I would like to allow the user to select them by clicking 'close enough' to it, such that if the click is, for example, 10 or fewer pixels away from the path, the path would be selected. I could write a mathematical algorithm for this (using Ray-Casting that only goes 10 pixels out for example, or turn points into 10px-radius circles and check for intersections with the path), but my intuition tells me there must be an easier way, perhaps via CSS.
One idea I was thinking of is if the stroke for this path was 10 pixels thick for selection purposes, but only 1 pixel thick visibly (or having a 'padding' that would apply to the path rather than the bounding box). I've also looked at pointer-events, but aside from bounding box (which I would rather not use to avoid selecting other shapes) I don't see any useful settings there. Is there something I could leverage in JavaScript or CSS that can help me accomplish this without doing the math for the paths myself (since it may get ugly for cubic beziers, etc.)?
I should also mention that this is an HTML5 app, so I don't care about backwards-compatibility.

If the paths have no stroke then you can create an invisible stroke (stroke-opacity="0") and use pointer-events="all" to give the shape an extended hit area.
If the paths do have strokes then you need to create a second invisible path on top of the visible path with the same coordinates but a bigger stroke-width. You can then use pointer-events="all" to make this invisible path the hit target and if it is hit you react in the event handler as if the visible shape was hit.

Robert Longson describes how you can do this, but I wanted to add some more details.
First, you don't need to duplicate or create new geometry if you don't want to. You can also use a <use> element. You can use some css to give shapes a different look even when some other invisible element is hovered.
<style>
g { fill: #ddd; stroke: black; pointer-events: none; }
use { pointer-events: all; stroke-width: 40; fill: none; stroke-opacity: 0; }
use:hover + * { fill: cornflowerblue; }
<g>
<use xlink:href="#c"/>
<circle id="c" cx="50" cy="60" r="20"/>
</g>
See live example.

Related

How do I set presentation attributes of Image tags that can be found within SVGs (Files or Inline)? [duplicate]

This question already has answers here:
How can I change the color of an 'svg' element?
(40 answers)
How to change color of SVG image using CSS (jQuery SVG image replacement)?
(18 answers)
Closed last year.
This post was edited and submitted for review last year and failed to reopen the post:
Original close reason(s) were not resolved
I am writing an SVG editor program. I am trying to implement highlighted feel for when an element (<image>, not <img>) is clicked. I have implemented this for other elements like <circle>, by setting stroke, stroke-width, and stroke-dash-array. But setting the presentation attributes of the <image> tag, either by setting the attributes directly or through CSS styling, is not taking effect. I also tried the border style but it's not working. How can I achieve this?
According to MDN , <image> is meant to have have global attributes, including presentation, which ultimately means this should work. If you also inspect <image> with a browser you will see it will highlight like it follows box model.
<svg viewBox="0 0 1370 1170" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image width="128" height="128" x="617.9207153320312" href="data:some-valid-link" style="stroke-width: 3px; stroke-dasharray: 7px;" stroke="#3aa2c2"></image>
</svg>
I don't think that you can set stroke on an image.
Usually those attributes are used to 'draw' vector image. Here you're trying to put a stroke on a raster image.
If you want to put lines around a square image you could use a border to achieve it.
If you want to put lines around a complicated shape, I don't think it's possible to do that in CSS. You would need to add that in graphics software.
You cannot restyle an external SVG, if you load it in an <img> tag.
Instead, you could paste the <svg> code directly into your HTML, and apply styles like this (source svg):
svg {
width: 500px;
}
svg path,
svg polygon {
stroke-width: 10px;
stroke-dasharray: 10px;
}
<svg height="210" width="500">
<polygon points="100,10 40,198 190,78 10,78 160,198" style="fill:lime;stroke:purple;stroke-width:5;fill-rule:nonzero;"/>
Sorry, your browser does not support inline SVG.
</svg>
You would need and SVG with a more specific code targeting all the vectors and not just a tag. Then you could make groups of the parts of the image/draw you want to style. You can do this by using illustrator, then after grouping the parts, your code will have a 'g' tag: <g id="group_to_style" > with an ID that you have named when doing the groups. Then using CSS you can target the groups of the SVG and style or even animate. Like gru said, with an you cant style or animate anything.

Is it possible to determine whether a shape is under a certian mask in SVG by JavaScript or even CSS?

I have many <rect> inside a <g> tag in my SVG.
And the <g> group is under a circle mask.
The result looks like this:
However, I would like to know, whether one <rect> is inside mask or not, for example:
This rect is inside the mask:
This rect is outside the mask:
This rect is half inside, half outside.
I want to assign different styles to them according to there masking status. For example:
For the shapes inside the mask, I want to add class .inside to them.
For the shapes outside the mask, I want to add class .outside to them.
For the shapes half inside the mask, I want to add class .half to them.
Currently, I can only calculate by using coordinate functions but what if the mask is not a circle?
Is there any way I can achieve this?

Angular SVG Path Not Visible

I am working on a project where I have a page that lists job positions and I need to visually show progression (how to get from one job to another) using lines/arrows so the user can follow along. I created a page that contains all the data for this and now I am trying to use SVG paths to create lines on top of the data and draw paths.
I found some code in another answer that I converted to pure JS and removed the jQuery references to be able to use it in my project.
Here is the working fiddle with one path example:
http://jsfiddle.net/x4nmqkLj/
Here is my attempt:
http://jsfiddle.net/szrdb263/
My issue that I am facing is that the SVG and Path appear to be created BUT, I am unable to see the line/path on the DOM. However if I view the elements in the web tools, I can see the SVG element and path element and they appear to be in the correct location to where the path would be drawn from the starting and ending position.
My rendered path is as follows:
<path class="path" id="path1" d="M449.953125 512.34375 V568.03125 A55.6875 55.6875 0 0 0 505.640625 623.71875 H765.515625 A55.6875 55.6875 0 0 1 821.203125 679.40625 V1055.3203125"></path>
Here is the SVG Element:
Here is the Path Element:
The path should be drawing a line from the first box 1A to the bottom row, second box 2D. The path appears to be where its expected, the bottom center of the first box to the top center of the bottom box where the arrow would be drawn.
CSS:
#svgContainer {
z-index: -10;
opacity: 0.5;
margin: 2.5em 2.5em;
position: absolute;
background-color: #999;
}
path {
fill: none !important;
stroke: #000 !important;
stroke-width: 0.7em !important;
}
Is anything angular specific that I am missing causing this to not show up?
I am not terribly familiar with SVG but seeing both the SVG element and Path element in the DOM makes me think its pretty close. I can't imagine the arrow is outside of the DOM anywhere but I may be wrong.
Update:
I pasted my path code in a few "validator" sites for SVG to see if it would draw it and I am not getting any visual indication. This would suggest that there is something wrong with the coordinates that it is using to create the path. However, the same code is being used on the working example so I am wondering if this is an angular quirk after all with manipulating the DOM of sorts.
Update 2:
I believe I have solved the issue. I had to change how I was getting the ending coordinates in the JS.
Before:
// calculate path's end (x,y) coords
var endX = endCoord.offsetLeft + 0.5 * endElem.offsetWidth - svgLeft;
var endY = endCoord.offsetTop - svgTop;
After:
// calculate path's end (x,y) coords
var endX = endCoord.getBoundingClientRect().left + 0.5 * endElem.offsetWidth - svgLeft;
var endY = endCoord.getBoundingClientRect().top - svgTop;
New Fiddle: http://jsfiddle.net/8dumowvt/
I got your example working by doing the following:
Replaced offsetTop and offsetLeft by getBoundingClientRect().top and getBoundingClientRect().left in connectElements()
Adjusted style of svgContainer to give it a positive z-index, so that the svg is drawn over the other elements
Added pointer-events: none; to the svgContainer so that you can still interact with the elements behind it
Here's the updated Fiddle.

How to combine two SVG paths together (without spaces)

I was able to merge two separate paths together using this technique. However, my animation is still treating this as two separate paths.
Is there a way to combine these two paths without using spaces?
M3322.09,361.23V473.45c0,14,2,23.41,23.41,23.41H3809.63
M3809.63,496.31c21.41,0,166.41-1,166.41-1s13.07.87,82.08.87c31.75,0,63.51-36.21,95.26-75.31"/>
svg {
fill: none;
}
path {
stroke: tomato;
stroke-width: 100;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 25 14905 623">
<path d="M3322.09,361.23V473.45c0,14,2,23.41,23.41,23.41H3809.63 M3809.63,496.31c21.41,0,166.41-1,166.41-1s13.07.87,82.08.87c31.75,0,63.51-36.21,95.26-75.31"/>
</svg>
The originally separate paths can be viewable here:
svg {
fill: none;
}
path {
stroke: tomato;
stroke-width: 100;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 25 14905 623">
<path d="M3322.09,361.23V473.45c0,14,2,23.41,23.41,23.41H3511.9" />
<path d="M3809.63,496.31c21.41,0,166.41-1,166.41-1s13.07.87,82.08.87c31.75,0,63.51-36.21,95.26-75.31" />
</svg>
The goal is to merge these paths to match the above svg snippet -- without using spaces in the path.
I found an awesome online editor for path manipulations:
yqnn.github.io/svg-path-editor/
Paste your path segments
[optional] Check parsed path commands (e.g. Remove the M between segments)
[optional] Convert to either 'abs' or 'rel' commands
[optional] Set 'minify output' checkbox
Copy output
m3322.09 361.23v112.22c0 14 2 23.41 23.41 23.41h464.13c21.41 0 166.41-1 166.41-1s13.07.87 82.08.87c31.75 0 63.51-36.21 95.26-75.31
The answer is
M3322.09,361.23V473.45c0,14,2,23.41,23.41,23.41H3809.63 c21.41,0,166.41-1,166.41-1s13.07.87,82.08.87c31.75,0,63.51-36.21,95.26-75.31
H3511.9 means draw a horizontal line until x point at 3511.9 (with whatever y was previously inherited)
M3809.63,496.31 means move the "cursor" to an x, y coordinate.
I changed H3511.9 to H3809.63 and removed M3809.63,496.31 and continued with c21.41... which is a draw curve command.
These resources helped me to understand the draw commands for the d path attribute.
https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
https://www.youtube.com/watch?v=k6TWzfLGAKo
I found the easiest solution was:
Open the SVG file in Inkscape (Free software, cross platform https://inkscape.org)
Select the paths to merge
From the Path menu, choose Union
Save the file
For those who came upon this question while researching to make sure there would be no problems from merging paths like I was, the problem here is just a typo.
In the above merged snippet, the ending of the first path was changed from its original 23.41H3511.9 to 23.41H3809.63.
Putting paths together by putting a space (or no space at all, just don't have a comma between the paths when placing them in the d attribute value) should always work without modification.
(On a side note, Raphael's answer made SVGs a lot less cryptic to me now after reading his links and tips)

Put Raphael (SVG) canvas behind other divisions to make them clickable?

I am using Raphael to create lines between divisions in an organization chart (or flow chart), but I need to be able to actually click on the divisions and content behind it.
If I could make the canvas be behind the other elements, kind of like a background image, that would be idea. Is this possible?
I found a solution. Raphael makes an SVG canvas that is absolutely positioned in my case. Absolute positions act as layers, and so to be on top of that layer, my content had to be absolutely positioned as well.
If someone else has a better solution, I would be happy to hear it, though this is working fine.
What I do is create a layer of invisible (but clickable) shapes on top of the informational lines being rendered, which will act as the target area for the content below.
If your lower layers being target are being created in Raphael you can easily clone them, set the opacity to 0, and position that layer to the top. (See Sets Reference for a way to easily group the layers together.)
Example:
#el = #parent.paper.rect(x,y,w,h); //your existing lower layer shape definition
#elTrigger = #el.clone(); //clone your existing shape
#elTrigger.attr
fill: '#fff'
opacity: 0
cursor: 'pointer'
#elTrigger.click(someAction); //assign the function
If you're lower layer isn't being rendered by Raphael (just HTML) you could still do something similar, but it would require just creating new (transparent) shapes to sit on top of the approximate coordinate of the targets below.

Categories