I came upon this particularly interesting issue the other day while building a system for basic Microsoft Paint-style drawing utilities. Functionality like drawing rectangles, circles, text and lines. In particular, I was working on generating a line drawing functionality in HTML/CSS + Javascript that did not rely on more advanced rendering techniques, like <canvas> or <svg>.
The basic approach I took involved CSS transformations, a border and just a little bit of high school trigonometry. Making use of the offsetX and offsetY attributes of the onMousedown and onMouseup events to a background layer, we can get the top-left and bottom-right points to a box that bounds our line. Not only that, but the line is drawn directly from one corner to the next.
It is, of course, possible for the line to go from top-right to bottom-left as well, but I will leave that as an exercise for the reader (i.e, you).
Using the above figure as reference, we can see that the line we are looking for is the hypotenuse of the triangle made using the width and height of the bounding box as its sides. Looking at the code that forms the above, we see the following
<style type="text/css">
#example-1 {
width: 100%;
height: 200px;
position: relative;
}
#example-1 .bounding-box {
position: absolute;
left: 50%;
width: 300px;
margin-left: -150px;
top: 25px;
height: 150px;
border-top: 1px dashed black;
border-right: 1px dashed black;
}
#example-1 .line {
position: absolute;
left: 0px;
top: 0px;
height: 1px;
box-sizing: border-box;
width: 335.41px;
border-top: 1px solid red;
transform: rotate(0.4636476rad);
transform-origin: 0;
}
</style>
<div id="example-1">
<div class="bounding-box">
<div class="line"></div>
</div>
</div>
Let’s refer to the highlighted lines, because those two contain all the magic.
We’ll start with the width of the line. Going back to our basic, basic trig, we know that the width of the hypotenuse of a triangle is longer than either of its sides. In fact, it is equal to the square foot of the square of its sides.
Math.sqrt((width * width) + (height * height))
Now, we can go to the slightly more difficult task of handling the rotation. You’ll see in the code above it’s handled by the two CSS lines involving transform and transform-origin. The transform, in particular, we use a rotate function to rotate the object by a particular amount. This amount is actual the angle of the top-left point of the triangle, translated in radians (rad). Going back to our trig, we may recall the sin/cos/tan functions. We can use any of them, but for this exercise, we are going to use tan, or more specifically, atan (which is the reverse of the tangent function)
tan (angle) = opposite / adjacent
angle = atan (opposite / adjacent)
or in our case
angle = Math.atan( height / width )
NOTE: Please remember to always check your code for possible divide-by-zero errors!
Noting that JavaScript trig functions always return and accept their angles in radian form, we use the rad suffix when plugging the value into our transformation. Finally, we quickly review transform-origin. This css attribute does exactly what it purports to do – it moves the origin that transformations are performed against. For our usage, we move it to the point <0,0> within the context of the container <div> to make any additional transformations simpler (and in the case of moving from top-left to bottom-right, no other transformations are needed).
Voila! We now have a diagonal line and all the information we need to dynamically draw one onto a page in response to user input!
The Rotated Hitbox
Now we come to (in my opinion) the really interesting part. Going back to the project I was mentioning before, I came across a rather particular issue when I began trying to drag these lines around. I found that whenever I began the drag, the line would jump on the screen and then move smoothly from there. It was strange, it was unexpected and for a little bit I didn’t have any idea what was causing the bug.
Jumping to the answer, what it came down to was the way that the browser (testing across several modern browsers) was interpreting the position of the mouse event in context with the rotated item – it was using the non-transformed coordinate plane of the transformed element. More simply, while the rendering of the <div> was rotated, the offset coordinates within the MouseEvent was not.
Upon realizing this, the answer is pretty simple. All we need to do is rotate the point the same way we rotated the line, and we’re golden. However, while rotating a line relies on simple trig, rotating a point in a coordinate plane involves something a bit more complex – let’s dust off our linear algebra!
A rotation matrix is a construct that allows us to transform any point from one coordinate plane to a different coordinate plane that is just a rotation of the first. Classically, the two-dimensional version is represented as the following:
| cos (angle) | -sin (angle) | 0 |
| sin(angle) | cos(angle) | 0 |
| 0 | 0 | 1 |
By performing matrix-multiplication of the transformation matrix and the point vector (x,y,1), we can arrive at the points in the standard coordinate plane where the point *should* reside, thus translating the non-transformed point returned by the MouseEvent into the transformed version that will correlate with where the use actually placed their mouse.
The code, in JavaScript, to affect the transformation looks like this:
var x = event.offsetX
var y = event.offsetY
var cos = Math.cos(angle)
var sin = Math.sin(angle)
var point = { x: (x * cos) - (y * sin), y: (x * sin) + (y * cos) }
And that’s it! We are now able to not only draw diagonal lines with simple HTML/CSS, but additional make use of the browser’s mouse events to handle hitboxing (instead of doing it ourselves, which would be exhausting).
Happy Coding!