10

Необходимо анимировать для веб странички Диаграмму процесса дизайн-мышления

введите сюда описание изображения

Сценарий анимации:

  • Движение кривых, показывающих процесс прохождения по этапам диаграммы
  • Во время выполнения одного этапа крутится лоадер
  • По окончанию выполнения одного этапа лоадер постепенно исчезает
  • Появляется зелёный чекбокс, сигнализирующий о завершении этапа
  • Начинается выполнении второго этапа и так далее до завершения последнего этапа

У меня есть лоадер с тремя сегментами

circle {
fill:none;
stroke:#777777;
}
<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"  width="320" height="60" viewBox="0 0 350 60" >

<circle id="crc1" cx="30" cy="30" r="20" stroke-width="3" stroke-dasharray="33.88 8" stroke-dashoffset="-4" > <animate attributeName="stroke-dashoffset" values="33.88;0" dur="0.2s" begin="0s" repeatCount="indefinite" restart="whenNotActive" />

</circle>

</svg>

Взят отсюда: https://ru.stackoverflow.com/a/944006/28748

Анимацию линий со стрелкой можно реализовать, как сделано в этом топике:

https://ru.stackoverflow.com/a/955002/28748

Вопрос: Как это всё собрать вместе, рассчитать тайминги анимаций, чтобы выполнить сценарий?

0xdb
  • 51,614
Alexandr_TT
  • 110,146
  • 23
  • 114
  • 384
  • я бы все сделал на d3.transition().on('end', () => ...) – Stranger in the Q Mar 11 '19 at 19:14
  • @Stranger in the Q делай, с удовольствием приму. Я сейчас готовлю свой ответ, но себе ставить галочку нет смысла, так что отдам в хорошие руки :) – Alexandr_TT Mar 11 '19 at 19:17

2 Answers2

11
  • Движение кривых, показывающих процесс прохождения по этапам диаграммы

    Загружаем картинку в векторный редактор и с помощью инструмента - Рисовать кривые Безье, повторяем траекторию кривых.

  • Сохраняем патчи кривых в другой файл и реализуем анимацию роста кривых с помощью stroke-dashoffset. При уменьшении этого атрибута от 1192px до нуля, линия будет расти от нуля до максимума.

.shape svg {
  width:100%;
  height: auto;
}
#outline1, #outline2 {
  fill:none;

stroke-dasharray: 1192; animation: dash 17s linear forwards; stroke-linejoin: round; stroke-width:4; }

@keyframes dash { from { stroke-dashoffset: 1192; } to { stroke-dashoffset: 0; } } .container {

width:100%; height:100%; background-image: url("https://www.transparenttextures.com/patterns/3px-tile.png"); background: linear-gradient(to right,#B452A0, #895FDD ); display: flex; justify-content: center; align-items:center; }

<div class="container">
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="90%" height="90%" viewBox="0 0 1214 863" preserveAspectRatio="xMinYMin meet">

 <image xlink:href="https://i.stack.imgur.com/NZ5NF.jpg" height="100%" width="100%"/>
    <path id="outline1" stroke="#EC1E4E" d="m62.2 447c0 0 33.3-38.2 48.5-58.5 12.5-16.7 19.9-37.4 34.7-52.1 13.4-13.3 29.7-24.9 47.5-31.1 16.2-5.6 34.2-7.1 51.2-4.6 18.5 2.7 35.8 11.8 52.1 21 23.5 13.2 43.4 30.3 64.9 48.5 17.3 14.6 39.6 25.5 62.2 27.4 21.3 1.8 41.6-9.9 62.2-15.5 20.5-5.6 40.4-13.7 61.3-18.3 18.6-4.1 37.6-8.4 56.7-8.2 19.4 0.1 39.4 2.3 57.6 9.1 20.5 7.7 36.1 25.1 55.8 34.7 21 10.4 42.4 23.1 65.8 24.7 21.8 1.5 44.6-4.5 64-14.6 19.9-10.4 38.4-16.2 58.5-21 18.8-4.5 38.2-7.4 57.6-7.3 20.9 0.1 41.8 3.6 62.2 8.2 19.1 4.3 36.9 13 55.8 18.3 15.4 4.3 46.6 11 46.6 11v0M62.2 447"/>
  <path id="outline2" stroke="#8B0FC5" d="m62.2 447c0 0 33.3 38.2 48.5 58.5 12.5 16.7 19.9 37.4 34.7 52.1 13.4 13.3 29.7 24.9 47.5 31.1 16.2 5.6 34.2 7.1 51.2 4.6 18.5-2.7 35.8-11.8 52.1-21 23.5-13.2 43.4-30.3 64.9-48.5 17.3-14.6 39.6-25.5 62.2-27.4 21.3-1.8 41.6 9.9 62.2 15.5 20.5 5.6 40.4 13.7 61.3 18.3 18.6 4.1 37.6 8.4 56.7 8.2 19.4-0.1 39.4-2.3 57.6-9.1 20.5-7.7 36.1-25.1 55.8-34.7 21-10.4 42.4-23.1 65.8-24.7 21.8-1.5 44.6 4.5 64 14.6 19.9 10.4 38.4 16.2 58.5 21 18.8 4.5 38.2 7.4 57.6 7.3 20.9-0.1 41.8-3.6 62.2-8.2 19.1-4.3 36.9-13 55.8-18.3 15.4-4.3 46.6-11 46.6-11v0" />

  </svg> 
  </div>

Все дальнейшие пояснения даны в комментариях кода.

Тайминг последовательных и параллельных анимаций осуществляется цепочками условий основанных на привязке к id анимаций.

Другими словами, begin="an_check1.end-0.5s" - данная анимация начнется, когда закончится анимация с id="an_check1" минус пол секунды.

.shape svg {
  width:100%;
  height: auto;
}
#outline1, #outline2 {
  fill:none;

stroke-dasharray: 1192; animation: dash 17s linear forwards; stroke-linejoin: round; stroke-width:4;

}

@keyframes dash { from { stroke-dashoffset: 1192; } to { stroke-dashoffset: 0; } } .container {

width:100%; height:100%; background: linear-gradient(to right,#B452A0, #895FDD ); display: flex; justify-content: center; align-items:center; }

<div class="container">
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="90%" height="90%" viewBox="0 0 1214 863" preserveAspectRatio="xMinYMin meet">

<defs>


</defs>

 <image xlink:href="https://i.stack.imgur.com/NZ5NF.jpg" height="100%" width="100%"/>
    <path id="outline1" stroke="#EC1E4E" d="m62.2 447c0 0 33.3-38.2 48.5-58.5 12.5-16.7 19.9-37.4 34.7-52.1 13.4-13.3 29.7-24.9 47.5-31.1 16.2-5.6 34.2-7.1 51.2-4.6 18.5 2.7 35.8 11.8 52.1 21 23.5 13.2 43.4 30.3 64.9 48.5 17.3 14.6 39.6 25.5 62.2 27.4 21.3 1.8 41.6-9.9 62.2-15.5 20.5-5.6 40.4-13.7 61.3-18.3 18.6-4.1 37.6-8.4 56.7-8.2 19.4 0.1 39.4 2.3 57.6 9.1 20.5 7.7 36.1 25.1 55.8 34.7 21 10.4 42.4 23.1 65.8 24.7 21.8 1.5 44.6-4.5 64-14.6 19.9-10.4 38.4-16.2 58.5-21 18.8-4.5 38.2-7.4 57.6-7.3 20.9 0.1 41.8 3.6 62.2 8.2 19.1 4.3 36.9 13 55.8 18.3 15.4 4.3 46.6 11 46.6 11v0M62.2 447"/>
  <path id="outline2" stroke="#8B0FC5" d="m62.2 447c0 0 33.3 38.2 48.5 58.5 12.5 16.7 19.9 37.4 34.7 52.1 13.4 13.3 29.7 24.9 47.5 31.1 16.2 5.6 34.2 7.1 51.2 4.6 18.5-2.7 35.8-11.8 52.1-21 23.5-13.2 43.4-30.3 64.9-48.5 17.3-14.6 39.6-25.5 62.2-27.4 21.3-1.8 41.6 9.9 62.2 15.5 20.5 5.6 40.4 13.7 61.3 18.3 18.6 4.1 37.6 8.4 56.7 8.2 19.4-0.1 39.4-2.3 57.6-9.1 20.5-7.7 36.1-25.1 55.8-34.7 21-10.4 42.4-23.1 65.8-24.7 21.8-1.5 44.6 4.5 64 14.6 19.9 10.4 38.4 16.2 58.5 21 18.8 4.5 38.2 7.4 57.6 7.3 20.9-0.1 41.8-3.6 62.2-8.2 19.1-4.3 36.9-13 55.8-18.3 15.4-4.3 46.6-11 46.6-11v0" />
    <!-- Верхний  Бегущий треугольник -->
  <polyline transform="translate(7 -12.5) rotate(90)" points="0,0 12.5,21.6 25,0" fill="#B51092" >
  <animateMotion
    id="an1_triangle"
    dur="17s"
    repeatCount="1"
    rotate="auto-reverse"
    begin="0s"
    fill="freeze"
    restart="whenNotActive">
     <mpath xlink:href="#outline1"/>
 </animateMotion>  
 <!-- Нижний  Бегущий треугольник -->
</polyline>  
    <polyline transform="translate(7 -12.5) rotate(90)" points="0,0 12.5,21.6 25,0" fill="#6326DD" >
  <animateMotion
    id="an2_triangle"
    dur="17s"
    repeatCount="1"
    rotate="auto-reverse"
    begin="0s"
    fill="freeze"
    restart="whenNotActive">
     <mpath xlink:href="#outline2"/>
   </animateMotion>  
    </polyline>   
    <!-- Лоадер 1-ой секции -->
 <g transform="translate(30 265)">
  <circle id="crc1" cx="30" cy="30" r="20" fill="none" stroke-opacity="1" stroke="#EC1E4E" stroke-width="3" 
   stroke-dasharray="33.88 8"  stroke-dashoffset="-4" >

    <animate id="an_crc1"
    attributeName="stroke-dashoffset"
    values="33.88;0"
    dur="0.2s"
    begin="0s"
     repeatCount="12"
    restart="whenNotActive" /> 
                 <animate id="fill_crc1"  attributeName="stroke-opacity" dur="1s" begin="an_crc1.end-0.5s" values="1;0" fill="freeze" />  
  </circle>   
       <!-- Анимация чекбокса 1-ой секции -->  
      <text x="15" y="43" fill="#00AF00" font-size="54" fill-opacity="0">&#10004;
    <animate id="an_check1"
       attributeName="fill-opacity"
    dur="1s"
    begin="fill_crc1.end-0.25s"
    values="0;1"
    fill="freeze" /> 
   </text>
    </g>   
     <!-- Лоадер 2-ой секции -->
 <g transform="translate(330 265)">
  <circle id="crc2" cx="30" cy="30" r="20" fill="none" stroke-opacity="1" stroke="#CA0C52" stroke-width="3" 
   stroke-dasharray="33.88 8"  stroke-dashoffset="-4" >

    <animate id="an_crc2"
    attributeName="stroke-dashoffset"
    values="33.88;0"
    dur="0.2s"
    begin="an_check1.end-0.5s"
    repeatCount="12"
    restart="whenNotActive" /> 
                 <animate id="fill_crc2"  attributeName="stroke-opacity" dur="1s" begin="an_crc2.end-0.5s" values="1;0" fill="freeze" />  
  </circle>   
       <!-- Анимация чекбокса 2-ой секции -->  
      <text x="15" y="43" fill="#00AF00" font-size="54" fill-opacity="0">&#10004;
    <animate id="an_check2"
       attributeName="fill-opacity"
    dur="1s"
    begin="fill_crc2.end-0.25s"
    values="0;1"
    fill="freeze" /> 
   </text>
    </g>      

         <!-- Лоадер 3-ой секции -->
 <g transform="translate(530 265)">
  <circle id="crc3" cx="30" cy="30" r="20" fill="none" stroke-opacity="1" stroke="#B51092" stroke-width="3" 
   stroke-dasharray="33.88 8"  stroke-dashoffset="-4" >

    <animate id="an_crc3"
    attributeName="stroke-dashoffset"
    values="33.88;0"
    dur="0.2s"
    begin="an_check2.end-0.5s"
    repeatCount="12"
    restart="whenNotActive" /> 
                 <animate id="fill_crc3"  attributeName="stroke-opacity" dur="1s" begin="an_crc3.end-0.5s" values="1;0" fill="freeze" />  
  </circle>   
       <!-- Анимация чекбокса 3-ой секции -->  
      <text x="15" y="43" fill="#00AF00" font-size="54" fill-opacity="0">&#10004;
    <animate id="an_check3"
       attributeName="fill-opacity"
    dur="1s"
    begin="fill_crc3.end-0.25s"
    values="0;1"
    fill="freeze" /> 
   </text>
    </g>  

            <!-- Лоадер 4-ой секции -->
 <g transform="translate(730 265)">
  <circle id="crc4" cx="30" cy="30" r="20" fill="none" stroke-opacity="1" stroke="#8B0FC5" stroke-width="3" 
   stroke-dasharray="33.88 8"  stroke-dashoffset="-4" >

    <animate id="an_crc4"
    attributeName="stroke-dashoffset"
    values="33.88;0"
    dur="0.2s"
    begin="an_check3.end-0.5s"
    repeatCount="12"
    restart="whenNotActive" /> 
                 <animate id="fill_crc4"  attributeName="stroke-opacity" dur="1s" begin="an_crc4.end-0.5s" values="1;0" fill="freeze" />  
  </circle>   
       <!-- Анимация чекбокса 4-ой секции -->  
      <text x="15" y="43" fill="#00AF00" font-size="54" fill-opacity="0">&#10004;
    <animate id="an_check4"
       attributeName="fill-opacity"
    dur="1s"
    begin="fill_crc4.end-0.25s"
    values="0;1"
    fill="freeze" /> 
   </text>
    </g>    

               <!-- Лоадер 5-ой секции -->
 <g transform="translate(930 265)">
  <circle id="crc5" cx="30" cy="30" r="20" fill="none" stroke-opacity="1" stroke="#6326DD" stroke-width="3" 
   stroke-dasharray="33.88 8"  stroke-dashoffset="-4" >

    <animate id="an_crc5"
    attributeName="stroke-dashoffset"
    values="33.88;0"
    dur="0.2s"
    begin="an_check4.end-0.5s"
    repeatCount="12"
    restart="whenNotActive" /> 
                 <animate id="fill_crc5"  attributeName="stroke-opacity" dur="1s" begin="an_crc5.end-0.5s" values="1;0" fill="freeze" />  
  </circle>   
       <!-- Анимация чекбокса 5-ой секции -->  
      <text x="15" y="43" fill="#00AF00" font-size="54" fill-opacity="0">&#10004;
    <animate id="an_check5"
       attributeName="fill-opacity"
    dur="1s"
    begin="fill_crc5.end-0.25s"
    values="0;1"
    fill="freeze" /> 
   </text>
    </g>    

                  <!-- Лоадер 6-ой секции -->
 <g transform="translate(1085 265)">
  <circle id="crc6" cx="30" cy="30" r="20" fill="none" stroke-opacity="1" stroke="#0336E1" stroke-width="3" 
   stroke-dasharray="33.88 8"  stroke-dashoffset="-4" >

    <animate id="an_crc6"
    attributeName="stroke-dashoffset"
    values="33.88;0"
    dur="0.2s"
    begin="an_check5.end-0.5s"
    repeatCount="12"
    restart="whenNotActive" /> 
                 <animate id="fill_crc6"  attributeName="stroke-opacity" dur="1s" begin="an_crc6.end-0.5s" values="1;0" fill="freeze" />  
  </circle>   
       <!-- Анимация чекбокса 6-ой секции -->  
      <text x="15" y="43" fill="#00AF00" font-size="54" fill-opacity="0">&#10004;
    <animate id="an_check6"
       attributeName="fill-opacity"
    dur="1s"
    begin="fill_crc6.end-0.25s"
    values="0;1"
    fill="freeze" /> 
   </text>
    </g>  



  </svg> 
  </div>

Приложение полностью адаптивно. Работает во всех современных браузерах.

Анимация не работает в IE и Edge, так как Microsoft не хочет поддерживать анимацию SVG.
Да и многое другое, в том числе современные технологии CSS3, плохо или совсем не поддерживаются браузерами Microsoft.

0xdb
  • 51,614
Alexandr_TT
  • 110,146
  • 23
  • 114
  • 384
  • 2
    Ого, интересная вещь. Спасибо за вопрос и подробный ответ –  Mar 12 '19 at 10:27
6

Вот потрудился над d3 вариантом (пока что не все анимации).

Тут все рассчитывается в рантайме, анимации синхронизированны программно и запускаются, когда стрелочка достаточно приближается к блокам, удаляется от блоков.

Фокусы d3, которыe я тут использовал:

1. d3.line().curve()

let line = d3.line().curve(d3.curveCardinal);

Этот вызов возвращает функцию-интерполятор, которая принимает на вход массив точек, из которых по выбранному алгоритму (здесь d3.curveCardinal) возвращает d для path.

2. selection.transition().attrTween(...)

arrow.transition(t).attrTween("transform", function(){return ...});

Позволяет задать правило для интерполяции строкового css параметра, такого, как color или transform.

3. d3.interpolateString()

Собственно, интерполятор между строковыми значениями. Вызов возвращает функцию, которая производит интерполяцию между строковыми значениями.


Вот результат, нажмите на чекбокс, чтобы отредактировать кривую:

let w = 115;
let editPath = d3.select('path.edit-path');

let paths = d3.selectAll('path.path');

let zones = d3.select('g.zones').selectAll('g') .data(d3.range(6).map(d => ({i: d, x:135 + d*w}))) .enter().append('g') .attr('transform', d => translate(${d.x}, 0));

let circles = zones.append('circle').classed('loader', true) .attr('r', 20).attr('cx', 60).attr('cy', 170);

let marks = zones.append('path').attr('stroke', 'transparent') .attr('fill', 'none').attr('stroke-width', 4) .attr('d', "M40,165 l15,20 l20,-30");

let rects = zones.append('rect').attr('width', w).attr('height', 480);

let arrow = d3.select(".arrow"); let pathNode = editPath.node(); let totalLength = pathNode.getTotalLength();

d3.select('input#modify').on('change', () => { let editMode = d3.select('input#modify').node().checked; paths.classed('no-dasharray', editMode); rects.classed('no-dasharray', editMode); d3.selectAll('circle.knob').classed('no-dasharray', editMode); });

editor(editPath);

anim();

function anim() { paths.attr('d', editPath.attr('d'));

draw(true);

function draw(now) {

  var t = d3.transition()
      .duration(16000)
      .delay(now ? 0 : 1000)
      .on("start", function() {
          d3.selectAll("path").style("display", "block");
          marks.attr('stroke','transparent')  
          circles.attr('stroke', 'transparent')
              .each(d =&gt; {d.started = d.done = d.checked = false});
      })
      .on("end", draw);

  arrow.transition(t).attrTween("transform", function() {
      return function(t) {
        let pos = t * totalLength;
        let pt = pointAtLength(pos); 
        circles.each(function(d) {
            let dx = Math.abs(d.x - pt[0] + w/2);
            if (!d.started &amp;&amp; dx &lt; w/2) {
                d.started = true;
                d3.select(this) .transition(100).attr('stroke', 'black');
            }
            if (!d.done &amp;&amp; d.started &amp;&amp; dx &gt; w/2) {
                d.done = true;
                d3.select(this).transition(300).attr('stroke','transparent')   
            }
        })
        marks.each(function(d) {
            if (!d.checked &amp;&amp; d.done) {
                d.checked = true;
                d3.select(this).transition(300).attr('stroke','green')   
            }
        })
        return `translate(${pt}) rotate(${tangentAt(pos)})`;
      };
    });

    paths.transition(t).attrTween("stroke-dasharray", function() {
      return d3.interpolateString("0," + totalLength, totalLength + "," + totalLength);
    });

    circles.transition(t).attrTween("stroke-dashoffset", function() {
      return d3.interpolate(0, 1000);
    });
}

function pointAtLength(l) {
  let xy = pathNode.getPointAtLength(l);
  return [xy.x, xy.y];
}

function tangentAt(l) {
  let a = pointAtLength(Math.max(l - 0.01, 0)), 
      b = pointAtLength(l + 0.01); 
  return Math.atan2(b[1] - a[1], b[0] - a[0]) * 180 / Math.PI;
}

}

function editor(target) { let size = 800;

let points = d3.range(0, 13).map(function(i) {
  return [135 + i*w/2, 250-100*Math.random()];
});

let dragged = null,
    selected = points[points.length-1];

let line = d3.line().curve(d3.curveCardinal);

let svg = d3.select("svg");

svg.append("rect")
    .attr("width", size)
    .attr("height", size);

d3.select(window)
    .on("mousemove", mousemove)
    .on("mouseup", mouseup);

redraw();

function redraw() {

  paths.datum(points).attr("d", line);  

  totalLength = pathNode.getTotalLength();

  var circle = svg.selectAll("circle.knob")
      .data(points, d =&gt; d);

  circle.exit().remove();

  let newNodes = circle.enter()
      .append("circle").classed('knob', true)
      .attr("r", 1e-6)
      .on("mousedown", d =&gt; { 
          selected = dragged = d; 
          redraw(); 
      })
      .transition()
      .duration(250)
      .attr("r", 6.5);

  circle.merge(newNodes)
      .classed("selected", d =&gt; d === selected)
      .attr("cx", d =&gt; d[0])
      .attr("cy", d =&gt; d[1]);

  if (d3.event) {
    d3.event.preventDefault();
    d3.event.stopPropagation();
  }
}

function mousemove() {
  if (!dragged) 
      return;
  let m = d3.mouse(svg.node());
  dragged[0] = m[0];
  dragged[1] = m[1];
  redraw();
}

function mouseup() {
  if (!dragged) 
      return;
  mousemove();
  dragged = null;
}

}

rect {
    fill: none;
}

circle.loader {
    fill: none;
    stroke-width: 2.2px;
    stroke-dasharray: 32 10;
}

path.path {
    fill: none;
    stroke: red;
    stroke-width:2.2;
}

circle {
  fill: transparent;
  cursor: move;
}

.no-dasharray {
    stroke-dasharray: 0 !important;
    stroke: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<input id="modify" type="checkbox">modify</input>
<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink" 
     preserveAspectRatio="xMidYMid meet"
     viewBox="0 0 960 500">
    <image xlink:href="https://i.stack.imgur.com/NZ5NF.jpg" 
           height="100%" width="100%"/>
    <g class="zones"></g>
    <path class='path edit-path'/>
    <path class='path mirror-path' transform='translate(0,520) scale(1, -1)'/>
    <path class="arrow" d="M-5,0 L-15,15 L15,0 L-15,-15 Z"/>
</svg>
0xdb
  • 51,614
  • не плохо получилось, но от такого мастера как ты, я честно говоря ожидал бОльшего. Во-первых путь не может быть произвольным, Он должен быть именно таким как в вопросе на картинке. Во-вторых задумка была, - пока идёт выполнение одного этапа лоадер крутится, затем исчезает. В третьих, - решение не адаптивно и это самый главный минус. Ну и я конечно ожидал, что ты добавишь дополнительно, какие-то новые яркие анимационные эффекты. – Alexandr_TT Mar 12 '19 at 11:23
  • @Alexandr_TT дык это все не 45 секунд делается, я вчера до 2х часов ночи и так с этим просидел =) вечером сегодня еще подпричешу =), я тут больше хотел показать возможность не рисовать руками, а программировать анимации, потом еще и описание напишу – Stranger in the Q Mar 12 '19 at 11:42
  • Давай Константин жду :) Плюс я уже давно поставил, жду когда можно галочку будет отдать. Хотя не в этом дело, просто как я понял, нам с тобой доставляет удовольствие просто заниматься творчеством А репа, медальки за этим сами придут :) – Alexandr_TT Mar 12 '19 at 11:49
  • 1
    @Alexandr_TT я вот mirror для path сделал, но захардкодил Y относительно которого отражение происходит, и застрял на его подсчете. – Stranger in the Q Mar 12 '19 at 12:04
  • да не торопись, я буду ждать сколько надо будет. Но змею двуглавую, надо меньше сделать, чтобы не залазила на картинки и текст. И не выходи за габариты картинки слева и справа, там могут быть другие блоки. – Alexandr_TT Mar 12 '19 at 12:10
  • @Alexandr_TT я хочу возврат обязятельно чтобы был, иначе не интересно =), а кривую подрихтуем – Stranger in the Q Mar 12 '19 at 12:13
  • про вращение лоадеров не забудь – Alexandr_TT Mar 12 '19 at 12:15
  • 1
    @Alexandr_TT это я на закуску оставил =) – Stranger in the Q Mar 12 '19 at 12:16
  • 1
    Замечательно. Мне очень понравилось. Особенно фича с изменением пути. Очень классный ответ и решение! – Alexandr_TT Mar 13 '19 at 05:07
  • 1
    @Alexandr_TT спасибо за акцепт, я часто именно этим кусочком кода пользуюсь для рисования всяких сплайнов "по месту", а потом готовую svg из отладчика достаю, попозже доделаю копию стрелочки и цвет кривых =) – Stranger in the Q Mar 13 '19 at 08:22
  • @Alexandr_TT https://codepen.io/strangerintheq/pen/WmROQO – Stranger in the Q Mar 13 '19 at 08:25
  • 1
    так я с утра посмотрел не заметил по сердечку? :) или это ссылка для других? – Alexandr_TT Mar 13 '19 at 08:26
  • @Alexandr_TT теперь заметил, а там есть оповещения? – Stranger in the Q Mar 13 '19 at 08:33
  • не знаю, скорее всего нет – Alexandr_TT Mar 13 '19 at 08:34
  • @Alexandr_TT а этот кусок кода частичка моего доморощенного svg редактора, на d3 под браузер – Stranger in the Q Mar 13 '19 at 08:36
  • Вот ещё очень похожий по применению генератор криволинейных патчей даю ссылку, может не видел – Alexandr_TT Mar 13 '19 at 08:40
  • @Alexandr_TT, да, только это несколько другое, по приведенной ссылке редактор кривой с контрольными точками, тут же - сплайн, причем алгоритм можно легко изменить и будет другой тип интерполяции – Stranger in the Q Mar 13 '19 at 08:42
  • @Alexandr_TT я такой прием использую для редактирования уже сгенерированных интерполятором путей, вот так это выглядит https://imgur.com/a/juOBaYM – Stranger in the Q Mar 13 '19 at 08:45
  • 1