<body id="masthead">
  <canvas id="canvasHeader"></canvas>
</body>
#masthead
{
  position: relative;
}

#canvasHeader
{
    width: 100%;
    height: 300px;
}
/*
 *  Constants...
 */
const  N_PNT : number = 4;
const  STEPS : number = 600.0;
const  SPLIT : string = "|";
const  COOKY : string = "colors";



/*
 *  RGB color of a vertex...
 */
class VColor
{
public  R : number;
public  G : number;
public  B : number;



  //
  // Constructor...
  //
  constructor (red? : number, green? : number, blue? : number)
  {
      this.R = red || Math.random ();
      this.G = green || Math.random ();
      this.B = blue || Math.random ();
  }

  //
  //
  //
  public addColor (color : VColor) : void
  {
    this.R += color.R;
    this.G += color.G;
    this.B += color.B;
  }

  //
  //
  //
  public static createStep (start : VColor, finish : VColor, steps : number) : VColor
  {
    var step_R : number = (finish.R - start.R) / steps;
    var step_G : number = (finish.G - start.G) / steps;
    var step_B : number = (finish.B - start.B) / steps;
    return new VColor (step_R, step_G, step_B);    
  }
}


/*
 *  Background animator...
 */
class AnimateBackground
{
// Shader attributes...
private  program           : WebGLProgram;
private  uni_vertex_colors : WebGLUniformLocation;
private  attr_vertex_coord : number;
// Buffers...
private  vertex_buffer     : WebGLBuffer;
// Colors stuff...
private  colors            : Float32Array;
private  current_color     : VColor[];
private  target_color      : VColor[];
private  step_color        : VColor[];
private  current_step      : number=0;


  //
  // Constructor...
  //
  constructor (private canvas : HTMLCanvasElement, private gl : WebGLRenderingContext)
  {
  // Create shaders program...
    this.program = this.createProgram ();
    this.gl.useProgram (this.program);

  // Extract and enable the "vertexCoord" attribute...
    this.attr_vertex_coord = this.gl.getAttribLocation (this.program, "vertexCoord");
    this.gl.enableVertexAttribArray (this.attr_vertex_coord);

  // Create vertices, color and indices buffers...
    this.vertex_buffer = this.gl.createBuffer ();

  // Initialize triangle vertices...
    var vertices = [-1,+1,  +1,+1,  -1,-1,  +1,-1];
    this.gl.bindBuffer (this.gl.ARRAY_BUFFER, this.vertex_buffer);
    this.gl.bufferData (this.gl.ARRAY_BUFFER, new Float32Array (vertices), this.gl.STATIC_DRAW);

  // Initialize background colors...
    this.current_color = new Array (N_PNT);
    this.target_color = new Array (N_PNT);
    this.step_color = new Array (N_PNT);
    var cookie_loaded = this.loadCookie ();
    for (var i = 0; i < this.current_color.length; ++i)
    {
        this.target_color[i] = new VColor ();
        if (!cookie_loaded) this.current_color[i] = new VColor();
        this.step_color[i] = VColor.createStep (this.current_color[i], this.target_color[i], STEPS);
      }
    this.colors = new Float32Array (this.current_color.length * 3);
  }

  //
  // Get and decode the cookie with given name...
  //
  public static getCookie (name : string) : string
  {
    var cookies : string[] = document.cookie.split (";");
    if (cookies)
      for (var i = 0; i < cookies.length; ++i)
        {
          var keyvalue : string[] = cookies[i].split ("=");
          if (keyvalue && (keyvalue.length == 2))
            if (keyvalue[0].trim () == name)
              {
                return decodeURI (keyvalue[1]);
              }
        }
  }

  //
  // Load colors from cookie...
  //
  public loadCookie () : boolean
  {
    var cookie : string = AnimateBackground.getCookie (COOKY);
    if (cookie)
      {
        var array : string[] = cookie.split (SPLIT);
        if (array && (array.length == N_PNT * 3))
          {
            for (var i = 0, j = 0; i < N_PNT; ++i)
              {
                var cR = parseFloat (array[j++]);
                var cG = parseFloat (array[j++]);
                var cB = parseFloat (array[j++]);
                this.current_color[i] = new VColor (cR, cG, cB);
              }
            return true;
          }
      }
    return false;
  }

  //
  // Save colors to cookie...
  //
  public saveCookie () : void
  {
    var str : string = "";
    for (var i = 0; i < this.current_color.length; ++i)
      {
        if (i > 0) str += SPLIT;
        var col : VColor = this.current_color[i];
        str += col.R + SPLIT + col.G + SPLIT + col.B;
      }
    document.cookie = COOKY + "=" + encodeURI (str) + "; path=/";
  }

  //
  // Start the core animation infinite loop...
  //
  public startAnimation ()
  {
    this.animateLoop (0);
  }

  //
  // Draw the background...
  //
  private drawBackground (timekey)
  {
  // Set colors buffer...
    for (var i = 0, j = 0; i < this.current_color.length; ++i)
      {
        this.colors[j++] = this.current_color[i].R;
        this.colors[j++] = this.current_color[i].G;
        this.colors[j++] = this.current_color[i].B;
      }

  // Draw triangles...
    this.gl.bindBuffer (this.gl.ARRAY_BUFFER, this.vertex_buffer);
    this.gl.vertexAttribPointer (this.attr_vertex_coord, 2, this.gl.FLOAT, false, 0, 0);
    this.gl.uniform3fv (this.uni_vertex_colors, this.colors);
    this.gl.drawArrays (this.gl.TRIANGLE_STRIP, 0, N_PNT);
  }

  //
  // Update colors for next frame...
  //
  private nextFrame ()
  {
    this.current_step += 1;
    if (this.current_step >= STEPS)
      {
        this.current_step = 0;
        for (var i = 0; i < this.current_color.length; ++i)
          {
            this.current_color[i] = this.target_color[i];
            this.target_color[i] = new VColor ();
            this.step_color[i] = VColor.createStep (this.current_color[i], this.target_color[i], STEPS);
          }
      }
    else
      {
        for (var i = 0; i < this.current_color.length; ++i)
          {
            this.current_color[i].addColor (this.step_color[i]);
          }
      }
  }

  //
  // Create shader program...
  //
  private createProgram () : WebGLProgram
  {
  // Vertex shader...
    var vertex_shader_text : string = 
     "attribute  vec2  vertexCoord;                  \
      varying    vec2  point2D;                      \
                                                     \
      void main (void)                               \
      {                                              \
        point2D = vertexCoord * 0.5 + 0.5;           \
        gl_Position = vec4 (vertexCoord, 0.0, 1.0);  \
      }";

  // Fragment shader...
    var fragment_shader_text : string = 
    "#define  TL  0\n                 \
     #define  TR  1\n                 \
     #define  BL  2\n                 \
     #define  BR  3\n                 \
                                      \
     precision  mediump  float;       \
                                      \
     varying    vec2     point2D;     \
     uniform    vec3     vColors[4];  \
                                      \
      void main (void)                \
      {                               \
        gl_FragColor = vec4 (mix (mix (vColors[TL], vColors[TR], point2D.x), mix (vColors[BL], vColors[BR], point2D.x), point2D.y), 1); \
      }";

  // Get the shaders from <script> tag...
    var vertex_shader : WebGLShader = this.createShader (this.gl.VERTEX_SHADER, vertex_shader_text);
    var pixel_shader: WebGLShader = this.createShader (this.gl.FRAGMENT_SHADER, fragment_shader_text);
    if (!vertex_shader || !pixel_shader) return null;

  // Create the program...
    var program : WebGLProgram = this.gl.createProgram ();
    this.gl.attachShader (program, vertex_shader);
    this.gl.attachShader (program, pixel_shader);
    this.gl.linkProgram(program);

  // Extract uniform location for the 4 corners vertex colors...
    this.uni_vertex_colors = this.gl.getUniformLocation (program, "vColors");

  // Finished!
    return program;
  }

  //
  // Create a shader of given type with given string...
  //
  private createShader (shaderType : number, shaderText : string) : WebGLShader
  {
    var shader : WebGLShader = this.gl.createShader (shaderType);
    this.gl.shaderSource (shader, shaderText);
    this.gl.compileShader (shader);
    return this.gl.getShaderParameter (shader, this.gl.COMPILE_STATUS) ? shader : null;
  }

  //
  // Make sure (CSS) width/height of canvas is equal to client width/height...
  //
  private applyResize () : boolean
  {
    var ret : boolean = false;
    if (this.canvas.width != this.canvas.clientWidth)
      {
        this.canvas.width = this.canvas.clientWidth;
        ret = true;
      }
    if (this.canvas.height != this.canvas.clientHeight)
      {
        this.canvas.height = this.canvas.clientHeight;
        ret = true;
      }
    return ret;
  }

  //
  // Main animation loop...
  //
  private animateLoop = (timekey) =>
  {
  // Process canvas resize...
    this.applyResize ();
    this.gl.viewport (0, 0, this.gl.drawingBufferWidth, this.gl.drawingBufferHeight);

  // Draw frame...
    this.drawBackground (timekey);

  // Next frame...
    this.nextFrame ();
    requestAnimationFrame (this.animateLoop);
  }
}


var animator : AnimateBackground = null;


window.onload = () => 
{
// Get main header element...
  var header : HTMLElement = document.getElementById ('masthead');
  if (header)
    {
    // Get the canvas element...
      var canvas : HTMLCanvasElement = document.getElementById ('canvasHeader');
      if (canvas)
        {
        // Make sure WebGL context can be obtained...
          var context: WebGLRenderingContext = canvas.getContext ("experimental-webgl");
          if (!context) context = canvas.getContext ("webgl");

        // We have a context...
          if (context)
            {
            // Start animation...
              animator = new AnimateBackground (canvas, context);
              animator.startAnimation ();

            // Remove background from header...
              header.style.background = "transparent";
            }
        }
    }
};


window.onunload = () =>
{
  if (animator) animator.saveCookie ();
};
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.