JSFiddle - React, Tailwind, and code Playground
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<br>
<br> <!-- для проверки, что мы рисуем откуда надо, а не просто сверху экрана -->
<div class="container">
<div class="foo">
<div class="a">
<div class="point" style="top:30px;left:10px;" data-anchor="x1"></div>
<div class="point" style="top:40px;left:160px;" data-anchor="x1"></div>
<div class="point" style="top:10px;left:90px;" data-anchor="x6"></div>
<div class="point" style="top:70px;left:190px;" data-anchor="x2"></div>
<div class="point" style="top:90px;left:10px;" data-anchor="x3"></div>
<div class="point" style="top:70px;left:70px;" data-anchor="x7"></div>
</div>
<div class="b">
<p><a id="x1"> </a> description1</p>
<p><a id="x2"> </a> description2</p>
<p><a id="x3"></a> description3</p>
<p><a id="x4"></a> description4</p>
<p><a id="x5"></a> description5</p>
<p><a id="x6"></a> description6</p>
<p><a id="x7"></a> description7</p>
</div>
</div>
<canvas id="canvas"></canvas>
</div>
CSS
.container {
position:relative;
}
#canvas {
position:absolute;
top:0; left:0; width:100%;height:100%;
overflow:hidden;
z-index:1001;
pointer-events: none;
}
.text {
}
.point {
position:absolute;
width:24px;
height:24px;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII=");
}
.foo {
display: flex;
flex-wrap: wrap;
flex-direction: column;
height: 300px;
}
.a {
flex: 0 0 100%;
background: green;
}
.b {
flex: 0...
JavaScript
let anima={
/* массив линий */
lines:[],
/**
* временная переменная
*/
_TO:false,
/**
* вычислить смещение точки старта при движении на dist по вектору
*/
moveupto:function(x,dist){
let d=this.dist(x);
return [x.start[0]+(dist/d)*(x.fin[0]-x.start[0]),x.start[1]+(dist/d)*(x.fin[1]-x.start[1])];
},
moveuptopercent:function(x,dist){
return [x.start[0]+dist*(x.fin[0]-x.start[0]),x.start[1]+dist*(x.fin[1]-x.start[1])];
},
dist:function(x,dist){
return Math.sqrt((x.start[0]-x.fin[0])*(x.start[0]-x.fin[0]) + (x.start[1]-x.fin[1])*(x.start[1]-x.fin[1]));
},
permanentdraw:function(){
if(!!this._interval) return;
this._interval=setInterval(()=>anima.draw(),10);
},
clearpermanent:function(){
if(!this._interval) return;
clearInterval(this._interval);
this._interval=null;
},
/**
* отложенный draw - заявка на перерисовку. Можно частить, все равно не должно тормозить
*/
draw: function () {
if (!this._TO) {
let that=this;
this._TO = window.requestAnimationFrame(function () {
that._TO = false;
that._draw();
});
}
},
/* полная перерисовка канваса, начиная с зачистки */
_draw: function (){
let ofs=$('.container')[0].getBoundingClientRect();
/* Вспомогательная функция для рисования линии */
function line(a, b, options) {
options = options || {};
ctx.beginPath();
ctx.lineCap =...