Search code examples
csssvghtml5-canvasgradient

CSS/SVG/Canvas: Dynamic / Moving Gradient


I was looking into ways to create a div with a dynamic/moving background based on a radial gradient with three colors like this:

enter image description here

The ideia is to have a center point like this one:

enter image description here

Dragging the animation and mixing the colors in more or less random ways.

I tried to search how to this with CSS but all I can find are simple static radial examples. What's the best way to approach this?


Solution

  • First step is to figure our how to make 1 of those. Then you can rotate it or whatever. I have found it to look like a color wheel. I adapted code from https://stackoverflow.com/a/39399649/3807365

    1. I've add offset to the hue as you get away from center for the spiral effect
    2. I've incorporated mouse position into the calculations of the center.
    3. It's a start you can tweak it further.

    var mx = 0
    var my = 0
    var diameter = 200
    var wheel
    var can
    var ctx
    var rotate = 0
    
    
    main()
    
    
    function newEl(tag) {
      return document.createElement(tag)
    }
    
    
    function main() {
      wheel = makeWheel(diameter);
      wheel.addEventListener('mousemove', moveCenter);
      document.body.appendChild(wheel);
    }
    
    
    function hsv2rgb(hsv) {
      var h = hsv.hue,
        s = hsv.sat,
        v = hsv.val;
      var rgb, i, data = [];
      if (s === 0) {
        rgb = [v, v, v];
      } else {
        h = h / 60;
        i = Math.floor(h);
        data = [v * (1 - s), v * (1 - s * (h - i)), v * (1 - s * (1 - (h - i)))];
        switch (i) {
          case 0:
            rgb = [v, data[2], data[0]];
            break;
          case 1:
            rgb = [data[1], v, data[0]];
            break;
          case 2:
            rgb = [data[0], v, data[2]];
            break;
          case 3:
            rgb = [data[0], data[1], v];
            break;
          case 4:
            rgb = [data[2], data[0], v];
            break;
          default:
            rgb = [v, data[0], data[1]];
            break;
        }
      }
      return rgb;
    };
    
    function clamp(min, max, val) {
      if (val < min) return min;
      if (val > max) return max;
      return val;
    }
    
    
    
    function drawWheel(ctx) {
      ctx.clearRect(0, 0, can.width, can.height);
    
      var imgData = ctx.getImageData(0, 0, diameter, diameter);
      var maxRange = diameter / 2;
    
      for (var y = 0; y < diameter; y++) {
        for (var x = 0; x < diameter; x++) {
          var xPos = x - (diameter / 2) - mx;
          var yPos = (diameter - y) - (diameter / 2) - my;
    
          var polar = pos2polar({
            x: xPos,
            y: yPos
          });
    
          var hueOffset = 50 * (polar.len / maxRange);
    
          var hue = polar.ang + hueOffset;
    
          hue = hue % 360;
          while (hue < 0) {
            hue += 360;
          }
    
          var sat = clamp(0, 1, polar.len / (maxRange / 2));
          var val = clamp(0, 1, (maxRange - polar.len) / (maxRange / 2));
    
          var rgb = hsv2rgb({
            hue: hue,
            sat: sat,
            val: val
          });
    
          var index = 4 * (x + y * diameter);
          imgData.data[index + 0] = rgb[0] * 255;
          imgData.data[index + 1] = rgb[1] * 255;
          imgData.data[index + 2] = rgb[2] * 255;
          imgData.data[index + 3] = 255;
        }
      }
      ctx.putImageData(imgData, 0, 0);
    }
    
    function makeWheel(diameter) {
      can = newEl('canvas');
      ctx = can.getContext('2d');
      can.width = diameter;
      can.height = diameter;
    
      drawWheel(ctx)
      return can;
    }
    
    function deg2rad(deg) {
      return (deg / 360) * (2 * Math.PI);
    }
    
    function rad2deg(rad) {
      return (rad / (Math.PI * 2)) * 360;
    }
    
    function pos2polar(inPos) {
      var vecLen = Math.sqrt((inPos.x + mx) * (inPos.x + mx) + (inPos.y + my) * (inPos.y + my));
      var something = Math.atan2(inPos.y, inPos.x) + deg2rad(rotate);
      while (something < 0)
        something += 2 * Math.PI;
    
      return {
        ang: rad2deg(something),
        len: vecLen
      };
    }
    
    
    
    function moveCenter(event) {
      var color = document.getElementById('color');
    
      mx = -diameter / 2 + event.offsetX;
      my = -1 * (-diameter / 2 + event.offsetY);
      var rx = mx / (diameter / 2)
      var ry = my / (diameter / 2)
    
      rotate = Math.sqrt(rx * rx + ry * ry) * 360
      color.innerText = "" + mx + ", " + my
    
    }
    
    function loop() {
      drawWheel(ctx)
      requestAnimationFrame(loop)
    }
    loop()
    canvas {
      border: solid 1px red;
    }
    <div id="color" style="width: 200px; height: 50px; float: left;">Move mouse</div>