Search code examples
javascriptcsssvgcanvasgsap

Create rounded fire effect on mouseover


Stumbled a pretty fire effect in Hobbit's site.This effect I saw actually only in game's.

There are have two block's with left and right side (evil and good)

enter image description here

When you hover one of this block there are have rounded circle of fire

like in bottom

FROM

enter image description here

TO

enter image description here

I created something like that but actually my fire is not showing smoothly like in the site above.I need exactly the same effect like in site please help.

.good {
  height:200px;
  width:200px;
  background-image: url(https://image.ibb.co/gapib8/good_icon.png);
  background-size:cover;
  box-sizing:border-box;
  padding:10px;
}
.good .ring {
  overflow:hidden;

  transition: 1s opacity;
  transition-timing-function:easeInOut;
  opacity:0;
  height:100%;
  width:100%;
  background-image: url(https://image.ibb.co/eJCQpT/fire_ring.png);
  background-repeat:no-repeat;
  background-size:cover;


}
.good:hover .ring {
  opacity:1;

}
<div class="good">
  <div class="ring">

  </div>
</div>


Solution

  • Here is heavyweight WebGL solution using fragment shader

    <img src="https://image.ibb.co/gapib8/good_icon.png">
    <div class="zone"></div>
    <canvas width="335" height="335" id="canvas"/>
    <script type="glsl">
    precision highp float;
    uniform float time;     
    uniform vec2 resolution;
    uniform float fill;      // fill of fire ring (0-1)
    
    vec3 firePalette(float i) {
        float T = 1400. + 1300.*i; 
        vec3 L = vec3(7.4, 5.6, 4.4); 
        L = pow(L,vec3(5.0)) * (exp(1.43876719683e5/(T*L))-1.0);
        return 1.0-exp(-5e8/L); 
    }    
    
    vec3 hash33(vec3 p) { 
        float n = sin(dot(p, vec3(7, 157, 113)));    
        return fract(vec3(2097152, 262144, 32768)*n); 
    }
    
    float voronoi(vec3 p) {
    // https://www.iquilezles.org/www/articles/voronoilines/voronoilines.htm
    	vec3 b, r, g = floor(p);
    	p = fract(p); 
    	float d = 1.; 
    	for(int j = -1; j <= 1; j++) {
    	    for(int i = -1; i <= 1; i++) {
    		    b = vec3(i, j, -1);
    		    r = b - p + hash33(g+b);
    		    d = min(d, dot(r,r));
    		    b.z = 0.0;
    		    r = b - p + hash33(g+b);
    		    d = min(d, dot(r,r));
    		    b.z = 1.;
    		    r = b - p + hash33(g+b);
    		    d = min(d, dot(r,r));	
    	    }
    	}
    	return d;
    }
    
    // https://www.iquilezles.org/www/articles/warp/warp.htm
    float noiseLayers(in vec3 p) {
        vec3 t = vec3(0., 0., p.z+time*1.5); 
        const int iter = 5; 
        float tot = 0., sum = 0., amp = 1.; 
        for (int i = 0; i < iter; i++) {
            tot += voronoi(p + t) * amp; 
            p *= 2.0; 
            t *= 1.5; 
            sum += amp; 
            amp *= 0.5; 
        }
        return tot/sum; 
    }
        
    float sdPie( in vec2 p, in vec2 c, in float r ) {
        p.x = abs(p.x);
        float l = length(p) - r;
    	float m = length(p - c*clamp(dot(p,c),0.0,r) );
        return max(l,m*sign(c.y*p.x-c.x*p.y));
    }
        
    void main(void) {
        float f = (1.-fill)*3.1415;
        vec2 uv = (gl_FragCoord.xy - resolution.xy*0.5)/ resolution.y;
        float cs = cos(f-time/4.), si = sin(f-time/4.);
        uv.xy *= mat2(cs, -si, si, cs); 
        vec3 rd = normalize(vec3(uv.x, uv.y, 3.1415/8.));
        float c = noiseLayers(rd*2.); 
        float d = dot(uv, uv);
        float ts = 0.105 + sin(time)*0.001;
        float ring = 1. - min(smoothstep(d, 0.11, ts), smoothstep(d, ts, 0.11));
        vec3 col = sqrt(clamp((firePalette(c*ring)-0.03)*1.5, 0., 1.));    
        if (fill<1.)
          col *= sdPie(uv,vec2(sin(f+0.001),cos(f+0.001)), 0.5)*3.1;
        gl_FragColor = vec4(col, ring);  
    }
    </script>
    
    <script>
    let gl = canvas.getContext('webgl');
    gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,3,-1,-1,3,-1]), gl.STATIC_DRAW);
    
    let pid = gl.createProgram();
    shader(`attribute vec2 v;void main(void){gl_Position=vec4(v,0.,1.);}`,gl.VERTEX_SHADER);
    shader(document.querySelector(`script[type="glsl"]`).textContent,gl.FRAGMENT_SHADER);
    gl.linkProgram(pid);
    gl.useProgram(pid);
    
    let v = gl.getAttribLocation(pid, "v");
    gl.vertexAttribPointer(v, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(v);
    
    let resolution = gl.getUniformLocation(pid, 'resolution');
    let time = gl.getUniformLocation(pid, 'time');
    let fill = gl.getUniformLocation(pid, 'fill');
    requestAnimationFrame(draw);
    
    function draw(t) {
      gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
      gl.clearColor(0, 0, 0, 0);
      gl.uniform1f(time, t/1000);
      gl.uniform1f(fill, getComputedStyle(canvas).opacity);
      gl.uniform2f(resolution, gl.drawingBufferWidth, gl.drawingBufferHeight);
      gl.drawArrays(gl.TRIANGLES, 0, 3);
      requestAnimationFrame(draw);
    }
    
    function shader(src, type) {
      let sid = gl.createShader(type);
      gl.shaderSource(sid, src);
      gl.compileShader(sid);
      var message = gl.getShaderInfoLog(sid);
      gl.attachShader(pid, sid);
      if (message.length > 0) {
        console.log(src.split("\n").map((s, i) => (""+(1+i)).padStart(4, 0)+": "+s).join("\n"));
        throw message;
      }
    }
    </script>
    
    <style>
    canvas {
      position: absolute;
      top: -29px;
      pointer-events: none;
      left: -26px;
      opacity: 0;
      transition: 1s;
    }
    
    .zone {
      position: absolute;
      top: 16px;
      left: 19px;
      border-radius: 50%;
      width: 245px;
      height: 245px;
    }
    
    div.zone:hover + canvas{
      opacity: 1;
    }
    </style>