<canvas id="canvas"></canvas>
body {
  overflow: hidden;
}

#canvas {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate( -50%, -50% );
}
View Compiled
var TAU = Math.PI * 2;

var config = {
    space: 40,
    lineWidth: 10,
    color: "#FFFFFF",
    backgroundColor: "#8a319d",
    tailDelay: 0.2,
    timeDelta: 0.01,
    size: 500
};

var canvas = document.getElementById( "canvas" );
var context = canvas.getContext( "2d" );
var center = config.size / 2;
var lines;
var time = 0;
var gui = new dat.GUI();


function init() {
    setupGUI();
    refresh();
    updateBackground();

    requestAnimationFrame( render );
}

function setupGUI() {
    gui.add( config, "space", 5, 100 ).name( "Gap Size" ).onChange( refresh );
    gui.add( config, "lineWidth", 1, 100 ).name( "Line Width" );
    gui.add( config, "timeDelta", 0.001, 0.2 ).name( "Speed" );
    gui.add( config, "tailDelay", 0, 1 ).name( "Line Length" );
    gui.add( config, "size", 100, 1000 ).name( "Radius" ).onChange( refresh );
    gui.addColor( config, "color" ).name( "Color" );
    gui.addColor( config, "backgroundColor" ).name( "Background Color" ).onChange( updateBackground );
  
    gui.close();
}

function updateBackground() {
    document.body.style.backgroundColor = config.backgroundColor;
}

function refresh() {
    lines = [];
    center = config.size / 2;

    var amount = ~~( ( center - 50 ) / config.space );
    var max = center - 50;

    for (var i = 0; i < amount; i++) {
        lines.push( circle( ( 1 - i / amount ) * max, i / ( amount * 2 ) ) );
    };
}

function clear() {
    canvas.width = config.size;
    canvas.height = config.size;
}

function render() {
    clear();

    context.translate( center, center );
    context.rotate( - TAU / 4 );
    context.translate( -center, -center );
    context.beginPath();

    lines.forEach( function( line ) {
        line.draw( time );
    } );

    context.strokeStyle = config.color;
    context.lineWidth = config.lineWidth;
    context.stroke();

    time += config.timeDelta;
    time %= 1;

    requestAnimationFrame( render );
}

function getCartesian( angle, distance ) {
    return {
        x: Math.cos( angle ) * distance,
        y: Math.sin( angle ) * distance
    };
}

function ease( t ) {
    return 1 - ( Math.cos( t * Math.PI ) / 2 + 0.5 );
}

function circle( radius, delay ) {

    function getTailValue( t ) {
        var s = t - config.tailDelay;

        if ( s < 0 ) s += 1;

        return Math.pow( ease( s ), 2 ) * TAU;
    }

    function getHeadValue( t ) {
        return Math.pow( ease( t ), 2 ) * TAU;
    }

    return {
        draw: function( t ) {
            t -= delay;

            if ( t < 0 ) t += 1;

            var tailAngle = getTailValue( t );
            var tail = getCartesian( tailAngle, radius );

            context.moveTo( center + tail.x, center + tail.y );
            context.arc( center, center, radius, tailAngle, getHeadValue( t ) );
        }
    }
}

init();


External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. //cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js