Liquid Hover Effect for Divi Button Module
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! ;)
Thanks for this! Just curious – is there a way to do this with a border radius of 0?
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 .
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…
Hmm… interesting. Need to test it more, thanks for letting me know.
This is so freaking awesome! Thanks for sharing!
You’re welcome, Dan ;)
Awesome, Is it possible to add this to a button on the slider?
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.Pretty cool. It would be exponentially cooler with sound. :)
Yeah, splash sounds would be awesome :)