admin管理员组

文章数量:1304931

I am currently working on a side project, where I want to "draw" diagrams. At the moment you just drag "sticky notes" to a grid, move, resize and label them. I now want to connect these notes with arrows. I got the calculations for length (pythagoras) and the angle of the line down.

calculateAngle(): number {
    return Math.atan2(this._start.y - this._end.y, this._start.x - this._end.x) * 180 / Math.PI + 180;
}

calculateLength(): number {
    return Math.round(Math.sqrt(Math.pow(this._start.y - this._end.y, 2) + Math.pow(this._start.x - this._end.x, 2)));
}

I also found a simple CSS solution for the arrows which work perfectly for horizontal and vertical arrows, but as soon the angle isn't dividable by 90deg the position is of course of.

This is based of a solution I found in SO and just added the transform. If you play around with the deg value, you can see how the base and head move around in the 2D space: /

Does anyone know a good way to calculate the "new" top and left values if the rotation is taken into account? Or maybe I just offset the position with margins if this calulation is easier.

I used to be good at math in school but that was a few decades ago.

I am currently working on a side project, where I want to "draw" diagrams. At the moment you just drag "sticky notes" to a grid, move, resize and label them. I now want to connect these notes with arrows. I got the calculations for length (pythagoras) and the angle of the line down.

calculateAngle(): number {
    return Math.atan2(this._start.y - this._end.y, this._start.x - this._end.x) * 180 / Math.PI + 180;
}

calculateLength(): number {
    return Math.round(Math.sqrt(Math.pow(this._start.y - this._end.y, 2) + Math.pow(this._start.x - this._end.x, 2)));
}

I also found a simple CSS solution for the arrows which work perfectly for horizontal and vertical arrows, but as soon the angle isn't dividable by 90deg the position is of course of.

This is based of a solution I found in SO and just added the transform. If you play around with the deg value, you can see how the base and head move around in the 2D space: https://jsfiddle/0r7k6L4c/

Does anyone know a good way to calculate the "new" top and left values if the rotation is taken into account? Or maybe I just offset the position with margins if this calulation is easier.

I used to be good at math in school but that was a few decades ago.

Share Improve this question asked Feb 3 at 21:43 ThomasThomas 7,1185 gold badges36 silver badges77 bronze badges 2
  • 1 That's a very old and obsolete way to draw triangles (and shapes in general). See this: css-shape/arrow – Temani Afif Commented Feb 3 at 21:51
  • See also the answer based on SVG-defined clip path – Sergey A Kryukov Commented Feb 4 at 1:26
Add a comment  | 

3 Answers 3

Reset to default 1

You could use the newly available CSS hypot and atan2 functions. Define the two points A, B using CSS Properties ax, ay and bx, by.
Make your element position fixed or absolute, set the CSS transform-origin point to be X left Y 50%, and rotate it by atan2 radians:

.arrow {
  --t: 5px; /* tail size */

  position: absolute;
  transform-origin: left 50%;
  left: calc(var(--ax) * 1px);
  top: calc(var(--ay) * 1px);
  height: var(--t);
  width: calc(hypot(calc(var(--by) - var(--ay)), calc(var(--bx) - var(--ax))) * 1px);
  rotate: atan2(calc(var(--by) - var(--ay)), calc(var(--bx) - var(--ax)));
  background: #000;
}
<div class="arrow" style="--ax:10; --ay:10; --bx:50; --by:100;"></div>

and to create the arrow shape, either use the :before pseudo or like the following where I borrowed Temani's cool solution using clip-path:

.arrow {
  
  --t: 5px;  /* tail size */
  --h: 10px; /* head size */
  
  position: absolute;
  transform-origin: left 50%;
  left: calc(var(--ax) * 1px);
  top: calc(var(--ay) * 1px);
  height: var(--h);
  width: calc(hypot(calc(var(--by) - var(--ay)), calc(var(--bx) - var(--ax))) * 1px);
  rotate: atan2(calc(var(--by) - var(--ay)), calc(var(--bx) - var(--ax)));
  clip-path: polygon(0 calc(50% - var(--t)/2),calc(100% - var(--h)) calc(50% - var(--t)/2),calc(100% - var(--h)) 0,100% 50%,calc(100% - var(--h)) 100%,calc(100% - var(--h)) calc(50% + var(--t)/2),0 calc(50% + var(--t)/2));
  background: #000;
}
<div class="arrow" style="--ax:10; --ay:10; --bx:50; --by:100;"></div>

By default transform-origin refers to the center. So if your original (left, top) is (100, 100) and width is 80, then center is at (100 + 80 / 2) etc.

In our example I've used 4 variables to make the point clear. The x, y, radius and deg of angle. I added also 3 pixels for the origin, the center, and the target point. The formula is simple sin and cos, you can look at the code.

I've also added some JS as a proof of concept.

var deg = 0
setInterval(function() {
  deg += 1
  document.documentElement.style.setProperty("--deg", deg + "deg")
}, 40)
:root {
  --origin-x: 100px;
  --origin-y: 100px;
  --radius: 40px;
  --deg: 45deg;

}

.arrow {
  position: absolute;
  left: var(--origin-x);
  top: var(--origin-y);
  width: calc(var(--radius) * 2);
  height: 0;
  border-bottom: 5px solid #000000;
  transform: rotate(var(--deg));
}

.arrow::after {
  content: "";
  border-top: 10px solid transparent;
  border-bottom: 10px solid transparent;
  border-left: 20px solid #000000;
  position: absolute;
  right: -10px;
  top: -8px;
}

.cavnas {
  border: 1px solid gray;
  position: relative;
  width: 300px;
  height: 180px;
  overflow: hidden;
}

.pixel {
  width: 3px;
  height: 3px;
  background: red;
  position: absolute;
}

.pixel-origin {
  left: var(--origin-x);
  top: var(--origin-y);
}

.pixel-center {
  left: calc(var(--origin-x) + var(--radius));
  top: var(--origin-y);
}

.pixel-target {
  left: calc(var(--origin-x) + var(--radius) + var(--radius) * cos(var(--deg)));
  top: calc(var(--origin-y) + var(--radius) * sin(var(--deg)));
}
<div class="cavnas">

  
  <div class="arrow"></div>
  
  
  <div class="pixel pixel-origin"></div>
  <div class="pixel pixel-center"></div>
  <div class="pixel pixel-target"></div>  

</div>

You can make it a lot simpler if you allow CSS to calculate rotation for your.

Here is the idea:

.arrow {
   margin-bottom: 60px;
   --t: 45%;
   --h: 20%;   
   aspect-ratio: 10/2;
   width: 100px;
   background: black;
   clip-path: polygon(0 calc(50% - var(--t)/2),
      calc(100% - var(--h)) calc(50% - var(--t)/2),
      calc(100% - var(--h)) 0, 100% 50%,
      calc(100% - var(--h)) 100%,
      calc(100% - var(--h)) calc(50% + var(--t)/2),
      0 calc(50% + var(--t)/2));
}
.r10 { transform: rotate(10deg); }
.r45 { transform: rotate(45deg); }
.r90 { transform: rotate(90deg); }
.r135 { transform: rotate(135deg); }
.r180 { transform: rotate(180deg); }
<!DOCTYPE html>
<html>
  <body>
    <section class="arrow"></section>
    <section class="arrow r10"></section>
    <section class="arrow r45"></section>
    <section class="arrow r90"></section>
    <section class="arrow r135"></section>
    <section class="arrow r180"></section>
  </body>
</html>

Pay attention for transform: rotate().

The main outline is borrowed from The Ultimate Collection of CSS-only Shapes.

本文标签: javascriptCSS Arrow with any angle but fixed start and end pointStack Overflow