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); + } + }); + + } +};