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">&nbsp;</a> description1</p>
        <p><a id="x2">&nbsp;</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 =...