Drawing a diagonal line in HTML/CSS/JS (with jQuery)
A simple and practical post this time. How do you draw a diagonal line in your JavaScript-powered web-application that uses HTML / CSS (and no “canvas”, since it is not trivial to keep it coordinated with regular HTML elements on the page).
This uses jQuery, but the same principles can be applied to pure JavaScript or with another library.
In my case, I have an organisation chart with nodes, which allows the user to connect the nodes visually. A way I solved it so far is have a “link” icon on each node, when the user presses it they switch to “link mode” — an on-screen line starts at the origin node and follows the mouse cursor, until the user clicks on another node (or right-clicks / presses Escape to exit the link mode).So how do I draw that line between arbitrary points A and B on the webpage?
The answer: Just draw it and rotate it.
Warning — details below may contain traces of math, please be careful if you're allergic to it. Though its density is so low that it is unlikely to hurt anybody, honest!
Details
First of all we need to establish that HTML blocks are rectangles. So the original line will be a rectangle of desired length and width. Since it will be rotated, length is a calculated value.Here's the initial CSS style for this link line:
#new-link-line { position: absolute; /* allows to position it anywhere */ width: 3px; /* your chosen line width */ background-color: #06a; /* line color */ z-index: 1000; /* make sure this is above your other elements */ -webkit-transform-origin: top left; -moz-transform-origin: top left; -o-transform-origin: top left; -ms-transform-origin: top left; transform-origin: top left; /* transform-origin sets rotation around top left (instead of the geometrical center) */ }
This is a 3-pixel-wide rectangle with no height set. Height will be the length of the link line, so to complete the task we need to determine its length and the rotation angle.
The battle plan |
As you can see from above, we're dealing with a so called “right triangle”, and it is very easy to calculate various lengths / widths / angles for it knowing what we know — namely, the coordinates of the points A and B. Our link line is the green side.
For my particular task this code is called from the mouse move event, so I'll assign the handler:
// Assign the mouse move event $(document).mousemove(linkMouseMoveEvent);
Length is a square root of the sum of squares of the red sides from the picture above. Each side is the absolute value of difference of coordinates: Xa - Xb and Ya - Yb, thus the following code sets the required length:
function linkMouseMoveEvent(event) { // Initialize values of originX and originY // … var length = Math.sqrt( (event.pageX - originX) * (event.pageX - originX) + (event.pageY - originY) * (event.pageY - originY)); $('#new-link-line').css('height', length);
I assume that top and left properties of the #new-link-line are already set to correct values — it would be inefficient to do that on every mouse move.
Next and last step is to calculate the rotation angle. It uses fairly simple trigonometry, yet I am aware that that word itself (trigonometry!) makes some people scream in fear, so if you're interested you can deduce the formula from the code below:
var angle = 180 / 3.14 * Math.acos((event.pageY - originY) / length); // Negate the angle if mouse pointer is to the right of the origin point if(event.pageX > originX) angle *= -1; $('#new-link-line') .css('-webkit-transform', 'rotate(' + angle + 'deg)') .css('-moz-transform', 'rotate(' + angle + 'deg)') .css('-o-transform', 'rotate(' + angle + 'deg)') .css('-ms-transform', 'rotate(' + angle + 'deg)') .css('transform', 'rotate(' + angle + 'deg)');
This code should be inserted into the linkMouseMoveEvent function.
That's it!
If you're interested, here's how I exit the link mode by right-click or Escape button press:
// Cancel on right click $(document).bind('mousedown.link', function(event) { if(event.which == 3) endLinkMode(); }); $(document).bind('keydown.link', function(event) { // ESCAPE key pressed if(event.keyCode == 27) endLinkMode(); }); function endLinkMode() { // Whatever needs to be done to exit link mode // … }
Note that both binds are to events with “.link” suffix — this allows to easily unbind them when needed, not touching the other potential handlers of the same events.
Here's a working example (I hope this works, blogger.com doesn't exactly make it easy to include JS files on pages!), also posted on jsFiddle:
Click!
This approach allows to even set shadows and borders to the line. One downside is that it won't work below IE9 — so you may need to look for alternatives if your environment forces you to support things like IE8 (about 11% of total Internet population, so could be important in some cases).
Quite a lot of code in this post, please let me know if I got anything wrong — or if it can be done better!
Great work! Any ideas on how this could be extended to "targets" too? Say e.g. if you wanted to do a game for children that pairs together some animals and some fruits (by drawing lines between them).
ReplyDeleteHi!
DeleteIn order to handle this, I add the following event to the objects which the link will be connected to:
$('.node').bind('click.link', linkSelectNodeEvent);
And then in that function:
// check for left click
if(event.which == 1) {
// .selected class is added on initiating linking mode
var source = $('.node.selected');
var target = $(this);
// .... and continue with your logic
I hope this helps :)
Hi iam a beginner so i coudnt get this properly ..to hit the target circle what should i exactly do ?? pls do help
DeleteThanks for sharing. You solved my doubt :)
ReplyDeleteHow are you going to make it work in IE8?
ReplyDeleteYes, it is a good question, and I haven't looked for a solution yet — CSS3 transforms don't work there, yet I did see mentions of filters which could allow rotating an element.
DeleteI will post an update if I find a working solution.
Is there any way to stop a line without removing it?? i was trying to implement the Anton Maslo example but i cannot reach the target, i would like to stop the line as soon as i click on another div so the line shd be link both without removing.
ReplyDeleteAny help will be really appreciated
Thx Guys.
BTW very good example
Hi Robert!
DeletePlease check the source of this page, you'll see that I have removing code in the endLinkMode function, as follows:
function endLinkMode() {
$('#new-link-line').remove();
$(document)
.unbind('mousemove.link')
.unbind('click.link')
.unbind('keydown.link');
}
If I understand your question correctly, then you just need to do something instead of removing the line — perhaps create a connector line (instead of #new-link-line, in case you need to reuse it), or just leave it be.
I hope this helps!
Anton
This was handy. I used it in this demo I made http://jsbin.com/owakew/4/edit
ReplyDeleteLooks great, Aaron, thanks for the feedback!
DeleteHey this is cool. Thanks for sharing.
ReplyDeleteI am working on similar thing. I want to generate diagonal line as I scroll my page. I dont want to use mouse event. Can you please help me with this. Thanks in advance.
Thanks for the kinds words. I don't really understand what you try to achieve, so can't really help, sorry. There are scroll events that you can hook into, if that is what you need.
DeleteAwesome, helped me a lot. Thank you.
ReplyDeleteI believe there is a typo above. The code
ReplyDeletevar length = Math.sqrt(
(event.pageX - originX)
* (event.pageX - originY)
+ (event.pageY - newLinkStartTop)
* (event.pageY - newLinkStartTop));
The third line should be:
* (event.pageX - originX)
(it's this way in the jsFiddle)
Also, I think the variable newLinkStartTop is confusing, as it's never defined anywhere in this explanation. in the jsFiddle, it's using origin, as it should.
Just wanted to point this out. But thank you again for this demonstration, it's proving very helpful for my current project!
Hi Corey, thank you so much for your attentive feedback — I've corrected both issues, really appreciate you helping me improve this post!
DeleteFixed the “click” button demo, looks like last corrections to the post re-formatted some of the included JS code.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteHey there, thank you so much for this example! It helped me get through one part of a project I'm working on. I fiddled around a bit with it to make it do the "connected" behavior I was aiming for.
ReplyDeleteThanks again!
Hi Elliot, I am really glad I could be of service, and cool to see what you did with the fiddle, good job!
DeleteHey Anton, nice Tutorial u have there!!
DeleteBut i have some Problems wheni try to implement in a own website, what references i have to link in my html file? because it isnt working for me.
sry im new in programming :(
I think you need to study how to include CSS and JavaScript code into HTML pages — there are several methods, for you the simplest may be to use the STYLE and the SCRIPT tags (for CSS and JS respectively) and include your code between them. This is not the best solution, yet easiest if you're just starting to learn.
DeleteNice Article.. Thanks for sharing.. :)
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteNice One Anton. This is what i wanted.
ReplyDeleteI noticed a small mistake:
The mousemove event won't get unbinded in the endLinkMode() bcoz its isn't binded by bind().
Cheers :)
Hi Jaleel,
DeleteYou're correct, it won't unbind, especially since I use the .link suffix in unbind call. Well spotted!
This only relates to the JSFiddle code, though, since I don't include any of the endLinkMode login in this article.