Code

const regl = createREGL({
  extensions: ['ANGLE_instanced_arrays']
});

// Instantiate a command for drawing lines
const drawLines = reglLines(regl, {
  vert: `
    precision highp float;

    #pragma lines: attribute vec2 xy;

    // Assign a function for computing the vec4 position
    #pragma lines: position = getPosition(xy);

    // Assign a function for computing the float width
    #pragma lines: width = getWidth(xy);

    uniform float width;
    uniform vec2 translate, scale;

    // Implement the functions above
    vec4 getPosition(vec2 xy) { return vec4(xy * scale + translate, 0, 1); }
    float getWidth(vec2 xy) { return width; }
    float getX(vec2 xy) { return xy.x; }`,
  frag: `
    precision highp float;
    varying vec3 lineCoord;
    uniform float width, borderWidth;
    void main () {
      // Convert the line coord into an SDF
      float sdf = length(lineCoord.xy) * width;

      vec3 borderColor = 0.5 + 0.5 * vec3(lineCoord.xy, 0);

      // Apply a border with 1px transition
      gl_FragColor = vec4(
        mix(vec3(0), borderColor, smoothstep(width - borderWidth - 1.0, width - borderWidth + 1.0, sdf)),
        1);
    }`,

  // Additional regl command properties are valid
  uniforms: {
    width: (ctx, props) => ctx.pixelRatio * props.width,
    borderWidth: (ctx, props) => ctx.pixelRatio * props.borderWidth,
    translate: regl.prop('translate'),
    scale: regl.prop('scale')
  },
  depth: {enable: false}
});

// Construct an array of xy pairs
const n = 11;
const xy = [...Array(n).keys()]
  .map(i => (i / (n - 1) * 2.0 - 1.0) * 0.8)
  .map(t => [t, 0.5 * Math.sin(54.0 * t)]);

// Set up the data to be drawn. Note that we preallocate buffers and don't create
// them on every draw call.
const lineData = {
  vertexCount: xy.length,
  vertexAttributes: {
    xy: regl.buffer(xy)
  },
  endpointCount: 2,
  endpointAttributes: {
    xy: regl.buffer([xy.slice(0, 3), xy.slice(-3).reverse()])
  },

  width: 35,
  borderWidth: 10,
  miterLimit: 3.0,
  scale: [1, 1]
};

window.addEventListener('mousemove', e => {
  lineData.scale = [
    e.offsetX / window.innerWidth * 2 - 1,
    -e.offsetY / window.innerHeight * 2 + 1
  ];
  draw();
});


function draw () {
  regl.poll();
  regl.clear({color: [0.2, 0.2, 0.2, 1]});
  drawLines([{
    ...lineData,
    translate: [0, -0.4],
    cap: 'round',
    join: 'round'
  }, {
    ...lineData,
    translate: [0, 0.4],
    cap: 'round',
    join: 'miter'
  }]);
}

draw();
window.addEventListener('resize', draw);