Search code examples
javascriptuser-interfacecanvashtml2canvas

Change the color of the "screen" waves


I been stuck on getting the waves to look just like I want. I'm trying to figure out how to get the base of the wave to be the color I need it. I can do my desired color but it blocks the background. I can not see anything behind it because I was using like a reflection. Maybe someone can figure it out cause I'm having difficulties getting it to work... I plan on making the wave drop and rise. Here is a link to the code pen: HERE

Here is where I have the vertical reflection:

var x = $.cx - $.length / 2 + $.length / $.count * i,
    y = height + $.simplex.noise2D($.xoff, $.yoff) * amp + sway;
    $.ctx[i === 0 ? 'moveTo' : 'lineTo'](x, y);
  }

  $.ctx.lineTo($.w, $.h); // -$.h - Vertically reflection
  $.ctx.lineTo(0, $.h); // -$.h - Vertically reflection
  $.ctx.closePath();
  $.ctx.fillStyle = color;

  if (comp) {
    $.ctx.globalCompositeOperation = comp;
  }

  $.ctx.fill();

My desired look for the waves is below:

The way I want it to look

Here is what I got with a successful transparent top, just not the right coloring: Not my desired look


Solution

  • Your problem is that the screen blending of the three colors generates a solid white color, so all the bottom of your canvas becomes white.

    Here I simplified a lot the situation, with just 3 rectangles. Your bottom of canvas is my central white square:

    const c2 = canvas.cloneNode();
    
    const ctx = canvas.getContext("2d");
    
    ctx.globalCompositeOperation = 'screen';
    ctx.fillStyle = '#fb0000';
    ctx.fillRect(0,0,50,50);
    ctx.fillStyle = "#00ff8e";
    ctx.fillRect(12,12,50,50);
    ctx.fillStyle = "#6F33FF";
    ctx.fillRect(25,25,50,50);
    body {
      background: #CCC;
    }
    <canvas id="canvas"></canvas>

    So what we need, is a way to make this central square transparent so that we can draw our background behind.

    To do this, we will need to draw our shapes at least two times:

    • once in normal compositing mode, so that we get the full overlap.
    • once again as source-in compositing mode, so that we get only where all our shapes do overlap.

    const ctx = canvas.getContext("2d");
    
    function drawShapes(mode) {
      ctx.globalCompositeOperation = mode;
      ctx.fillStyle = '#fb0000';
      ctx.fillRect(0,0,50,50);
      ctx.fillStyle = "#00ff8e";
      ctx.fillRect(12,12,50,50);
      ctx.fillStyle = "#6F33FF";
      ctx.fillRect(25,25,50,50);
    }
    
    drawShapes('screen');
    drawShapes('source-in');
    body {
      background: #CCC;
    }
    <canvas id="canvas"></canvas>

    Now we have our overlapping area, we will be able to use it as a cutting shape in a third operation. But to do it, we will need a second, off-screen canvas to perform the compositing of the two states:

    const c2 = canvas.cloneNode();
    
    const ctx = canvas.getContext("2d");
    const ctx2 = c2.getContext("2d");
    
    function drawShapes(ctx, comp) {
      ctx.globalCompositeOperation = comp;
      ctx.fillStyle = '#fb0000';
      ctx.fillRect(0, 0, 50, 50);
      ctx.fillStyle = "#00ff8e";
      ctx.fillRect(12, 12, 50, 50);
      ctx.fillStyle = "#6F33FF";
      ctx.fillRect(25, 25, 50, 50);
    }
    // first draw our screen, with unwanted white square
    drawShapes(ctx, 'screen');
    // draw it on the offscreen canvas
    ctx2.drawImage(ctx.canvas, 0, 0)
    // draw the shapes once again on the offscreen canvas to get the cutting shape
    drawShapes(ctx2, 'source-in');
    // cut the visible canvas
    ctx.globalCompositeOperation = 'destination-out'
    ctx.drawImage(ctx2.canvas, 0, 0);
    body {
      background: #CCC
    }
    <canvas id="canvas"></canvas>

    And voilà, our white square is now transparent, we can draw whatever we want behind our scene using the destination-over composite operation.