Get a Custom Header Built for Your Divi Website

5

Liquid Hover Effect for Divi Button Module

by Ivan Chiurcci | May 25, 2017 | 10 comments

Using canvas and JavaScript you can achieve really awesome effects. Recently I've come across this cool liquid hover effect and I tried to apply it to Divi button module. It might look like too much code for a single button, but it is experimental and I just wanted to show how it can be implemented for Divi.

Demo:

To implement this effect we'll have to assign a CSS class to the Button Module and apply neccessary settings, then use Javascript to wrap the button text with span and insert the canvas inside the button and apply the liquid hover effect. Also we'll use some CSS for canvas and button text.

Step 1: Apply the following button settings. Important ones are the border radius and transparent background color.

Step 2: Add the btn-liquid CSS class to Button Settings -> Advanced -> CSS Class field.

Step 3: This is the JS code snippet for the liquid hover effect. You can set the color stops for the button dynamic gradient and the background color (see highlighted lines in the code below).

<script type="text/javascript">
/* Liquid Button */
(function($) {

               $( ".btn-liquid" ).wrapInner(function() {
                           return "<span class='inner'></span>";
               });

$(function() {
  // Vars
  var pointsA = [],
    pointsB = [],
    $canvas = null,
    canvas = null,
    context = null,
    vars = null,
    points = 8,
    viscosity = 20,
    mouseDist = 70,
    damping = 0.05,
    showIndicators = false;
    mouseX = 0,
    mouseY = 0,
    relMouseX = 0,
    relMouseY = 0,
    mouseLastX = 0,
    mouseLastY = 0,
    mouseDirectionX = 0,
    mouseDirectionY = 0,
    mouseSpeedX = 0,
    mouseSpeedY = 0;

  /**
   * Get mouse direction
   */
  function mouseDirection(e) {
    if (mouseX < e.pageX)
      mouseDirectionX = 1;
    else if (mouseX > e.pageX)
      mouseDirectionX = -1;
    else
      mouseDirectionX = 0;

    if (mouseY < e.pageY)
      mouseDirectionY = 1;
    else if (mouseY > e.pageY)
      mouseDirectionY = -1;
    else
      mouseDirectionY = 0;

    mouseX = e.pageX;
    mouseY = e.pageY;

    relMouseX = (mouseX - $canvas.offset().left);
    relMouseY = (mouseY - $canvas.offset().top);
  }
  $(document).on('mousemove', mouseDirection);

  /**
   * Get mouse speed
   */
  function mouseSpeed() {
    mouseSpeedX = mouseX - mouseLastX;
    mouseSpeedY = mouseY - mouseLastY;

    mouseLastX = mouseX;
    mouseLastY = mouseY;

    setTimeout(mouseSpeed, 50);
  }
  mouseSpeed();

  /**
   * Init button
   */
  function initButton() {
    // Get button
    var button = $('.btn-liquid');
    var buttonWidth = button.outerWidth();
    var buttonHeight = button.outerHeight();

    // Create canvas
    $canvas = $('<canvas></canvas>');
    button.prepend($canvas);

    canvas = $canvas.get(0);
    canvas.width = buttonWidth+100;
    canvas.height = buttonHeight+100;
    context = canvas.getContext('2d');

    // Add points

    var x = buttonHeight/2;
    for(var j = 1; j < points; j++) {
      addPoints((x+((buttonWidth-buttonHeight)/points)*j), 0);
    }
    addPoints(buttonWidth-buttonHeight/5, 0);
    addPoints(buttonWidth+buttonHeight/10, buttonHeight/2);
    addPoints(buttonWidth-buttonHeight/5, buttonHeight);
    for(var j = points-1; j > 0; j--) {
      addPoints((x+((buttonWidth-buttonHeight)/points)*j), buttonHeight);
    }
    addPoints(buttonHeight/5, buttonHeight);

    addPoints(-buttonHeight/10, buttonHeight/2);
    addPoints(buttonHeight/5, 0);
    // addPoints(x, 0);
    // addPoints(0, buttonHeight/2);

    // addPoints(0, buttonHeight/2);
    // addPoints(buttonHeight/4, 0);

    // Start render
    renderCanvas();
  }

  /**
   * Add points
   */
  function addPoints(x, y) {
    pointsA.push(new Point(x, y, 1));
    pointsB.push(new Point(x, y, 2));
  }

  /**
   * Point
   */
  function Point(x, y, level) {
    this.x = this.ix = 50+x;
    this.y = this.iy = 50+y;
    this.vx = 0;
    this.vy = 0;
    this.cx1 = 0;
    this.cy1 = 0;
    this.cx2 = 0;
    this.cy2 = 0;
    this.level = level;
  }

  Point.prototype.move = function() {
    this.vx += (this.ix - this.x) / (viscosity*this.level);
    this.vy += (this.iy - this.y) / (viscosity*this.level);

    var dx = this.ix - relMouseX,
      dy = this.iy - relMouseY;
    var relDist = (1-Math.sqrt((dx * dx) + (dy * dy))/mouseDist);

    // Move x
    if ((mouseDirectionX > 0 && relMouseX > this.x) || (mouseDirectionX < 0 && relMouseX < this.x)) {
      if (relDist > 0 && relDist < 1) {
        this.vx = (mouseSpeedX / 4) * relDist;
      }
    }
    this.vx *= (1 - damping);
    this.x += this.vx;

    // Move y
    if ((mouseDirectionY > 0 && relMouseY > this.y) || (mouseDirectionY < 0 && relMouseY < this.y)) {
      if (relDist > 0 && relDist < 1) {
        this.vy = (mouseSpeedY / 4) * relDist;
      }
    }
    this.vy *= (1 - damping);
    this.y += this.vy;
  };


  /**
   * Render canvas
   */
  function renderCanvas() {
    // rAF
    rafID = requestAnimationFrame(renderCanvas);

    // Clear scene
    context.clearRect(0, 0, $canvas.width(), $canvas.height());
    context.fillStyle = 'transparent';
    context.fillRect(0, 0, $canvas.width(), $canvas.height());

    // Move points
    for (var i = 0; i <= pointsA.length - 1; i++) {
      pointsA[i].move();
      pointsB[i].move();
    }

    // Create dynamic gradient
    var gradientX = Math.min(Math.max(mouseX - $canvas.offset().left, 0), $canvas.width());
    var gradientY = Math.min(Math.max(mouseY - $canvas.offset().top, 0), $canvas.height());
    var distance = Math.sqrt(Math.pow(gradientX - $canvas.width()/2, 2) + Math.pow(gradientY - $canvas.height()/2, 2)) / Math.sqrt(Math.pow($canvas.width()/2, 2) + Math.pow($canvas.height()/2, 2));

    var gradient = context.createRadialGradient(gradientX, gradientY, 300+(300*distance), gradientX, gradientY, 0);
    
    // Set dynamic gradient color stops here
    gradient.addColorStop(0, '#b30be8');
    gradient.addColorStop(1, '#2f0be8');

    // Draw shapes
    var groups = [pointsA, pointsB]

    for (var j = 0; j <= 1; j++) {
      var points = groups[j];

      if (j == 0) {
        // Background style - set button background color here
        context.fillStyle = '#c30c5a';
      } else {
        // Foreground style
        context.fillStyle = gradient;
      }

      context.beginPath();
      context.moveTo(points[0].x, points[0].y);

      for (var i = 0; i < points.length; i++) {
        var p = points[i];
        var nextP = points[i + 1];
        var val = 30*0.552284749831;

        if (nextP != undefined) {
          // if (nextP.ix > p.ix && nextP.iy < p.iy) {
          // 	p.cx1 = p.x;
          // 	p.cy1 = p.y-val;
          // 	p.cx2 = nextP.x-val;
          // 	p.cy2 = nextP.y;
          // } else if (nextP.ix > p.ix && nextP.iy > p.iy) {
          // 	p.cx1 = p.x+val;
          // 	p.cy1 = p.y;
          // 	p.cx2 = nextP.x;
          // 	p.cy2 = nextP.y-val;
          // }  else if (nextP.ix < p.ix && nextP.iy > p.iy) {
          // 	p.cx1 = p.x;
          // 	p.cy1 = p.y+val;
          // 	p.cx2 = nextP.x+val;
          // 	p.cy2 = nextP.y;
          // } else if (nextP.ix < p.ix && nextP.iy < p.iy) {
          // 	p.cx1 = p.x-val;
          // 	p.cy1 = p.y;
          // 	p.cx2 = nextP.x;
          // 	p.cy2 = nextP.y+val;
          // } else {

            p.cx1 = (p.x+nextP.x)/2;
            p.cy1 = (p.y+nextP.y)/2;
            p.cx2 = (p.x+nextP.x)/2;
            p.cy2 = (p.y+nextP.y)/2;

            context.bezierCurveTo(p.x, p.y, p.cx1, p.cy1, p.cx1, p.cy1);
          // 	continue;
          // }

          // context.bezierCurveTo(p.cx1, p.cy1, p.cx2, p.cy2, nextP.x, nextP.y);
        } else {
nextP = points[0];
            p.cx1 = (p.x+nextP.x)/2;
            p.cy1 = (p.y+nextP.y)/2;

            context.bezierCurveTo(p.x, p.y, p.cx1, p.cy1, p.cx1, p.cy1);
        }
      }

      // context.closePath();
      context.fill();
    }

    if (showIndicators) {
      // Draw points
      context.fillStyle = '#000';
      context.beginPath();
      for (var i = 0; i < pointsA.length; i++) {
        var p = pointsA[i];

        context.rect(p.x - 1, p.y - 1, 2, 2);
      }
      context.fill();

      // Draw controls
      context.fillStyle = '#f00';
      context.beginPath();
      for (var i = 0; i < pointsA.length; i++) {
        var p = pointsA[i];

        context.rect(p.cx1 - 1, p.cy1 - 1, 2, 2);
        context.rect(p.cx2 - 1, p.cy2 - 1, 2, 2);
      }
      context.fill();
    }
  }

  // Init
  initButton();
});


})(jQuery);

/* End Liquid Button */

</script>

Add the JS code snippet into the Divi -> Theme Options -> Integration -> Add code to the < body > field.

Step 4: We need to apply some CSS rules to button text and the canvas, for this we'll use the following CSS snippet:

/* Liquid Button */

.btn-liquid .inner {
  display: block;
  position: relative;
  z-index: 2;
}

.btn-liquid canvas {
  position: absolute;
  top: -50px;
  right: -50px;
  bottom: -50px;
  left: -50px;
  z-index: 1;
}

/* End Liquid Button */

Copy the CSS snippet above and add it into the Divi -> Theme Options -> General -> Custom CSS field.

That's all, enjoy the effect. Subscribe to stay updated and feel free to share your thoughts and suggestions below! ;)

View Live Demos & Get Divi MadMenu

Download FREE Divi Sections

10 Comments

  1. Jessica

    Thanks for this! Just curious – is there a way to do this with a border radius of 0?

    Reply
  2. Dwayne

    Hi , this is an awesome effect. Can it only be used on on button ? I got it working but when I add the class to other buttons the effect only works on the last button the class was added to .

    Reply
  3. Ronald Colijn

    It was great but I am getting a new error so had to remove it, the error is:

    (index):2131 Uncaught TypeError: Failed to execute ‘createRadialGradient’ on ‘CanvasRenderingContext2D’: The provided double value is non-finite.
    at renderCanvas ((index):2131)
    renderCanvas @ (index):2131

    Not sure why…

    Reply
    • Ivan Chi

      Hmm… interesting. Need to test it more, thanks for letting me know.

      Reply
  4. Dan

    This is so freaking awesome! Thanks for sharing!

    Reply
    • Ivan Chi

      You’re welcome, Dan ;)

      Reply
  5. Will

    Awesome, Is it possible to add this to a button on the slider?

    Reply
    • Ivan Chi

      Haven’t tried it but it should work, you only need to adjust the CSS selectors to target the slider button for canvas insertion and maybe apply some CSS rules to the button.

      Reply
  6. Ed

    Pretty cool. It would be exponentially cooler with sound. :)

    Reply
    • Ivan Chi

      Yeah, splash sounds would be awesome :)

      Reply

Submit a Comment

Your email address will not be published. Required fields are marked *

Get FREE Divi Layouts

Download 20+ Divi templates for FREE!

Please confirm your email address to complete your subscription. Thank you!

Copy link