Creating a triangle connecting a div and a point on an image
I'm trying to sort out the best way to create the lower opacity triangles you see that connect the boxes with the names of the deltas on the left with their locations on the right. The solution only needs to work on larger viewports (once the elements stack the triangles aren't necessary).
My default idea is finding a way to get three points with JS (top and bottom right of the box and then a rough estimation of the point on the map), then create a shape from those three points (somehow). I feel like there might be a way to do it with pseudo elements, but I'm not sure that's actually possible. What's the right approach here?
If it matters, it's setup using grid, and it goes 1 (white boxes), 2 (map), 3 (colored boxes) so that it will stack in the right order on narrower viewports.
13 Replies
I would probably use JavaScript generated SVGs to join the boxes with their locations on the map.
Are the points actual icons/images placed on the map or are they part of the original image? If they are elements placed on the map you can use JS to get their position and use that as one of the "points" of the SVG path. The other 2 poiints (as this is a triangle) would be the top-right and bottom-right of the corresponding label element.
I could do it either way — I was thinking about having them be part of the image just to guarantee they're anchored correctly no matter what, but if they're actual elements it would let people click on them which might be good.
I haven't done much with SVGs (yet). Do you have a resource you might recommend I look at to start?
mdn is a good resource for svg
and inkscape is a somewhat complicated but usable vector editor
i think photopea can do vector, but im not sure
yes, it works with vectors too
you can try it (and yes, it's a website)
Creating an SVG in JS is much like creating any other html element except that, instead of using
createElement
you use createElementNS
You then define the properties a normal.
An SVG is made up of a container and then it's "contents". This might be a path (lines) a shape, (polygon, circle etc.) or other elements.
In your case, as you want a triangle, you would need to define a polygon.
The good thing is that a single SVG can (and normally does) contain many elements so in fact you actually only need a single SVG. This means that you can add all of your triangles within a single SVG element.
I will try to give an outline of how you could achieve this:
In this case you will need the SVG to be able to extend across the whole viewport so it has width and height of 100%
You then need to calculate the positions of your elements, both the starting element and it's corresponding end point. Specifically you need to get the top-right and bottom-right points of your starting element and the left-center point of your image on the map.
You can use getBoundingClientRect
to get the element data which will give you an object containing data amongst which are the top,right,bottom & left points.
You need these to define your 3 points of your polygon, something like this:
As you can (hopefully) see, you should have 3 points within the viewport.
Now you need to generate the polygon to add to the SVG that we have already created:
You now need to add this polygon to the SVG
In the HTML add an empty container where you will add the SVG:
Then finally, back in the JS, append the SVG to this empty container
sorry for all the code 😟
Note - as you have multiple elements that require triangles your will need to create a loop of some sort to generate each separate polygon. My demo code would only look for 2 specific elements.Thank you! I had started working a bunch of this out, but was stuck at setting the points because I couldn't get it to actually set properly
is there a good reason to fully generate it with JS vs putting the elements in the HTML and then updating them?
you are right, you could do that.
However bear in mind that, possibly, your might have more elements or that they might be dynamic.
It could also cause issues on different screen sizes.
This way the JS takes care of the positioning automatically and allows you to have more or less elements without having to touch the code.
I would also add a viewport resize function that recalculates the values if the user changes the size.
But yes, you could hard code them. You are right.
Got it. I had just been thinking about writing one function that updates and hard coding them in, then running the function on page load and on resize.
yes, that would work too
Thank you so much! It's working now (I haven't done all 3 yet, but the first is working)
👏
last question — is using startElementRect just a little easier than using offset math? I'm guessing both work, just trying to make sure I'm also learning/understanding why different choices are better/worse/different
"startElementRect" is just the variable name that I gave to the
getBoundingClientRect()
for the start element.
getBoundingClientRect()
returns the size and position relative to the viewport.
https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
offsetTop
and offsetBottom
(I presume you are referring to those), return the position relative to the nearest (positioned) parent. So, to use these, as you say you will then need to do some math to calculate the corresponding corners and even then probably won't be the position in the viewport.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop
I am sure that somebody with a better understanding of the inner workings of JS can explain it better.Ah, makes sense. I was positioning the SVG exactly under the grid I setup, trying to make the offset from the parent elements the same as what it needs to be for the svg