Smooth gradient color transition automatically

Asked

Viewed 2,106 times

9

Hello, I own a div with the following css:

background: linear-gradient(to right, #3e42e4, #e53472);

And I would like the colors to change through Javascript.

Similar to the top of this site: Pixelapse

How can I do that?

Note: I’ve thought of something like:

setInterval(function() {
var cor1 = (cor gerada);
var cor2 = (cor gerada);

$("div").css("background", "linear-gradient(to right, cor1, cor2)";
}, 500);

But I don’t know how I would generate the colors nor if my logic is correct.

Edit

Obs².: The problem is that so (as in the suggested answers) the colors change in a sudden way, and in reference site, as you can see the colors will change according to the current color, and leaves in a more "smooth" way. So it is this effect that would like.

  • 2

    I believe this helps you: http://stackoverflow.com/questions/1484506/random-color-generator-in-javascript

  • Thank you for replying, please read my comment in the reply given by Fernando.

  • 2

    I suggest [Dit] the question putting this relevant detail, it is not clear only to see the site in question (the colors change very slowly) and the comments in the answers should refer to the answer itself - not add necessary information to the question.

  • To those interested, I posted a Webgl version.

6 answers

8


Although you say "random", the site in question seems to follow a predefined sequence - and nothing random. If you have two colors A and B and would like to make the smooth transition towards the colors C and D, just go incrementing/decreasing each of its components. If you do this randomly, the result will hardly be as you want, because the trend will always be to return to the original color.

Here is an example of a single transition. If you want more than one, an option is that at the end of the first (i.e. when progresso come to 1) you update the current colors and draw new colors "target", resetting progresso for 0 and start all over again...

var origem1 = [0, 255, 0]; // Cor 1 inicial
var origem2 = [255, 128, 64]; // Cor 2 inicial

var alvo1 = [255, 0, 0]; // Cor 1 final
var alvo2 = [0, 0, 255]; // Cor 2 final

// etc
var predefinidas = [
     [[255,255,0],   [255,0,255]],
     [[255,255,255], [255,255,0]],
     [[0,255,255],   [0,255,0]]
];
var indice = -1;

var progresso = 0; // Progresso em relação a cor 2; começa com 0 (só cor inicial)

// Faz uma média ponderada de cada componente das cores 1 e 2
function calcular(cor1, cor2, progresso) {
  var ret = [];
  for ( var i = 0 ; i < cor1.length ; i++ )
    ret[i] = Math.round(cor2[i] * progresso + cor1[i] * (1 - progresso));
  return ret;
}

setInterval(function() {
  // Atualiza o progresso
  progresso += 0.01;
  if ( progresso > 1 ) { // Se chegou ao final
    progresso = 0; // Reseta o progresso

    origem1 = alvo1; // A cor alvo agora é cor origem
    origem2 = alvo2;

    indice++; // Pega o próximo par de cores
    if ( indice < predefinidas.length ) {
        alvo1 = predefinidas[indice][0];
        alvo2 = predefinidas[indice][1];
    }
    else { // Ou sorteia uma nova cor
        alvo1 = [Math.floor(Math.random()*256), Math.floor(Math.random()*256), Math.floor(Math.random()*256)];
        alvo2 = [Math.floor(Math.random()*256), Math.floor(Math.random()*256), Math.floor(Math.random()*256)];
        // Nota: nada garante que as cores sorteadas não sejam iguais ou muito próximas
    }
  }

  // Calcula as cores para essa "rodada"
  var cor1 = "rgb(" + calcular(origem1, alvo1, progresso).join(",") + ")";
  var cor2 = "rgb(" + calcular(origem2, alvo2, progresso).join(",") + ")";

  // Atribui no CSS
  $("div").css("background", "linear-gradient(to right, " + cor1 + ", " + cor2 + ")");
}, 500);
div {
  border: 1px solid black;
  width: 200px;
  height: 200px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div></div>

  • Perfect! Exactly how I needed it. Just one more question, how do I add more colors? For example origem3, origem4 and alvo3, alvo4?

  • 1

    @Igor I will update my answer with an example. But my recommendation is to adapt the suggestion of Sergio, because it should be much more "light" for the browser.

  • All right, @mgibsonbr. But how could I adapt it to match the effect you have developed that the codes are generated randomly?

  • @Igor In his code the transition occurs very quickly (less than 1 second), if you want it to change very slowly increase the interval in transition: opacity .8s; (try 10s for example). If you do this, adjust the setTimeout and the setInterval accordingly (10100 ms and 11000 ms for example). Finally, you can replace the getRandomColor by another looking for a predefined list, if you do not want to draw a new pair of colors for each change (my example above does both - first uses predefined colors, then goes to drawn colors when the list ends).

6

Unfortunately CSS Transitions do not help to make gradient transitions... To make smooth gradient transitions you can calculate via Javascript and apply to each change (half heavy, I’ll give you an example) or use another element and transition between them with opacity.

To randomly generate colors you can use it like this:

function getRandomColor() {
    var letters = '0123456789ABCDEF'.split('');
    var color = '#';
    for (var i = 0; i < 6; i++ ) {
        color += letters[Math.floor(Math.random() * 16)];
    }
    return color;
}

To use an element to help transition you need to use a combination of CSS classes and a function that assigns the new CSS to the help/support div and expects the transition to be complete and then hides it again.

jsFiddle: https://jsfiddle.net/wzkgz28a/

function getRandomColor() {
    var letters = '0123456789ABCDEF'.split('');
    var color = '#';
    for (var i = 0; i < 6; i++) {
        color += letters[Math.floor(Math.random() * 16)];
    }
    return color;
}

function colorir() {
    var original = document.getElementById('original'),
        ajuda = document.getElementById('ajuda'),
        a = getRandomColor(),
        b = getRandomColor();

    ajuda.style.backgroundImage = 'linear-gradient(to right, ' + a + ', ' + b + ')';
    ajuda.style.opacity = 1;

    setTimeout(function () {
        original.style.backgroundImage = 'linear-gradient(to right, ' + a + ', ' + b + ')';
        var subst = ajuda.cloneNode(true);
        subst.classList.add('reset');
        ajuda.parentNode.replaceChild(subst, ajuda);
        ajuda = subst;
        ajuda.style.opacity = 0;
        ajuda.classList.remove('reset');

    }, 5200);
}
setInterval(colorir, 5300);
colorir();
div div {
    width: 500px;
    height: 500px;
    background: linear-gradient(to right, #3e42e4, #e53472);
    position: absolute;
    left: 0;
    top: 0;
    transition: opacity .8s;
}
#ajuda {
    opacity: 0;
    z-index: 100;
}
.reset {
    transition: none;
    opacity: 0;
}
<div>
    <div id="original"></div>
    <div id="ajuda"></div>
</div>

Note that I had to apply a gambit to reset the transition because of the Browser flow that does not directly apply some changes to improve performance. You can read about it here in English.

  • Thank you, @Sergio. As always available to help. Your code was the one that got closer than I need. The effect of the transition became very good, but in this way, even with transition the color changes, for example, from the gradient between green and blue to purple and orange and I need it to change color in relation to the current, for example, go from blue and go lightening until you get another color, etc. Just like the link I passed in the question. Thank you!

  • 1

    I confess that I’m having a little trouble understanding how this solution works (and adapt it to the transition become slower - the best I could). I’ve seen that when you do ajuda.style.opacity = 1; CSS ensures that the opacity transition occurs slowly, but when ajuda.classList.add('reset');ajuda.style.opacity = 0;ajuda.classList.remove('reset'); what happens? In my understanding the opacity should go to zero immediately, is that correct? I saw that if you don’t use less than half the value of setInterval troublesome.

  • 1

    On the other hand, if I take the ajuda.classList.remove('reset'); and move it to just before you do ajuda.style.opacity = 1; he seems to behave more the way I expected...

  • 1

    @mgibsonbr pertinent remark! My logic is apparently correct but the browser flow is shuffling things. Apparently there are gambiarras for this, together a version and uses half brute force and replaces the element of the DOM: https://jsfiddle.net/wzkgz28a/

6

Damn life... they forgot about Webgl!

Downside:

  • old browsers do not support
  • the code is a little bigger (because it needs to boot Webgl)
  • requires some knowledge of Webgl (not to err in the order of things)

Upside:

  • very fast
  • pliability
  • for me this is beautiful (hehe, disregard =)

There goes the alternative using canvas and Webgl:

window.onload = main;

var randomizeColors;
var fixedColors;
var rotacionar0;
var rotacionar1;

function main() {
  // Obtendo o contexto WebGL
  var canvas = document.getElementById("canvas");
  var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");

  // inicializando o programa GLSL
  var vertexShader = createShaderFromScriptElement(gl, "shader-vs");
  var fragmentShader = createShaderFromScriptElement(gl, "shader-fs");
  var program = createProgram(gl, [vertexShader, fragmentShader]);
  gl.useProgram(program);

  // Criando o buffer e definindo as variáveis de posição dos vértices, além dos dados do buffer
  var aVertexPosition = gl.getAttribLocation(program, "aVertexPosition");
  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array([+1.0, +1.0, +1.0, -1.0, -1.0, +1.0, -1.0, -1.0]),
    gl.STATIC_DRAW);

  gl.enableVertexAttribArray(aVertexPosition);
  gl.vertexAttribPointer(aVertexPosition, 2, gl.FLOAT, false, 0, 0);

  // Criando o buffer e definindo as variáveis de cores iniciais e finais
  var colorLocation0 = gl.getAttribLocation(program, "aVertexColor0");
  gl.enableVertexAttribArray(colorLocation0);
  var bufferColors0 = gl.createBuffer();

  var colorLocation1 = gl.getAttribLocation(program, "aVertexColor1");
  gl.enableVertexAttribArray(colorLocation1);
  var bufferColors1 = gl.createBuffer();

  function corAleatoria() {
    var c = [Math.random(), Math.random(), Math.random()];
    var max = Math.max.apply(null, c);
    var c2 = c.map(function(i) {
      return i / max;
    });
    return c2;
  }

  function corAleatoriaX4() {
    var c = [];
    c.push.apply(c, corAleatoria());
    c.push.apply(c, corAleatoria());
    c.push.apply(c, corAleatoria());
    c.push.apply(c, corAleatoria());
    return c;
  }

  var corInicial, corFinal;

  function setColors(cores0, cores1) {
    corInicial = cores0;
    corFinal = cores1;

    gl.bindBuffer(gl.ARRAY_BUFFER, bufferColors0);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(corInicial), gl.STATIC_DRAW);
    gl.vertexAttribPointer(colorLocation0, 3, gl.FLOAT, false, 0, 0);

    gl.bindBuffer(gl.ARRAY_BUFFER, bufferColors1);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(corFinal), gl.STATIC_DRAW);
    gl.vertexAttribPointer(colorLocation1, 3, gl.FLOAT, false, 0, 0);
  }

  function rotacionar(lista) {
    lista.push(lista.shift());
    lista.push(lista.shift());
    lista.push(lista.shift());
    setColors(corInicial, corFinal);
  }

  rotacionar0 = function() {
    rotacionar(corInicial);
  };
  rotacionar1 = function() {
    rotacionar(corFinal);
  };

  randomizeColors = function() {
    setColors(
      corAleatoriaX4(),
      corAleatoriaX4());
  }

  fixedColors = function() {
    setColors(
      [0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0], [1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1]);
  }

  fixedColors();

  var uTempo = gl.getUniformLocation(program, "uTempo");
  var tempo = 0.0;

  setInterval(function() {
    gl.uniform1f(uTempo, tempo);

    // desenhando o fundo
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

    tempo += 0.1;
  }, 100);
}
<script src="http://greggman.github.io/webgl-fundamentals/webgl/resources/webgl-utils.js"></script>
<div style="height:0;">
  <canvas id="canvas" width="600" height="150"></canvas>
</div>
<div style="position: relative; width:600px; height: 150px; padding: 10px;">
  <button onclick="randomizeColors()">randomizeColors</button>
  <button onclick="fixedColors()">fixedColors</button>
  <button onclick="rotacionar0()">rotacionar0</button>
  <button onclick="rotacionar1()">rotacionar1</button>
</div>

<script id="shader-vs" type="x-shader/x-vertex">
        const float speed = 0.1;

        attribute vec3 aVertexPosition;
        attribute vec3 aVertexColor0;
        attribute vec3 aVertexColor1;
        uniform float uTempo;

        varying lowp vec4 vColor;

        const float PI = 3.14159265359;
        void main(void) {
            gl_Position = vec4(aVertexPosition, 1.0);
            float t = (cos(uTempo*2.*PI*speed)+1.)*0.5;
            vColor = vec4(aVertexColor0, 1.0) * t + vec4(aVertexColor1, 1.0) * (1.-t);
        }
</script>

<script id="shader-fs" type="x-shader/x-fragment">
        varying lowp vec4 vColor;

        void main(void) {
            gl_FragColor = vColor;
        }
</script>

  • His alternative, although it seems to have a degree of complexity a level above the other alternatives, is very interesting and I believe it has a great performance, and is a good alternative (+1). But there are some other disadvantages compared to the scenario of the question, because you can’t just, put your html menu, for example, inside the tag canvas, so you have to make your menu float over the canvas or something like that. That’s right? Or not?

  • 1

    @Fernando is right, I would have to make them overlap. I changed the example to show a technique to do this.

  • Wow, that’s using a cannon to kill a fly... : P Can’t you do something similar with canvas? (i.e. 2D context) Anyway, however "heavy" it is, this solution is actually very flexible, and you can create some very interesting effects. + 1

  • Huahuahu... is a really big cannon... the cool thing is that after overcoming the barrier of implementing this little monster (I spent about 3 hours on it), additional effects are easier to add directly to the shaders... for example, change the transition speed, in my example I used cos to make gradual transitions, but could be any function. (P.S. to doubt in Portuguese "cannon" has two ~ even? what a weird)

  • About using Context 2d... I actually have 0 experience with it. Already Webgl I already have some. But I had to recall some concepts... so I went straight to Webgl.

  • From the 6 months since I saw this answer I ended up taking a course of Webgl, and I take back what I said about the solution being "heavy" - will see until it is lighter than the alternatives with pure JS rsrs (because the interpolation of colors occur in the GPU). Only though it’s only four vertices, I’d use uniforms to set the 4 colors instead of re-popular a ARRAY_BUFFER with each rendering (I may be mistaken, however, I haven’t had time to gain practical experience with Webgl).

  • 1

    @mgibsonbr It can be heavy to initialize the context of Webgl. uniform is for global data. Since the color of each vertex is distinct, it is necessary to use attribute. It’s actually eight colors, two colors for each of the four vertices. Color arrays are not reset to each frame, only when one of the color change buttons is clicked.

  • Offtopic: samples of shaders Webgl (very cool) - https://www.shadertoy.com/browse

  • True, I had not read the code carefully (the function it uses setTimeout does not update the buffers... and the attribute is required for interpolation, yes). One last tip: maybe the setTimeout could be replaced by a requestAnimationFrame, so that the loop would stop when the page was not visible (saving features, especially the battery of mobile devices). P.S. Ops, that goes for my own answer too hehe!

Show 4 more comments

5

Use this code to create random colors:

function corAleatoria(){
    var pad = '000000';
    return '#' + (pad + Math.floor(0x1000000 * Math.random()).toString(16)).slice(-pad.length);
};

And concatenate the random colors with the style at the time of arrow it with jQuery

Example:

function corAleatoria(){
    var pad = '000000';
    return '#' + (pad + Math.floor(0x1000000 * Math.random()).toString(16)).slice(-pad.length);
};

setInterval(function() {
  var cor1 = corAleatoria();
  var cor2 = corAleatoria();

  $("body").css({
    background: "linear-gradient(to right, " + cor1 + "," + cor2 + ")"
  });
}, 500);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Check out my other solution, with smooth transitions, as per AP requests

  • Thank you for answering, and that’s almost what I want (I got that same result). The problem is that so the colors will change in a sudden way, and on the site, as you can see the colors will change according to the current color, and leaves in a more "soft" way... Do you understand? And thank you again!

  • 2

    Great answer! It doesn’t matter in this case (in others it may matter), but use round a random number causes the first 0 and the last ffffff have half the chance to appear that all other colors. A floor with 0x1000000 wouldn’t have that problem.

  • 1

    Vixi @mgibsonbr, you and bfavaretto, are speaking Greek to me, this code generate random colors I just know it works, but now how? Can you edit the answer with your remarks? Because I didn’t quite understand your remarks enough to repair the code myself. Thank you!

  • 2

    Sorry, I wrote that on the phone. Here’s the thing, suppose your account gives the value 500 (decimal). That value with .toString(16) results in 1f4. But to use the background you would need it to be 0001f4, that is, you need to add 3 zeros to the left. The amount of zeros will vary from 0 to 5 depending on the result of your account.

  • 2

    What I meant to say is that if you are drawing a value from 0 to 9 (to simplify) and you use round, the values 0...0,499 will turn 0, the values 0,5...1,499 will turn 1, the values 1,5...2,499 will turn 2, etc, and the values 8,5...8,999 will turn 9. Both 0 and 9 have half the chance to appear as others. Already with floor, 0...0,999 flipped 0, 1...1,999 flipped 1, and 9...9,999 flipped 9 - all are drawn with equal probability. My suggestion is therefore Math.floor(0x1000000*Math.random())

  • @mgibsonbr, I edited according to your recommendations. Check if I understand correctly?

  • 1

    @Fernando Yes, that’s right. Incidentally, interesting this method of doing padding, did not know! It would have saved me a lot of anger in some past projects... : P

Show 2 more comments

5

You can do it this way:

Create a function to randomly generate RGB color:

function getRandomColor() {
  var letters = '0123456789ABCDEF'.split('');
  var color = '#';
  for (var i = 0; i < 6; i++ ) {
      color += letters[Math.floor(Math.random() * 16)];
  }
  return color;
}

Then inside your setInterval calls the function to generate the Random color in the css exchange:

setInterval(function() {
  $("div").css("background", "linear-gradient(to right,"+ getRandomColor()+", "+getRandomColor()+")");
}, 500);

See working on Jsfiddle.

  • Thank you for answering. Please see the comment I made about Fernando’s reply.

2

I’m posting as another answer because it’s totally different from my first answer (I want to keep it, because it brings a different but useful approach). And this was created to address the new requests made by the AP, in the comments and in the issue the question. (smooth and manageable transitions)

Consists of the following (follow the comments and put to rotate):

// construtor do gerenciador de cor, você pode especificar varios parâmetros 
// para obter o seu resultado esperado, como a cor RGB inicial, fator de troca 
// de cor para cada cor, valor maximo e minino que cada cor deve chegar 
// (esse é extremamente util para manter uma cor sempre dentro de uma faixa)
function ColorManager(r /* red */ , g /* green */ , b /* blur */ , fatorR, fatorG, fatorB, maxR, maxG, maxB, minR, minG, minB) {
    return {
        r: r,
        g: g,
        b: b,
        fatorR: fatorR,
        fatorB: fatorB,
        fatorG: fatorG,
        maxR: maxR,
        maxG: maxG,
        maxB: maxB,
        minR: minR,
        minG: minG,
        minB: minB,
        toRGBA: function() {
            return "rgba(" + Math.round(this.r) + ", " + Math.round(this.g) + ", " + Math.round(this.b) + ", 1)";
        },
        apply: function() {
            this.r += this.fatorR;
            this.b += this.fatorB;
            this.g += this.fatorG;
            if (this.r > this.maxR || this.r < this.minR) {
                this.r -= this.fatorR;
                this.fatorR = this.fatorR * -1;
            }
            if (this.g > this.maxG || this.g < this.minG) {
                this.g -= this.fatorG;
                this.fatorG = this.fatorG * -1;
            }  
            if (this.b > this.maxB || this.b < this.minB) {
                this.b -= this.fatorB;
                this.fatorB = this.fatorB * -1;
            }
            return this;
        }
    };
};
    
// aqui você faz todas as configurações do Color
var cor1 = new ColorManager(0, 0, 0, 0.5, 0.3, 0.1, 255, 255, 255, 0, 0, 0);
var cor2 = new ColorManager(255, 255, 255, 1, 2, 3, 255, 255, 255, 0, 0, 0);

// cria o timer
setInterval(function() {
    $("body").css({
        background: "linear-gradient(to right, " + cor1.apply().toRGBA() + "," + cor2.apply().toRGBA() + ")"
    });
}, 500);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

To test settings

Test by changing the parameters in this jsFiddle

Browser other questions tagged

You are not signed in. Login or sign up in order to post.