├── .babelrc ├── .env ├── .gitignore ├── LICENSE ├── README.md ├── dist ├── softripple.js └── softripple.min.js ├── package-lock.json ├── package.json ├── src ├── Ripple.js ├── main.js └── utils.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | NAME=SoftRipple 2 | NODE_ENV=production 3 | TARGET=window -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sikriti Dakua 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Soft Ripple 2 | 3 | ![image](https://i.imgur.com/msl0Lti.gif) 4 | 5 | A small Javascript library to create soft ripple interaction 6 | 7 | Check the demos: [here](https://codepen.io/dev_loop/full/KKVKqrq) 8 | 9 | ## Installation 10 | 11 | You can install softripple-js in your project via npm 12 | 13 | ``` 14 | npm install --save softripple-js 15 | ``` 16 | 17 | Or load the ES module directly 18 | 19 | ```html 20 | 21 | ``` 22 | 23 | ## Usage 24 | 25 | It's pretty easy to use. Just import the package an declare SoftRipple with the element you want the ripple effect to take place. 26 | 27 | ```javascript 28 | const rippleEffect = new SoftRipple(el); 29 | ``` 30 | 31 | Also you can add effect to multiple elements at once. 32 | 33 | ```javascript 34 | const el1 = document.getElementById("el1"); 35 | const el2 = document.getElementById("el2"); 36 | const rippleEffect = new SoftRipple([el1, el2]); 37 | ``` 38 | 39 | You can also pass in options as the second argument which needs to be an object. 40 | 41 | ```javascript 42 | const rippleEffect = new SoftRipple(el, { options }); 43 | ``` 44 | 45 | ## Options 46 | 47 | To customize the ripple effect you can use the following properties, and then pass them as an object as the second argument of the constructor. 48 | 49 | #### rippleColor 50 | 51 | A String representing the color of the ripple. Defaults to the `background-color` of the element. 52 | 53 | #### transitionDuration 54 | 55 | Duration of the transition in seconds. Default is `0.8s`. 56 | minimum is `0.4` seconds and max is `2` seconds. 57 | 58 | #### rippleWidth 59 | 60 | Width of the ripple. Defaults to `4`. 61 | minimum is `2` and max is `8`. 62 | 63 | #### rippleMaxSize 64 | 65 | Maximum size of the ripple. Defaults to `100`. 66 | minimum is `50` and max is `200`. 67 | 68 | #### randomSize 69 | 70 | Boolean property to set random size of the ripples. Default value is `false`. 71 | 72 | #### randomColor 73 | 74 | Boolean property to set random color of the ripples. Default value is `false`. 75 | 76 | #### overrideDefaults 77 | 78 | Boolean property to override the defaults. Default value is `false`. 79 | -------------------------------------------------------------------------------- /dist/softripple.js: -------------------------------------------------------------------------------- 1 | window["SoftRipple"] = 2 | /******/ (function(modules) { // webpackBootstrap 3 | /******/ // The module cache 4 | /******/ var installedModules = {}; 5 | /******/ 6 | /******/ // The require function 7 | /******/ function __webpack_require__(moduleId) { 8 | /******/ 9 | /******/ // Check if module is in cache 10 | /******/ if(installedModules[moduleId]) { 11 | /******/ return installedModules[moduleId].exports; 12 | /******/ } 13 | /******/ // Create a new module (and put it into the cache) 14 | /******/ var module = installedModules[moduleId] = { 15 | /******/ i: moduleId, 16 | /******/ l: false, 17 | /******/ exports: {} 18 | /******/ }; 19 | /******/ 20 | /******/ // Execute the module function 21 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 22 | /******/ 23 | /******/ // Flag the module as loaded 24 | /******/ module.l = true; 25 | /******/ 26 | /******/ // Return the exports of the module 27 | /******/ return module.exports; 28 | /******/ } 29 | /******/ 30 | /******/ 31 | /******/ // expose the modules object (__webpack_modules__) 32 | /******/ __webpack_require__.m = modules; 33 | /******/ 34 | /******/ // expose the module cache 35 | /******/ __webpack_require__.c = installedModules; 36 | /******/ 37 | /******/ // define getter function for harmony exports 38 | /******/ __webpack_require__.d = function(exports, name, getter) { 39 | /******/ if(!__webpack_require__.o(exports, name)) { 40 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 41 | /******/ } 42 | /******/ }; 43 | /******/ 44 | /******/ // define __esModule on exports 45 | /******/ __webpack_require__.r = function(exports) { 46 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 47 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 48 | /******/ } 49 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 50 | /******/ }; 51 | /******/ 52 | /******/ // create a fake namespace object 53 | /******/ // mode & 1: value is a module id, require it 54 | /******/ // mode & 2: merge all properties of value into the ns 55 | /******/ // mode & 4: return value when already ns object 56 | /******/ // mode & 8|1: behave like require 57 | /******/ __webpack_require__.t = function(value, mode) { 58 | /******/ if(mode & 1) value = __webpack_require__(value); 59 | /******/ if(mode & 8) return value; 60 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 61 | /******/ var ns = Object.create(null); 62 | /******/ __webpack_require__.r(ns); 63 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 64 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 65 | /******/ return ns; 66 | /******/ }; 67 | /******/ 68 | /******/ // getDefaultExport function for compatibility with non-harmony modules 69 | /******/ __webpack_require__.n = function(module) { 70 | /******/ var getter = module && module.__esModule ? 71 | /******/ function getDefault() { return module['default']; } : 72 | /******/ function getModuleExports() { return module; }; 73 | /******/ __webpack_require__.d(getter, 'a', getter); 74 | /******/ return getter; 75 | /******/ }; 76 | /******/ 77 | /******/ // Object.prototype.hasOwnProperty.call 78 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 79 | /******/ 80 | /******/ // __webpack_public_path__ 81 | /******/ __webpack_require__.p = ""; 82 | /******/ 83 | /******/ 84 | /******/ // Load entry module and return exports 85 | /******/ return __webpack_require__(__webpack_require__.s = "./src/main.js"); 86 | /******/ }) 87 | /************************************************************************/ 88 | /******/ ({ 89 | 90 | /***/ "./src/Ripple.js": 91 | /*!***********************!*\ 92 | !*** ./src/Ripple.js ***! 93 | \***********************/ 94 | /*! no static exports found */ 95 | /***/ (function(module, exports, __webpack_require__) { 96 | 97 | "use strict"; 98 | 99 | 100 | Object.defineProperty(exports, "__esModule", { 101 | value: true 102 | }); 103 | 104 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 105 | 106 | var _utils = __webpack_require__(/*! ./utils.js */ "./src/utils.js"); 107 | 108 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 109 | 110 | var Ripple = function () { 111 | function Ripple(el) { 112 | var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 113 | 114 | _classCallCheck(this, Ripple); 115 | 116 | this.el = el; 117 | this.props = props; 118 | 119 | this.init(); 120 | } 121 | 122 | _createClass(Ripple, [{ 123 | key: "init", 124 | value: function init() { 125 | this.elBox = this.el.getBoundingClientRect(); 126 | this.ripples = []; 127 | 128 | this.rippleSizeMin = 50; 129 | this.rippleSizeMax = 200; 130 | this.rippleSizeDefault = 100; 131 | 132 | this.transitionDurationMin = 0.4; 133 | this.transitionDurationMax = 2; 134 | this.transitionDurationDefault = 0.8; 135 | 136 | this.rippleWidthMin = 2; 137 | this.rippleWidthMax = 10; 138 | this.rippleWidthDefault = 4; 139 | 140 | this.overrideDefaults = this.props.overrideDefaults || false; 141 | 142 | if (this.overrideDefaults) this.overrideRipplePropsDefaults();else { 143 | this.rippleProps = { 144 | rippleColor: this.props.rippleColor || window.getComputedStyle(this.el).getPropertyValue("background-color"), 145 | transitionDuration: (0, _utils.clampValue)(this.props.transitionDuration, this.transitionDurationMin, this.transitionDurationMax) || this.transitionDurationDefault, 146 | rippleWidth: (0, _utils.clampValue)(this.props.rippleWidth, this.rippleWidthMin, this.rippleWidthMax) || this.rippleWidthDefault, 147 | rippleMaxSize: (0, _utils.clampValue)(this.props.rippleMaxSize, this.rippleSizeMin, this.rippleSizeMax) || this.rippleSizeDefault, 148 | randomSize: this.props.randomSize || false, 149 | randomColor: this.props.randomColor || false 150 | }; 151 | } 152 | 153 | this.el.style.position = "relative"; 154 | this.el.style.overflow = "hidden"; 155 | 156 | this.addRippleStyles(); 157 | this.addListeners(); 158 | } 159 | }, { 160 | key: "addRipple", 161 | value: function addRipple(e) { 162 | var x = e.x - this.elBox.left; 163 | var y = e.y - this.elBox.top; 164 | var rippleSize = this.rippleProps.randomSize != true ? this.rippleProps.rippleMaxSize : (0, _utils.getRandomIntFromRange)(this.rippleSizeMin, this.rippleProps.rippleMaxSize); 165 | var rippleEl = document.createElement("div"); 166 | rippleEl.id = "ripple"; 167 | rippleEl.style.left = x + "px"; 168 | rippleEl.style.top = y + "px"; 169 | rippleEl.style.width = rippleSize + "px"; 170 | rippleEl.style.height = rippleSize + "px"; 171 | rippleEl.style.pointerEvents = "none"; 172 | 173 | rippleEl.innerHTML = this.returnCompleteSVG(); 174 | 175 | this.el.appendChild(rippleEl); 176 | this.ripples.push(rippleEl); 177 | this.removeRipple(rippleEl, this.rippleProps.transitionDuration * 1000); 178 | } 179 | }, { 180 | key: "returnCompleteSVG", 181 | value: function returnCompleteSVG() { 182 | var rippleWidth = this.rippleProps.randomSize != true ? this.rippleProps.rippleWidth : (0, _utils.getRandomIntFromRange)(this.rippleWidthMin, this.rippleWidthMax); 183 | var rippleColor = this.rippleProps.randomColor != true ? this.rippleProps.rippleColor : (0, _utils.getRandomHex)(); 184 | 185 | return " "; 186 | } 187 | }, { 188 | key: "removeRipple", 189 | value: function removeRipple(ripple, delay) { 190 | var _this = this; 191 | 192 | var t = setTimeout(function () { 193 | _this.el.removeChild(ripple); 194 | _this.ripples.splice(_this.ripples.indexOf(ripple), 1); 195 | clearTimeout(t); 196 | }, delay); 197 | } 198 | }, { 199 | key: "addRippleStyles", 200 | value: function addRippleStyles() { 201 | this.style = document.createElement("style"); 202 | document.head.appendChild(this.style); 203 | this.style.sheet.insertRule("\n\t\t\t#ripple { position: absolute; transform: translate(-50%, -50%); width: 100%; height: 100%; border-radius: 50%; overflow: hidden; animation: scale-up " + this.rippleProps.transitionDuration + "s ease forwards;}\n\t\t"); 204 | this.style.sheet.insertRule("\n\t\t\t#ripple svg { width: 100%; height: 100%;}\n\t\t"); 205 | 206 | this.style.sheet.insertRule("\n\t\t\t@keyframes scale-up {\n\t\t\t\tfrom {\n\t\t\t\t\topacity: 1;\n\t\t\t\t\ttransform: translate(-50%, -50%) scale(0);\n\t\t\t\t}\n\t\t\t\tto {\n\t\t\t\t\topacity: 0;\n\t\t\t\t\ttransform: translate(-50%, -50%) scale(1);\n\t\t\t\t}\n\t\t\t}\t\t\n\t\t\t"); 207 | } 208 | }, { 209 | key: "overrideRipplePropsDefaults", 210 | value: function overrideRipplePropsDefaults() { 211 | this.rippleProps = { 212 | rippleColor: this.props.rippleColor || window.getComputedStyle(this.el).getPropertyValue("background-color"), 213 | transitionDuration: this.props.transitionDuration || this.transitionDurationDefault, 214 | rippleWidth: this.props.rippleWidth || this.rippleWidthDefault, 215 | rippleMaxSize: this.props.rippleMaxSize || this.rippleSizeDefault, 216 | randomSize: this.props.randomSize || false, 217 | randomColor: this.props.randomColor || false 218 | }; 219 | } 220 | }, { 221 | key: "addListeners", 222 | value: function addListeners() { 223 | var _this2 = this; 224 | 225 | this.el.addEventListener("pointerdown", this.addRipple.bind(this)); 226 | window.addEventListener("resize", function () { 227 | _this2.elBox = _this2.el.getBoundingClientRect(); 228 | }); 229 | } 230 | }]); 231 | 232 | return Ripple; 233 | }(); 234 | 235 | exports.default = Ripple; 236 | 237 | /***/ }), 238 | 239 | /***/ "./src/main.js": 240 | /*!*********************!*\ 241 | !*** ./src/main.js ***! 242 | \*********************/ 243 | /*! no static exports found */ 244 | /***/ (function(module, exports, __webpack_require__) { 245 | 246 | "use strict"; 247 | 248 | 249 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 250 | 251 | var _Ripple = __webpack_require__(/*! ./Ripple.js */ "./src/Ripple.js"); 252 | 253 | var _Ripple2 = _interopRequireDefault(_Ripple); 254 | 255 | var _utils = __webpack_require__(/*! ./utils.js */ "./src/utils.js"); 256 | 257 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 258 | 259 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 260 | 261 | var SoftRipple = function () { 262 | function SoftRipple(els) { 263 | var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 264 | 265 | _classCallCheck(this, SoftRipple); 266 | 267 | this.els = (0, _utils.convertToArray)(els); 268 | this.props = props; 269 | 270 | this.init(); 271 | } 272 | 273 | _createClass(SoftRipple, [{ 274 | key: "init", 275 | value: function init() { 276 | for (var i = 0; i < this.els.length; i++) { 277 | var el = this.els[i]; 278 | new _Ripple2.default(el, this.props); 279 | } 280 | } 281 | }]); 282 | 283 | return SoftRipple; 284 | }(); 285 | 286 | module.exports = function (els, props) { 287 | return new SoftRipple(els, props); 288 | }; 289 | 290 | /***/ }), 291 | 292 | /***/ "./src/utils.js": 293 | /*!**********************!*\ 294 | !*** ./src/utils.js ***! 295 | \**********************/ 296 | /*! no static exports found */ 297 | /***/ (function(module, exports, __webpack_require__) { 298 | 299 | "use strict"; 300 | 301 | 302 | Object.defineProperty(exports, "__esModule", { 303 | value: true 304 | }); 305 | exports.convertToArray = convertToArray; 306 | exports.lightenColor = lightenColor; 307 | 308 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } 309 | 310 | var clampValue = exports.clampValue = function clampValue(val, min, max) { 311 | return val > max ? max : val < min ? min : val; 312 | }; 313 | 314 | var getRandomIntFromRange = exports.getRandomIntFromRange = function getRandomIntFromRange(min, max) { 315 | return Math.floor(Math.random() * (max - min + 1)) + min; 316 | }; 317 | 318 | var getRandomHex = exports.getRandomHex = function getRandomHex() { 319 | return "#" + Math.floor(Math.random() * 16777215).toString(16); 320 | }; 321 | 322 | function convertToArray(input) { 323 | if (NodeList.prototype.isPrototypeOf(input)) return [].concat(_toConsumableArray(input));else if (Array.isArray(input)) return input;else return [input]; 324 | } 325 | 326 | function lightenColor(color, percent) { 327 | var num = parseInt(color.replace("#", ""), 16), 328 | amt = Math.round(2.55 * percent), 329 | R = (num >> 16) + amt, 330 | B = (num >> 8 & 0x00ff) + amt, 331 | G = (num & 0x0000ff) + amt; 332 | return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 + (B < 255 ? B < 1 ? 0 : B : 255) * 0x100 + (G < 255 ? G < 1 ? 0 : G : 255)).toString(16).slice(1); 333 | } 334 | 335 | /***/ }) 336 | 337 | /******/ }); 338 | //# sourceMappingURL=softripple.js.map -------------------------------------------------------------------------------- /dist/softripple.min.js: -------------------------------------------------------------------------------- 1 | window.SoftRipple=function(t){function e(r){if(i[r])return i[r].exports;var o=i[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var i={};return e.m=t,e.c=i,e.d=function(t,i,r){e.o(t,i)||Object.defineProperty(t,i,{enumerable:!0,get:r})},e.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},e.t=function(t,i){if(1&i&&(t=e(t)),8&i)return t;if(4&i&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(e.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&i&&"string"!=typeof t)for(var o in t)e.d(r,o,function(e){return t[e]}.bind(null,o));return r},e.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(i,"a",i),i},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=1)}([function(t,e){"use strict";var i=Math.floor;Object.defineProperty(e,"__esModule",{value:!0}),e.convertToArray=function(t){return NodeList.prototype.isPrototypeOf(t)?[].concat(function(t){if(Array.isArray(t)){for(var e=0,i=Array(t.length);e>16)+r,n=(255&i>>8)+r,l=(255&i)+r;return"#"+(16777216+65536*(255>o?1>o?0:o:255)+256*(255>n?1>n?0:n:255)+(255>l?1>l?0:l:255)).toString(16).slice(1)};e.clampValue=function(t,e,i){return t>i?i:t '}},{key:"removeRipple",value:function(t,e){var i=this,r=setTimeout((function(){i.el.removeChild(t),i.ripples.splice(i.ripples.indexOf(t),1),clearTimeout(r)}),e)}},{key:"addRippleStyles",value:function(){this.style=document.createElement("style"),document.head.appendChild(this.style),this.style.sheet.insertRule("\n\t\t\t#ripple { position: absolute; transform: translate(-50%, -50%); width: 100%; height: 100%; border-radius: 50%; overflow: hidden; animation: scale-up "+this.rippleProps.transitionDuration+"s ease forwards;}\n\t\t"),this.style.sheet.insertRule("\n\t\t\t#ripple svg { width: 100%; height: 100%;}\n\t\t"),this.style.sheet.insertRule("\n\t\t\t@keyframes scale-up {\n\t\t\t\tfrom {\n\t\t\t\t\topacity: 1;\n\t\t\t\t\ttransform: translate(-50%, -50%) scale(0);\n\t\t\t\t}\n\t\t\t\tto {\n\t\t\t\t\topacity: 0;\n\t\t\t\t\ttransform: translate(-50%, -50%) scale(1);\n\t\t\t\t}\n\t\t\t}\t\t\n\t\t\t")}},{key:"overrideRipplePropsDefaults",value:function(){this.rippleProps={rippleColor:this.props.rippleColor||window.getComputedStyle(this.el).getPropertyValue("background-color"),transitionDuration:this.props.transitionDuration||this.transitionDurationDefault,rippleWidth:this.props.rippleWidth||this.rippleWidthDefault,rippleMaxSize:this.props.rippleMaxSize||this.rippleSizeDefault,randomSize:this.props.randomSize||!1,randomColor:this.props.randomColor||!1}}},{key:"addListeners",value:function(){var t=this;this.el.addEventListener("pointerdown",this.addRipple.bind(this)),window.addEventListener("resize",(function(){t.elBox=t.el.getBoundingClientRect()}))}}]),t}();e.default=l}]); 2 | //# sourceMappingURL=softripple.min.js.map -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "softripple-js", 3 | "version": "1.2.1", 4 | "description": "A small library to for creating soft ripples", 5 | "main": "src/main.js", 6 | "scripts": { 7 | "build:dev": "webpack && NODE_ENV=development webpack", 8 | "build:prod": "webpack && NODE_ENV=production webpack" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/devloop01/soft-ripple-js.git" 13 | }, 14 | "keywords": [ 15 | "ripples", 16 | "library", 17 | "interaction" 18 | ], 19 | "author": "Sikriti Dakua", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/devloop01/soft-ripple-js/issues" 23 | }, 24 | "homepage": "https://github.com/devloop01/soft-ripple-js#readme", 25 | "devDependencies": { 26 | "babel-core": "^6.26.3", 27 | "babel-loader": "^7.1.5", 28 | "babel-minify-webpack-plugin": "^0.3.1", 29 | "babel-preset-es2015": "^6.24.1", 30 | "dotenv": "^8.2.0", 31 | "webpack": "^4.43.0", 32 | "webpack-cli": "^3.3.11", 33 | "webpack-dev-server": "^3.11.0" 34 | }, 35 | "dependencies": {} 36 | } 37 | -------------------------------------------------------------------------------- /src/Ripple.js: -------------------------------------------------------------------------------- 1 | import { clampValue, lightenColor, getRandomIntFromRange, getRandomHex } from "./utils.js"; 2 | 3 | class Ripple { 4 | constructor(el, props = {}) { 5 | this.el = el; 6 | this.props = props; 7 | 8 | this.init(); 9 | } 10 | 11 | init() { 12 | this.elBox = this.el.getBoundingClientRect(); 13 | this.ripples = []; 14 | 15 | this.rippleSizeMin = 50; 16 | this.rippleSizeMax = 200; 17 | this.rippleSizeDefault = 100; 18 | 19 | this.transitionDurationMin = 0.4; 20 | this.transitionDurationMax = 2; 21 | this.transitionDurationDefault = 0.8; 22 | 23 | this.rippleWidthMin = 2; 24 | this.rippleWidthMax = 10; 25 | this.rippleWidthDefault = 4; 26 | 27 | this.overrideDefaults = this.props.overrideDefaults || false; 28 | 29 | if (this.overrideDefaults) this.overrideRipplePropsDefaults(); 30 | else { 31 | this.rippleProps = { 32 | rippleColor: 33 | this.props.rippleColor || 34 | window.getComputedStyle(this.el).getPropertyValue("background-color"), 35 | transitionDuration: 36 | clampValue( 37 | this.props.transitionDuration, 38 | this.transitionDurationMin, 39 | this.transitionDurationMax 40 | ) || this.transitionDurationDefault, 41 | rippleWidth: 42 | clampValue(this.props.rippleWidth, this.rippleWidthMin, this.rippleWidthMax) || 43 | this.rippleWidthDefault, 44 | rippleMaxSize: 45 | clampValue(this.props.rippleMaxSize, this.rippleSizeMin, this.rippleSizeMax) || 46 | this.rippleSizeDefault, 47 | randomSize: this.props.randomSize || false, 48 | randomColor: this.props.randomColor || false, 49 | }; 50 | } 51 | 52 | this.el.style.position = "relative"; 53 | this.el.style.overflow = "hidden"; 54 | 55 | this.addRippleStyles(); 56 | this.addListeners(); 57 | } 58 | 59 | addRipple(e) { 60 | const x = e.x - this.elBox.left; 61 | const y = e.y - this.elBox.top; 62 | const rippleSize = 63 | this.rippleProps.randomSize != true 64 | ? this.rippleProps.rippleMaxSize 65 | : getRandomIntFromRange(this.rippleSizeMin, this.rippleProps.rippleMaxSize); 66 | const rippleEl = document.createElement("div"); 67 | rippleEl.id = "ripple"; 68 | rippleEl.style.left = `${x}px`; 69 | rippleEl.style.top = `${y}px`; 70 | rippleEl.style.width = `${rippleSize}px`; 71 | rippleEl.style.height = `${rippleSize}px`; 72 | rippleEl.style.pointerEvents = "none"; 73 | 74 | rippleEl.innerHTML = this.returnCompleteSVG(); 75 | 76 | this.el.appendChild(rippleEl); 77 | this.ripples.push(rippleEl); 78 | this.removeRipple(rippleEl, this.rippleProps.transitionDuration * 1000); 79 | } 80 | 81 | returnCompleteSVG() { 82 | const rippleWidth = 83 | this.rippleProps.randomSize != true 84 | ? this.rippleProps.rippleWidth 85 | : getRandomIntFromRange(this.rippleWidthMin, this.rippleWidthMax); 86 | const rippleColor = 87 | this.rippleProps.randomColor != true ? this.rippleProps.rippleColor : getRandomHex(); 88 | 89 | return ` `; 102 | } 103 | 104 | removeRipple(ripple, delay) { 105 | let t = setTimeout(() => { 106 | this.el.removeChild(ripple); 107 | this.ripples.splice(this.ripples.indexOf(ripple), 1); 108 | clearTimeout(t); 109 | }, delay); 110 | } 111 | 112 | addRippleStyles() { 113 | this.style = document.createElement("style"); 114 | document.head.appendChild(this.style); 115 | this.style.sheet.insertRule(` 116 | #ripple { position: absolute; transform: translate(-50%, -50%); width: 100%; height: 100%; border-radius: 50%; overflow: hidden; animation: scale-up ${this.rippleProps.transitionDuration}s ease forwards;} 117 | `); 118 | this.style.sheet.insertRule(` 119 | #ripple svg { width: 100%; height: 100%;} 120 | `); 121 | 122 | this.style.sheet.insertRule( 123 | ` 124 | @keyframes scale-up { 125 | from { 126 | opacity: 1; 127 | transform: translate(-50%, -50%) scale(0); 128 | } 129 | to { 130 | opacity: 0; 131 | transform: translate(-50%, -50%) scale(1); 132 | } 133 | } 134 | ` 135 | ); 136 | } 137 | 138 | overrideRipplePropsDefaults() { 139 | this.rippleProps = { 140 | rippleColor: 141 | this.props.rippleColor || 142 | window.getComputedStyle(this.el).getPropertyValue("background-color"), 143 | transitionDuration: this.props.transitionDuration || this.transitionDurationDefault, 144 | rippleWidth: this.props.rippleWidth || this.rippleWidthDefault, 145 | rippleMaxSize: this.props.rippleMaxSize || this.rippleSizeDefault, 146 | randomSize: this.props.randomSize || false, 147 | randomColor: this.props.randomColor || false, 148 | }; 149 | } 150 | 151 | addListeners() { 152 | this.el.addEventListener("pointerdown", this.addRipple.bind(this)); 153 | window.addEventListener("resize", () => { 154 | this.elBox = this.el.getBoundingClientRect(); 155 | }); 156 | } 157 | } 158 | 159 | export default Ripple; 160 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import Ripple from "./Ripple.js"; 4 | import { convertToArray } from "./utils.js"; 5 | 6 | class SoftRipple { 7 | constructor(els, props = {}) { 8 | this.els = convertToArray(els); 9 | this.props = props; 10 | 11 | this.init(); 12 | } 13 | 14 | init() { 15 | for (let i = 0; i < this.els.length; i++) { 16 | const el = this.els[i]; 17 | new Ripple(el, this.props); 18 | } 19 | } 20 | } 21 | 22 | module.exports = (els, props) => { 23 | return new SoftRipple(els, props); 24 | }; 25 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | export const clampValue = (val, min, max) => (val > max ? max : val < min ? min : val); 2 | 3 | export const getRandomIntFromRange = (min, max) => 4 | Math.floor(Math.random() * (max - min + 1)) + min; 5 | 6 | export const getRandomHex = () => `#${Math.floor(Math.random() * 16777215).toString(16)}`; 7 | 8 | export function convertToArray(input) { 9 | if (NodeList.prototype.isPrototypeOf(input)) return [...input]; 10 | else if (Array.isArray(input)) return input; 11 | else return [input]; 12 | } 13 | 14 | export function lightenColor(color, percent) { 15 | var num = parseInt(color.replace("#", ""), 16), 16 | amt = Math.round(2.55 * percent), 17 | R = (num >> 16) + amt, 18 | B = ((num >> 8) & 0x00ff) + amt, 19 | G = (num & 0x0000ff) + amt; 20 | return ( 21 | "#" + 22 | ( 23 | 0x1000000 + 24 | (R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 + 25 | (B < 255 ? (B < 1 ? 0 : B) : 255) * 0x100 + 26 | (G < 255 ? (G < 1 ? 0 : G) : 255) 27 | ) 28 | .toString(16) 29 | .slice(1) 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const config = require("./package.json"); 3 | 4 | require("dotenv").config(); 5 | 6 | const MinifyPlugin = require("babel-minify-webpack-plugin"); 7 | 8 | const PROD = process.env.NODE_ENV === "production"; 9 | 10 | let plugins = []; 11 | 12 | PROD ? [plugins.push(new MinifyPlugin())] : ""; 13 | 14 | module.exports = { 15 | entry: path.resolve(__dirname, config.main), 16 | devtool: "source-map", 17 | output: { 18 | library: process.env.NAME, 19 | libraryTarget: process.env.TARGET, 20 | path: __dirname, 21 | filename: PROD ? "dist/softripple.min.js" : "dist/softripple.js", 22 | }, 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.(js)$/, 27 | exclude: /node_modules/, 28 | use: ["babel-loader"], 29 | }, 30 | ], 31 | }, 32 | plugins: plugins, 33 | mode: process.env.NODE_ENV, 34 | }; 35 | --------------------------------------------------------------------------------