<button id="random">Мне повезёт!</button>
<button id="clear">Очистить</button>
<canvas id="myCanvas"></canvas>
body,html{
  height:100%;
}
var canvas = document.querySelector("#myCanvas");
var ctx = canvas.getContext("2d");
var ctxW,ctxH,currentAnimId = 0;

canvas.width = ctxW = document.body.offsetWidth;
canvas.height = ctxH =document.body.offsetHeight;

var dots = [];

document.querySelector("#clear").addEventListener("click",()=>{
  dots = [];
  runAnimation();
});
document.querySelector("#random").addEventListener("click",()=>{//Рандомно расставить точки
  var rand = (min,max)=>{ return Math.floor(min + max *Math.random())};
  var n = rand(5,10);
  dots = [];
  while(n--)
    dots.push([rand(0,canvas.width),rand(0,canvas.height)]);
    runAnimation();  
});
document.querySelector("#random").dispatchEvent(new Event("click"));
canvas.addEventListener("mousedown",function(e){
  dots.push([e.offsetX,e.offsetY]);
  runAnimation();
});

function runAnimation(){
  currentAnimId++;
  let animId = currentAnimId;
  var speedAnim = 10;
  let  iTo = 0,tTo = 0; 
  (function anim(){
    var step = draw(iTo,tTo,speedAnim);
    
    tTo+=step;
    if(tTo>1){
      tTo = 0;
      iTo++;
    }
    if(iTo >= dots.length - 1){    
      
      draw(dots.length - 2,1,speedAnim);
      return;
     }
    if(currentAnimId == animId)
      setTimeout(anim,1000/60);
  })();
}

function draw(iTo, tTo,speed){
  var resultStep = 1;
  ctx.clearRect(0,0,ctxW,ctxH);  
  ctx.fillText("Кликай по канвасу",10,10)
  ctx.lineWidth = 2;
  for(var i  = 0; i < dots.length;i++){
    ctx.fillStyle = i>=iTo+1 && !(iTo == dots.length-2 && tTo  == 1)?"red":"blue";
    ctx.beginPath();
    ctx.arc(dots[i][0],dots[i][1],10,0,Math.PI*2);
    ctx.closePath();
    ctx.fill();
  }
  if(dots.length  >= 2){
     for(var i  = 0; i <= iTo;i++){
        var inxLast = i > 0?i-1:0;//Индекс предыдущей точки в массиве точек
        var inxNext = i < dots.length - 2? i+2 :i+1;   //Индекс следующей точки в массиве точек
        //Вычисляем промежуточные точки между двумя точками
        var control = calcControlPoints(dots[inxLast][0],dots[inxLast][1],
                          dots[i][0],dots[i][1],
                          dots[i+1][0],dots[i+1][1],
                          dots[inxNext][0],dots[inxNext][1]);       
       
       
       resultStep = (1/bezierCurveLength(
                           dots[i][0],dots[i][1],
                           control.cx1,control.cy1,
                            control.cx2,control.cy2,        
                            dots[i+1][0],dots[i+1][1])) * speed;
       var TmpTTo = i == iTo?tTo:1;
       let t = 0;
       let lastDot = null;
       let isBreak = false;
       while(true){
         var tmp = getCordOnCurve(t,dots[i][0],dots[i][1],control.cx1,control.cy1,
                            control.cx2,control.cy2,        
                            dots[i+1][0],dots[i+1][1]);
          if(lastDot)
            ctx.lineTo(tmp.x,tmp.y);
          else
            ctx.moveTo(tmp.x,tmp.y);
        lastDot = tmp;
        if(isBreak)break;
        t+=resultStep;        
        if(t > TmpTTo){
          t = TmpTTo;
          isBreak = true;
        }
       }
    } 
       ctx.stroke();
  }
    return resultStep;
}

draw();

//Вычисляем точки на кривой безье по формуле 4х точеченой кривой
//P = (1−t)3P1 + 3(1−t)2tP2 +3(1−t)t2P3 + t3P4
//Подробнее https://learn.javascript.ru/bezier-curve
function getCordOnCurve(t,x0,y0,x1,y1,x2,y2,x3,y3){//t[0-1]
  var x  = Math.pow(1-t,3)*x0 + 3* Math.pow(1-t,2)*t*x1 + 3 * (1-t) * Math.pow(t,2)* x2 + Math.pow(t,3)*x3;
  var y  = Math.pow(1-t,3)*y0 + 3* Math.pow(1-t,2)*t*y1 + 3 * (1-t) * Math.pow(t,2)* y2 + Math.pow(t,3)*y3;
  return {x:x,y:y};
}

function calcControlPoints(x0,y0,x1,y1,x2,y2,x3,y3){
//Вычисляем промежуточные точки между двумя точками, по методу Maxim Shemanarev Interpolation with Bezier Curves
        //Подробнее http://agg.sourceforge.net/antigrain.com/research/bezier_interpolation/index.html
    var xc1 = (x0 + x1) / 2.0;
    var yc1 = (y0 + y1) / 2.0;
    var xc2 = (x1 + x2) / 2.0;
    var yc2 = (y1 + y2) / 2.0;
    var xc3 = (x2 + x3) / 2.0;
    var yc3 = (y2 + y3) / 2.0;

    var len1 = Math.sqrt((x1-x0) * (x1-x0) + (y1-y0) * (y1-y0));
    var len2 = Math.sqrt((x2-x1) * (x2-x1) + (y2-y1) * (y2-y1));
    var len3 = Math.sqrt((x3-x2) * (x3-x2) + (y3-y2) * (y3-y2));

    var k1 = len1 / (len1 + len2);
    var k2 = len2 / (len2 + len3);

    var xm1 = xc1 + (xc2 - xc1) * k1;
    var ym1 = yc1 + (yc2 - yc1) * k1;

    var xm2 = xc2 + (xc3 - xc2) * k2;
    var ym2 = yc2 + (yc3 - yc2) * k2;

    
    var smooth_value  = 1;//КОЭФИЦИЕНТ СГЛАЖИВАНИЯ ОТ 0 ДО 1
    ctrl1_x = xm1 + (xc2 - xm1) * smooth_value + x1 - xm1;
    ctrl1_y = ym1 + (yc2 - ym1) * smooth_value + y1 - ym1;

    ctrl2_x = xm2 + (xc2 - xm2) * smooth_value + x2 - xm2;
    ctrl2_y = ym2 + (yc2 - ym2) * smooth_value + y2 - ym2;
    
    return {cx1:ctrl1_x, cy1: ctrl1_y, cx2:ctrl2_x, cy2:ctrl2_y};
}
//Получение длинны кривой
function bezierCurveLength(x1, y1, x2, y2, x3, y3, x4, y4) {
  const n = 100; // Количество сегментов для интегрирования
  let length = 0;
  let px = x1;
  let py = y1;
  for (let i = 1; i <= n; i++) {
    const t = i / n;
    const cx = (3 * x2 - 3 * x1) * t * t * t + (3 * x1 - 6 * x2 + 3 * x3) * t * t + (3 * x2 - 3 * x3) * t + x4 - x1;
    const cy = (3 * y2 - 3 * y1) * t * t * t + (3 * y1 - 6 * y2 + 3 * y3) * t * t + (3 * y2 - 3 * y3) * t + y4 - y1;
    length += Math.sqrt((cx - px) * (cx - px) + (cy - py) * (cy - py));
    px = cx;
    py = cy;
  }
  return length;
}


External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.