diff --git a/README.md b/README.md
index b5dad7e8..c600c4bb 100644
--- a/README.md
+++ b/README.md
@@ -70,12 +70,17 @@ Remember to use the proper HTML markup to get radio and checkboxes styled correc
# Plugins
-Material Design for Bootstrap comes with styling support for various Bootstrap plugins, at the moment only one plugin is supported but others will come:
+Material Design for Bootstrap comes with styling support for various external scripts, at the moment only two scripts are supported but others will come:
### SnackbarJS
Create snackbars and toasts with [SnackbarJS plugin](https://github.com/FezVrasta/snackbarjs), the default toast style is the squared one (snackbar style), if you like to use the rounded style (toast style) please add the `toast` class to the `style` option of SnackbarJS.
+### RipplesJS
+
+This is part of Material Design for Bootstrap project and is a plain Javascript script which creates the ripple effect on click of the defined elements.
+At the moment RipplesJS has not an own repository but probably in future it will have one.
+
# Compatibility
diff --git a/css-compiled/material.css b/css-compiled/material.css
index 081a85fc..a58934b0 100644
--- a/css-compiled/material.css
+++ b/css-compiled/material.css
@@ -315,44 +315,6 @@ h6,
.btn-group-flat {
box-shadow: none !important;
}
-.ripple-wrapper {
- position: absolute;
- top: 0;
- left: 0;
- z-index: 1;
- width: 100%;
- height: 100%;
- overflow: hidden;
- border-radius: 2px;
-}
-.ripple {
- position: absolute;
- width: 20px;
- height: 20px;
- margin-left: -10px;
- margin-top: -10px;
- border-radius: 100%;
- background-color: rgba(0, 0, 0, 0.05);
- -webkit-transform: scale(1);
- -ms-transform: scale(1);
- transform: scale(1);
- -webkit-transform-origin: 50%;
- -ms-transform-origin: 50%;
- transform-origin: 50%;
- opacity: 0;
-}
-.ripple.ripple-on {
- -webkit-transition: opacity 0.15s ease-in 0s, -webkit-transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s;
- -ms-transition: opacity 0.15s ease-in 0s, -ms-transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s;
- -moz-transition: opacity 0.15s ease-in 0s, -moz-transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s;
- transition: opacity 0.15s ease-in 0s, transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s;
- opacity: 1;
-}
-.ripple.ripple-out {
- -webkit-transition: opacity 1s linear 0s !important;
- transition: opacity 0.8s linear 0s !important;
- opacity: 0;
-}
.form-horizontal .checkbox {
padding-top: 15px;
}
diff --git a/css-compiled/ripples.css b/css-compiled/ripples.css
new file mode 100644
index 00000000..a4967241
--- /dev/null
+++ b/css-compiled/ripples.css
@@ -0,0 +1,42 @@
+/* Generated by less 1.7.0 */
+.withripple {
+ position: relative;
+}
+.ripple-wrapper {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 1;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ border-radius: 2px;
+}
+.ripple {
+ position: absolute;
+ width: 20px;
+ height: 20px;
+ margin-left: -10px;
+ margin-top: -10px;
+ border-radius: 100%;
+ background-color: rgba(0, 0, 0, 0.05);
+ -webkit-transform: scale(1);
+ -ms-transform: scale(1);
+ transform: scale(1);
+ -webkit-transform-origin: 50%;
+ -ms-transform-origin: 50%;
+ transform-origin: 50%;
+ opacity: 0;
+}
+.ripple.ripple-on {
+ -webkit-transition: opacity 0.15s ease-in 0s, -webkit-transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s;
+ -ms-transition: opacity 0.15s ease-in 0s, -ms-transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s;
+ -moz-transition: opacity 0.15s ease-in 0s, -moz-transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s;
+ transition: opacity 0.15s ease-in 0s, transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s;
+ opacity: 1;
+}
+.ripple.ripple-out {
+ -webkit-transition: opacity 1s linear 0s !important;
+ transition: opacity 0.8s linear 0s !important;
+ opacity: 0;
+}
diff --git a/less/material.less b/less/material.less
index cd22f503..ce2d9058 100644
--- a/less/material.less
+++ b/less/material.less
@@ -26,9 +26,6 @@ body, h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
// Buttons
@import "buttons.less";
-// Ripple effect
-@import "ripple.less";
-
// Checkboxes
@import "checkboxes.less";
diff --git a/less/ripple.less b/less/ripples.less
similarity index 93%
rename from less/ripple.less
rename to less/ripples.less
index 77a9a909..d51b0862 100644
--- a/less/ripple.less
+++ b/less/ripples.less
@@ -1,5 +1,8 @@
-// main: material.less
+// out: ../css-compiled/ripples.css
+.withripple {
+ position: relative;
+}
.ripple-wrapper {
position: absolute;
top: 0;
diff --git a/scripts/material.js b/scripts/material.js
index 57b812d1..85cb16eb 100644
--- a/scripts/material.js
+++ b/scripts/material.js
@@ -1,9 +1,8 @@
-$(function (){
- // with ripple elements
- var withRipple = ".btn:not('.btn-link'), .navbar a, .nav-tabs a";
+/* globals ripples */
- // Add ripple elements to material buttons
- $(withRipple).append("
");
+$(function (){
+
+ ripples.init(".btn:not(.btn-link), .navbar a, .nav-tabs a, .withripple");
// Add fake-checkbox to material checkboxes
$(".checkbox label input").after("");
@@ -25,74 +24,6 @@ $(function (){
}
});
-
- var mouseDown = false;
- $(document).mousedown(function() {
- mouseDown = true;
- }).mouseup(function() {
- mouseDown = false;
- });
-
- // Material buttons engine
- $(document).on("mousedown", withRipple, function(e){
- // Cache elements
- var $self = $(this),
- $rippleWrapper = $self.find(".ripple-wrapper"),
- $ripple = $self.find(".ripple");
-
- // Remove previous animation
- $ripple.attr("class", "ripple");
- $rippleWrapper.stop(true, true);
-
- // Get mouse position
- var parentOffset = $self.offset();
- var relX = e.pageX - parentOffset.left;
- var relY = e.pageY - parentOffset.top;
-
- // Move ripple to the click position
- $ripple.attr({"style": "top: " + relY + "px; left:" + relX + "px"});
-
- // Start the animation
- $rippleWrapper.attr("class", "ripple-wrapper").data("animating", true);
- var scaleVal = "scale(" + Math.round($rippleWrapper.width() / 10) + ")";
- $ripple.attr("class", "ripple ripple-on").css({
- "-ms-transform": scaleVal,
- "-moz-transform": scaleVal,
- "-webkit-transform": scaleVal,
- "transform": scaleVal
- });
- setTimeout(function() {
- $rippleWrapper.attr("class", "ripple-wrapper").data("animating", false).trigger("rippleEnd");
- }, 500);
- })
- .on("rippleEnd", withRipple, function() {
- if (!mouseDown) {
- var $self = $(this),
- $rippleWrapper = $self.find(".ripple-wrapper"),
- $ripple = $self.find(".ripple");
-
- rippleOut($ripple, $rippleWrapper);
- }
- })
- .on("mouseup mouseleave", withRipple, function() {
- var $self = $(this),
- $rippleWrapper = $self.find(".ripple-wrapper"),
- $ripple = $self.find(".ripple");
-
- if (!$rippleWrapper.data("animating")) {
- rippleOut($ripple, $rippleWrapper);
- }
-
- });
-
- var rippleOut = function($ripple, $rippleWrapper) {
- $ripple.attr("class", "ripple ripple-on ripple-out");
- $rippleWrapper.fadeOut(800, function() {
- $rippleWrapper.attr("class", "ripple-wrapper").attr("style", "");
- $ripple.attr("class", "ripple").attr("style", "");
- });
- };
-
// Material inputs engine (ripple effect)
$(document).on("click", ".checkbox label, .radio label", function() {
var $ripple = $(this).find(".ripple"),
diff --git a/scripts/ripples.js b/scripts/ripples.js
new file mode 100644
index 00000000..4336dbe3
--- /dev/null
+++ b/scripts/ripples.js
@@ -0,0 +1,110 @@
+/* globals CustomEvent */
+var ripples = {
+ init : function(withRipple) {
+ "use strict";
+
+ // Helper to bind events on dynamically created elements
+ var bind = function(event, selector, callback) {
+ document.addEventListener(event, function(e) {
+ var target = (typeof e.detail !== "number") ? e.detail : e.target;
+
+ if (target.matches(selector)) {
+ callback(e, target);
+ }
+ });
+ };
+
+ var rippleStart = function(e, target) {
+
+ // Init variables
+ var $rippleWrapper = (target.matches(".ripple-wrapper")) ? target : target.parentNode,
+ $el = $rippleWrapper.parentNode,
+ $ripple = document.createElement("div"),
+ elPos = $el.getBoundingClientRect(),
+ mousePos = {x: e.clientX - elPos.left, y: e.clientY - elPos.top},
+ scale = "transform:scale(" + Math.round($rippleWrapper.offsetWidth / 5) + ")",
+ rippleEnd = new CustomEvent("rippleEnd", {detail: $ripple}),
+ refreshElementStyle;
+
+ // Set ripple class
+ $ripple.className = "ripple";
+
+ // Move ripple to the mouse position
+ $ripple.setAttribute("style", "left:" + mousePos.x + "px; top:" + mousePos.y + "px;");
+
+ // Insert new ripple into ripple wrapper
+ $rippleWrapper.appendChild($ripple);
+
+ // Make sure the ripple has the class applied (ugly hack but it works)
+ refreshElementStyle = window.getComputedStyle($ripple).opacity;
+
+ // Let other funtions know that this element is animating
+ $ripple.dataset.animating = 1;
+
+ // Set scale value to ripple and animate it
+ $ripple.className = "ripple ripple-on";
+ $ripple.setAttribute("style", $ripple.getAttribute("style") + ["-ms-" + scale,"-moz-" + scale,"-webkit-" + scale,scale].join(";"));
+
+ // This function is called when the animation is finished
+ setTimeout(function() {
+
+ // Let know to other functions that this element has finished the animation
+ $ripple.dataset.animating = 0;
+ document.dispatchEvent(rippleEnd);
+
+ }, 500);
+
+ };
+
+ var rippleOut = function($ripple) {
+ // Clear previous animation
+ $ripple.className = "ripple ripple-on ripple-out";
+
+ // Let ripple fade out (with CSS)
+ setTimeout(function() {
+ $ripple.remove();
+ }, 1000);
+ };
+
+ // Helper, need to know if mouse is up or down
+ var mouseDown = false;
+ document.body.onmousedown = function() {
+ mouseDown = true;
+ };
+ document.body.onmouseup = function() {
+ mouseDown = false;
+ };
+
+ // Append ripple wrapper if not exists already
+ var rippleInit = function(e, target) {
+
+ if (target.getElementsByClassName("ripple-wrapper").length === 0) {
+ target.className += " withripple";
+ var $rippleWrapper = document.createElement("div");
+ $rippleWrapper.className = "ripple-wrapper";
+ target.appendChild($rippleWrapper);
+ rippleStart(e, $rippleWrapper);
+ }
+
+ };
+
+ // Events handler
+ // init RippleJS and start ripple effect on mousedown
+ bind("mousedown", withRipple, rippleInit);
+ // start ripple effect on mousedown
+ bind("mousedown", ".ripple-wrapper, .ripple", rippleStart);
+ // if animation ends and user is not holding mouse then destroy the ripple
+ bind("rippleEnd", ".ripple-wrapper, .ripple", function(e, $ripple) {
+ if (!mouseDown) {
+ rippleOut($ripple);
+ }
+ });
+ // Destroy ripple when mouse is not holded anymore if the ripple still exists
+ bind("mouseup", ".ripple-wrapper, .ripple", function(e, $ripple) {
+ if ($ripple.dataset.animating != 1) {
+ rippleOut($ripple);
+ }
+ });
+
+ }
+};