├── .babelrc ├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── demo └── index.html ├── dist └── vue-waves.js ├── index.js ├── package-lock.json ├── package.json ├── waves.css ├── waves.js └── webpack.config.babel.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "undef": true, 3 | "unused": true, 4 | "esversion": 6, 5 | "asi": true, 6 | "browser": true, 7 | "node": true, 8 | "devel": true, 9 | "predef": ["Vue", "VueWaves"] 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License (MIT) 2 | 3 | Copyright (c) 2016 TeddyZhu 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 | # vue-waves 2 | 3 | A Vue.js version of waves based on https://github.com/fians/Waves. 4 | 5 | ## How to Use 6 | 7 | ### SetUp 8 | 9 | ```bash 10 | npm i -S vue-waves 11 | ``` 12 | 13 | then in the js file 14 | 15 | ```javascript 16 | import Vue from 'vue'; 17 | import VueWaves from 'vue-waves'; 18 | 19 | Vue.use(VueWaves); 20 | ``` 21 | 22 | ### Config 23 | 24 | ```javascript 25 | Vue.use(VueWaves, { 26 | name: 'waves' // Vue指令名称 27 | duration: 500, // 涟漪效果持续时间 28 | delay: 200 // 延时显示涟漪效果 29 | }) 30 | ``` 31 | 32 | ### Usage 33 | 34 | ```html 35 | 36 | 37 | ``` 38 | 39 | Vue directive: 40 | ``` 41 | v-waves.button 按钮 42 | v-waves.circle 圆形 43 | v-waves.block 块 44 | v-waves.float 阴影效果 45 | v-waves.light 亮色涟漪 46 | v-waves.classic ?? 47 | ``` 48 | 49 | Demo: 50 | 51 | git clone https://github.com/Teddy-Zhu/vue-waves.git 52 | open vue-waves/demo/index.html 53 | 54 | # Contributes 55 | 56 | ``` 57 | $ npm i 58 | $ npm run build 59 | ``` 60 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vue-waves 7 | 8 | 9 | 10 | 11 | 12 |
13 |

14 | {{ message }} 15 |

16 |
17 | Vue Waves 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 | V 27 |
28 |
29 | V 30 |
31 |
32 | 33 |
34 |
35 | 60 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /dist/vue-waves.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.VueWaves=e():t.VueWaves=e()}(window,function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.r=function(t){Object.defineProperty(t,"__esModule",{value:!0})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=7)}([function(t,e){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(t){"object"==typeof window&&(n=window)}t.exports=n},function(t,e,n){"use strict";(function(n){var r,o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};!function(n,o){void 0===(r=function(){return o.apply(n)}.apply(e,[]))||(t.exports=r)}("object"===(void 0===n?"undefined":o(n))?n:void 0,function(){var t=t||{},e=document.querySelectorAll.bind(document),n=Object.prototype.toString,r="ontouchstart"in window;function a(t){var e=void 0===t?"undefined":o(t);return"function"===e||"object"===e&&!!t}function i(t){var r,o=n.call(t);return"[object String]"===o?e(t):a(t)&&/^\[object (Array|HTMLCollection|NodeList|Object)\]$/.test(o)&&t.hasOwnProperty("length")?t:a(r=t)&&r.nodeType>0?[t]:[]}function s(t){var e,n,r={top:0,left:0},a=t&&t.ownerDocument;return e=a.documentElement,"undefined"!==o(t.getBoundingClientRect)&&(r=t.getBoundingClientRect()),n=function(t){return null!==(e=t)&&e===e.window?t:9===t.nodeType&&t.defaultView;var e}(a),{top:r.top+n.pageYOffset-e.clientTop,left:r.left+n.pageXOffset-e.clientLeft}}function u(t){var e="";for(var n in t)t.hasOwnProperty(n)&&(e+=n+":"+t[n]+";");return e}var l={duration:750,delay:200,show:function(t,e,n){if(2===t.button)return!1;e=e||this;var r=document.createElement("div");r.className="waves-ripple waves-rippling",e.appendChild(r);var o=s(e),a=0,i=0;"touches"in t&&t.touches.length?(a=t.touches[0].pageY-o.top,i=t.touches[0].pageX-o.left):(a=t.pageY-o.top,i=t.pageX-o.left),i=i>=0?i:0,a=a>=0?a:0;var c="scale("+e.clientWidth/100*3+")",f="translate(0,0)";n&&(f="translate("+n.x+"px, "+n.y+"px)"),r.setAttribute("data-hold",Date.now()),r.setAttribute("data-x",i),r.setAttribute("data-y",a),r.setAttribute("data-scale",c),r.setAttribute("data-translate",f);var d={top:a+"px",left:i+"px"};r.classList.add("waves-notransition"),r.setAttribute("style",u(d)),r.classList.remove("waves-notransition"),d["-webkit-transform"]=c+" "+f,d["-moz-transform"]=c+" "+f,d["-ms-transform"]=c+" "+f,d["-o-transform"]=c+" "+f,d.transform=c+" "+f,d.opacity="1";var p="mousemove"===t.type?2500:l.duration;d["-webkit-transition-duration"]=p+"ms",d["-moz-transition-duration"]=p+"ms",d["-o-transition-duration"]=p+"ms",d["transition-duration"]=p+"ms",r.setAttribute("style",u(d))},hide:function(t,e){for(var n=(e=e||this).getElementsByClassName("waves-rippling"),o=0,a=n.length;o=0&&null!==e.wait){setTimeout(d({type:"mouseup",button:1},r),e.wait)}},t.calm=function(t){for(var e={type:"mouseup",button:1},n=0,r=(t=i(t)).length;n=0&&c.splice(e,1)}function h(t){var e=document.createElement("style");return t.attrs.type="text/css",b(e,t.attrs),v(t,e),e}function b(t,e){Object.keys(e).forEach(function(n){t.setAttribute(n,e[n])})}function g(t,e){var n,r,o,a;if(e.transform&&t.css){if(!(a=e.transform(t.css)))return function(){};t.css=a}if(e.singleton){var i=l++;n=u||(u=h(e)),r=x.bind(null,n,i,!1),o=x.bind(null,n,i,!0)}else t.sourceMap&&"function"==typeof URL&&"function"==typeof URL.createObjectURL&&"function"==typeof URL.revokeObjectURL&&"function"==typeof Blob&&"function"==typeof btoa?(n=function(t){var e=document.createElement("link");return t.attrs.type="text/css",t.attrs.rel="stylesheet",b(e,t.attrs),v(t,e),e}(e),r=function(t,e,n){var r=n.css,o=n.sourceMap,a=void 0===e.convertToAbsoluteUrls&&o;(e.convertToAbsoluteUrls||a)&&(r=f(r));o&&(r+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(o))))+" */");var i=new Blob([r],{type:"text/css"}),s=t.href;t.href=URL.createObjectURL(i),s&&URL.revokeObjectURL(s)}.bind(null,n,e),o=function(){m(n),n.href&&URL.revokeObjectURL(n.href)}):(n=h(e),r=function(t,e){var n=e.css,r=e.media;r&&t.setAttribute("media",r);if(t.styleSheet)t.styleSheet.cssText=n;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(n))}}.bind(null,n),o=function(){m(n)});return r(t),function(e){if(e){if(e.css===t.css&&e.media===t.media&&e.sourceMap===t.sourceMap)return;r(t=e)}else o()}}t.exports=function(t,e){if("undefined"!=typeof DEBUG&&DEBUG&&"object"!=typeof document)throw new Error("The style-loader cannot be used in a non-browser environment");(e=e||{}).attrs="object"==typeof e.attrs?e.attrs:{},e.singleton||"boolean"==typeof e.singleton||(e.singleton=i()),e.insertInto||(e.insertInto="head"),e.insertAt||(e.insertAt="bottom");var n=p(t,e);return d(n,e),function(t){for(var r=[],o=0;o1&&void 0!==arguments[1]?arguments[1]:{name:"waves"};t.directive(e.name,{inserted:function(t,e){var n=["button","circle","block","float","light","classic"].filter(function(t){return e.modifiers[t]}).map(function(t){return"waves-"+t});a.default.attach(t,n)}}),t.mixin({created:function(){a.default.init(e)}})}}}])}); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import './waves.css' 2 | import Waves from './waves.js' 3 | 4 | export default { 5 | install(Vue, options = { name: 'waves' }) { 6 | Vue.directive(options.name, { 7 | inserted(el, binding) { 8 | let classes = ['button', 'circle', 'block', 'float', 'light', 'classic'] 9 | .filter(cls => binding.modifiers[cls]) 10 | .map(cls => `waves-${cls}`) 11 | Waves.attach(el, classes) 12 | } 13 | }) 14 | Vue.mixin({ 15 | created: function() { 16 | Waves.init(options); 17 | } 18 | }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-waves", 3 | "homepage": "https://github.com/Teddy-Zhu/vue-waves", 4 | "version": "0.1.1", 5 | "description": "A Vue.js version of waves", 6 | "author": "teddyzhu", 7 | "scripts": { 8 | "build": "./node_modules/webpack/bin/webpack.js -p" 9 | }, 10 | "dependencies": {}, 11 | "readme": "README.md", 12 | "main": "index.js", 13 | "repository": { 14 | "type": "git", 15 | "url": "git@github.com:Teddy-Zhu/vue-waves.git" 16 | }, 17 | "license": "MIT", 18 | "devDependencies": { 19 | "babel-core": "^6.17.0", 20 | "babel-loader": "^7.1.4", 21 | "babel-preset-es2015": "^6.16.0", 22 | "css-loader": "^0.28.11", 23 | "style-loader": "^0.20.3", 24 | "vue": "^2.5.16", 25 | "webpack": "^4.4.1" 26 | }, 27 | "files": [ 28 | "dist", 29 | "waves.css", 30 | "waves.js" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /waves.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Waves v0.7.5 3 | * http://fian.my.id/Waves 4 | * 5 | * Copyright 2014-2016 Alfiana E. Sibuea and other contributors 6 | * Released under the MIT license 7 | * https://github.com/fians/Waves/blob/master/LICENSE 8 | */ 9 | .waves-effect { 10 | position: relative; 11 | cursor: pointer; 12 | display: inline-block; 13 | overflow: hidden; 14 | -webkit-user-select: none; 15 | -moz-user-select: none; 16 | -ms-user-select: none; 17 | user-select: none; 18 | -webkit-tap-highlight-color: transparent; 19 | } 20 | .waves-effect .waves-ripple { 21 | position: absolute; 22 | border-radius: 50%; 23 | width: 100px; 24 | height: 100px; 25 | margin-top: -50px; 26 | margin-left: -50px; 27 | opacity: 0; 28 | background: rgba(0, 0, 0, 0.2); 29 | background: -webkit-radial-gradient(rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.5) 60%, rgba(255, 255, 255, 0) 70%); 30 | background: -o-radial-gradient(rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.5) 60%, rgba(255, 255, 255, 0) 70%); 31 | background: -moz-radial-gradient(rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.5) 60%, rgba(255, 255, 255, 0) 70%); 32 | background: radial-gradient(rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.5) 60%, rgba(255, 255, 255, 0) 70%); 33 | -webkit-transition: all 0.5s ease-out; 34 | -moz-transition: all 0.5s ease-out; 35 | -o-transition: all 0.5s ease-out; 36 | transition: all 0.5s ease-out; 37 | -webkit-transition-property: -webkit-transform, opacity; 38 | -moz-transition-property: -moz-transform, opacity; 39 | -o-transition-property: -o-transform, opacity; 40 | transition-property: transform, opacity; 41 | -webkit-transform: scale(0) translate(0, 0); 42 | -moz-transform: scale(0) translate(0, 0); 43 | -ms-transform: scale(0) translate(0, 0); 44 | -o-transform: scale(0) translate(0, 0); 45 | transform: scale(0) translate(0, 0); 46 | pointer-events: none; 47 | } 48 | .waves-effect.waves-light .waves-ripple { 49 | background: rgba(255, 255, 255, 0.4); 50 | background: -webkit-radial-gradient(rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%); 51 | background: -o-radial-gradient(rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%); 52 | background: -moz-radial-gradient(rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%); 53 | background: radial-gradient(rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%); 54 | } 55 | .waves-effect.waves-classic .waves-ripple { 56 | background: rgba(0, 0, 0, 0.2); 57 | } 58 | .waves-effect.waves-classic.waves-light .waves-ripple { 59 | background: rgba(255, 255, 255, 0.4); 60 | } 61 | .waves-notransition { 62 | -webkit-transition: none !important; 63 | -moz-transition: none !important; 64 | -o-transition: none !important; 65 | transition: none !important; 66 | } 67 | .waves-button, 68 | .waves-circle { 69 | -webkit-transform: translateZ(0); 70 | -moz-transform: translateZ(0); 71 | -ms-transform: translateZ(0); 72 | -o-transform: translateZ(0); 73 | transform: translateZ(0); 74 | -webkit-mask-image: -webkit-radial-gradient(circle, #ffffff 100%, #000000 100%); 75 | } 76 | .waves-button, 77 | .waves-button:hover, 78 | .waves-button:visited, 79 | .waves-button-input { 80 | white-space: nowrap; 81 | vertical-align: middle; 82 | cursor: pointer; 83 | border: none; 84 | outline: none; 85 | color: inherit; 86 | background-color: rgba(0, 0, 0, 0); 87 | font-size: 1em; 88 | line-height: 1em; 89 | text-align: center; 90 | text-decoration: none; 91 | z-index: 1; 92 | } 93 | .waves-button { 94 | padding: 0.85em 1.1em; 95 | border-radius: 0.2em; 96 | } 97 | .waves-button-input { 98 | margin: 0; 99 | padding: 0.85em 1.1em; 100 | } 101 | .waves-input-wrapper { 102 | border-radius: 0.2em; 103 | vertical-align: bottom; 104 | } 105 | .waves-input-wrapper.waves-button { 106 | padding: 0; 107 | } 108 | .waves-input-wrapper .waves-button-input { 109 | position: relative; 110 | top: 0; 111 | left: 0; 112 | z-index: 1; 113 | } 114 | .waves-circle { 115 | text-align: center; 116 | width: 2.5em; 117 | height: 2.5em; 118 | line-height: 2.5em; 119 | border-radius: 50%; 120 | } 121 | .waves-float { 122 | -webkit-mask-image: none; 123 | -webkit-box-shadow: 0px 1px 1.5px 1px rgba(0, 0, 0, 0.12); 124 | box-shadow: 0px 1px 1.5px 1px rgba(0, 0, 0, 0.12); 125 | -webkit-transition: all 300ms; 126 | -moz-transition: all 300ms; 127 | -o-transition: all 300ms; 128 | transition: all 300ms; 129 | } 130 | .waves-float:active { 131 | -webkit-box-shadow: 0px 8px 20px 1px rgba(0, 0, 0, 0.3); 132 | box-shadow: 0px 8px 20px 1px rgba(0, 0, 0, 0.3); 133 | } 134 | .waves-block { 135 | display: block; 136 | } 137 | -------------------------------------------------------------------------------- /waves.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Waves v0.7.5 3 | * http://fian.my.id/Waves 4 | * 5 | * Copyright 2014-2016 Alfiana E. Sibuea and other contributors 6 | * Released under the MIT license 7 | * https://github.com/fians/Waves/blob/master/LICENSE 8 | */ 9 | 10 | ;(function(window, factory) { 11 | 'use strict'; 12 | 13 | // AMD. Register as an anonymous module. Wrap in function so we have access 14 | // to root via `this`. 15 | if (typeof define === 'function' && define.amd) { 16 | define([], function() { 17 | return factory.apply(window); 18 | }); 19 | } 20 | 21 | // Node. Does not work with strict CommonJS, but only CommonJS-like 22 | // environments that support module.exports, like Node. 23 | else if (typeof exports === 'object') { 24 | module.exports = factory.call(window); 25 | } 26 | 27 | // Browser globals. 28 | else { 29 | window.Waves = factory.call(window); 30 | } 31 | })(typeof global === 'object' ? global : this, function() { 32 | 'use strict'; 33 | 34 | var Waves = Waves || {}; 35 | var $$ = document.querySelectorAll.bind(document); 36 | var toString = Object.prototype.toString; 37 | var isTouchAvailable = 'ontouchstart' in window; 38 | 39 | 40 | // Find exact position of element 41 | function isWindow(obj) { 42 | return obj !== null && obj === obj.window; 43 | } 44 | 45 | function getWindow(elem) { 46 | return isWindow(elem) ? elem : elem.nodeType === 9 && elem.defaultView; 47 | } 48 | 49 | function isObject(value) { 50 | var type = typeof value; 51 | return type === 'function' || type === 'object' && !!value; 52 | } 53 | 54 | function isDOMNode(obj) { 55 | return isObject(obj) && obj.nodeType > 0; 56 | } 57 | 58 | function getWavesElements(nodes) { 59 | var stringRepr = toString.call(nodes); 60 | 61 | if (stringRepr === '[object String]') { 62 | return $$(nodes); 63 | } else if (isObject(nodes) && /^\[object (Array|HTMLCollection|NodeList|Object)\]$/.test(stringRepr) && nodes.hasOwnProperty('length')) { 64 | return nodes; 65 | } else if (isDOMNode(nodes)) { 66 | return [nodes]; 67 | } 68 | 69 | return []; 70 | } 71 | 72 | function offset(elem) { 73 | var docElem, win, 74 | box = { top: 0, left: 0 }, 75 | doc = elem && elem.ownerDocument; 76 | 77 | docElem = doc.documentElement; 78 | 79 | if (typeof elem.getBoundingClientRect !== typeof undefined) { 80 | box = elem.getBoundingClientRect(); 81 | } 82 | win = getWindow(doc); 83 | return { 84 | top: box.top + win.pageYOffset - docElem.clientTop, 85 | left: box.left + win.pageXOffset - docElem.clientLeft 86 | }; 87 | } 88 | 89 | function convertStyle(styleObj) { 90 | var style = ''; 91 | 92 | for (var prop in styleObj) { 93 | if (styleObj.hasOwnProperty(prop)) { 94 | style += (prop + ':' + styleObj[prop] + ';'); 95 | } 96 | } 97 | 98 | return style; 99 | } 100 | 101 | var Effect = { 102 | 103 | // Effect duration 104 | duration: 750, 105 | 106 | // Effect delay (check for scroll before showing effect) 107 | delay: 200, 108 | 109 | show: function(e, element, velocity) { 110 | 111 | // Disable right click 112 | if (e.button === 2) { 113 | return false; 114 | } 115 | 116 | element = element || this; 117 | 118 | // Create ripple 119 | var ripple = document.createElement('div'); 120 | ripple.className = 'waves-ripple waves-rippling'; 121 | element.appendChild(ripple); 122 | 123 | // Get click coordinate and element width 124 | var pos = offset(element); 125 | var relativeY = 0; 126 | var relativeX = 0; 127 | // Support for touch devices 128 | if('touches' in e && e.touches.length) { 129 | relativeY = (e.touches[0].pageY - pos.top); 130 | relativeX = (e.touches[0].pageX - pos.left); 131 | } 132 | //Normal case 133 | else { 134 | relativeY = (e.pageY - pos.top); 135 | relativeX = (e.pageX - pos.left); 136 | } 137 | // Support for synthetic events 138 | relativeX = relativeX >= 0 ? relativeX : 0; 139 | relativeY = relativeY >= 0 ? relativeY : 0; 140 | 141 | var scale = 'scale(' + ((element.clientWidth / 100) * 3) + ')'; 142 | var translate = 'translate(0,0)'; 143 | 144 | if (velocity) { 145 | translate = 'translate(' + (velocity.x) + 'px, ' + (velocity.y) + 'px)'; 146 | } 147 | 148 | // Attach data to element 149 | ripple.setAttribute('data-hold', Date.now()); 150 | ripple.setAttribute('data-x', relativeX); 151 | ripple.setAttribute('data-y', relativeY); 152 | ripple.setAttribute('data-scale', scale); 153 | ripple.setAttribute('data-translate', translate); 154 | 155 | // Set ripple position 156 | var rippleStyle = { 157 | top: relativeY + 'px', 158 | left: relativeX + 'px' 159 | }; 160 | 161 | ripple.classList.add('waves-notransition'); 162 | ripple.setAttribute('style', convertStyle(rippleStyle)); 163 | ripple.classList.remove('waves-notransition'); 164 | 165 | // Scale the ripple 166 | rippleStyle['-webkit-transform'] = scale + ' ' + translate; 167 | rippleStyle['-moz-transform'] = scale + ' ' + translate; 168 | rippleStyle['-ms-transform'] = scale + ' ' + translate; 169 | rippleStyle['-o-transform'] = scale + ' ' + translate; 170 | rippleStyle.transform = scale + ' ' + translate; 171 | rippleStyle.opacity = '1'; 172 | 173 | var duration = e.type === 'mousemove' ? 2500 : Effect.duration; 174 | rippleStyle['-webkit-transition-duration'] = duration + 'ms'; 175 | rippleStyle['-moz-transition-duration'] = duration + 'ms'; 176 | rippleStyle['-o-transition-duration'] = duration + 'ms'; 177 | rippleStyle['transition-duration'] = duration + 'ms'; 178 | 179 | ripple.setAttribute('style', convertStyle(rippleStyle)); 180 | }, 181 | 182 | hide: function(e, element) { 183 | element = element || this; 184 | 185 | var ripples = element.getElementsByClassName('waves-rippling'); 186 | 187 | for (var i = 0, len = ripples.length; i < len; i++) { 188 | removeRipple(e, element, ripples[i]); 189 | } 190 | 191 | if (isTouchAvailable) { 192 | element.removeEventListener('touchend', Effect.hide); 193 | element.removeEventListener('touchcancel', Effect.hide); 194 | } 195 | 196 | element.removeEventListener('mouseup', Effect.hide); 197 | element.removeEventListener('mouseleave', Effect.hide); 198 | } 199 | }; 200 | 201 | /** 202 | * Collection of wrapper for HTML element that only have single tag 203 | * like and 204 | */ 205 | var TagWrapper = { 206 | 207 | // Wrap tag so it can perform the effect 208 | input: function(element) { 209 | 210 | var parent = element.parentNode; 211 | 212 | // If input already have parent just pass through 213 | if (parent.tagName.toLowerCase() === 'i' && parent.classList.contains('waves-effect')) { 214 | return; 215 | } 216 | 217 | // Put element class and style to the specified parent 218 | var wrapper = document.createElement('i'); 219 | wrapper.className = element.className + ' waves-input-wrapper'; 220 | element.className = 'waves-button-input'; 221 | 222 | // Put element as child 223 | parent.replaceChild(wrapper, element); 224 | wrapper.appendChild(element); 225 | 226 | // Apply element color and background color to wrapper 227 | var elementStyle = window.getComputedStyle(element, null); 228 | var color = elementStyle.color; 229 | var backgroundColor = elementStyle.backgroundColor; 230 | 231 | wrapper.setAttribute('style', 'color:' + color + ';background:' + backgroundColor); 232 | element.setAttribute('style', 'background-color:rgba(0,0,0,0);'); 233 | 234 | }, 235 | 236 | // Wrap tag so it can perform the effect 237 | img: function(element) { 238 | 239 | var parent = element.parentNode; 240 | 241 | // If input already have parent just pass through 242 | if (parent.tagName.toLowerCase() === 'i' && parent.classList.contains('waves-effect')) { 243 | return; 244 | } 245 | 246 | // Put element as child 247 | var wrapper = document.createElement('i'); 248 | parent.replaceChild(wrapper, element); 249 | wrapper.appendChild(element); 250 | 251 | } 252 | }; 253 | 254 | /** 255 | * Hide the effect and remove the ripple. Must be 256 | * a separate function to pass the JSLint... 257 | */ 258 | function removeRipple(e, el, ripple) { 259 | 260 | // Check if the ripple still exist 261 | if (!ripple) { 262 | return; 263 | } 264 | 265 | ripple.classList.remove('waves-rippling'); 266 | 267 | var relativeX = ripple.getAttribute('data-x'); 268 | var relativeY = ripple.getAttribute('data-y'); 269 | var scale = ripple.getAttribute('data-scale'); 270 | var translate = ripple.getAttribute('data-translate'); 271 | 272 | // Get delay beetween mousedown and mouse leave 273 | var diff = Date.now() - Number(ripple.getAttribute('data-hold')); 274 | var delay = 350 - diff; 275 | 276 | if (delay < 0) { 277 | delay = 0; 278 | } 279 | 280 | if (e.type === 'mousemove') { 281 | delay = 150; 282 | } 283 | 284 | // Fade out ripple after delay 285 | var duration = e.type === 'mousemove' ? 2500 : Effect.duration; 286 | 287 | setTimeout(function() { 288 | 289 | var style = { 290 | top: relativeY + 'px', 291 | left: relativeX + 'px', 292 | opacity: '0', 293 | 294 | // Duration 295 | '-webkit-transition-duration': duration + 'ms', 296 | '-moz-transition-duration': duration + 'ms', 297 | '-o-transition-duration': duration + 'ms', 298 | 'transition-duration': duration + 'ms', 299 | '-webkit-transform': scale + ' ' + translate, 300 | '-moz-transform': scale + ' ' + translate, 301 | '-ms-transform': scale + ' ' + translate, 302 | '-o-transform': scale + ' ' + translate, 303 | 'transform': scale + ' ' + translate 304 | }; 305 | 306 | ripple.setAttribute('style', convertStyle(style)); 307 | 308 | setTimeout(function() { 309 | try { 310 | el.removeChild(ripple); 311 | } catch (e) { 312 | return false; 313 | } 314 | }, duration); 315 | 316 | }, delay); 317 | } 318 | 319 | 320 | /** 321 | * Disable mousedown event for 500ms during and after touch 322 | */ 323 | var TouchHandler = { 324 | 325 | /* uses an integer rather than bool so there's no issues with 326 | * needing to clear timeouts if another touch event occurred 327 | * within the 500ms. Cannot mouseup between touchstart and 328 | * touchend, nor in the 500ms after touchend. */ 329 | touches: 0, 330 | 331 | allowEvent: function(e) { 332 | 333 | var allow = true; 334 | 335 | if (/^(mousedown|mousemove)$/.test(e.type) && TouchHandler.touches) { 336 | allow = false; 337 | } 338 | 339 | return allow; 340 | }, 341 | registerEvent: function(e) { 342 | var eType = e.type; 343 | 344 | if (eType === 'touchstart') { 345 | 346 | TouchHandler.touches += 1; // push 347 | 348 | } else if (/^(touchend|touchcancel)$/.test(eType)) { 349 | 350 | setTimeout(function() { 351 | if (TouchHandler.touches) { 352 | TouchHandler.touches -= 1; // pop after 500ms 353 | } 354 | }, 500); 355 | 356 | } 357 | } 358 | }; 359 | 360 | 361 | /** 362 | * Delegated click handler for .waves-effect element. 363 | * returns null when .waves-effect element not in "click tree" 364 | */ 365 | function getWavesEffectElement(e) { 366 | 367 | if (TouchHandler.allowEvent(e) === false) { 368 | return null; 369 | } 370 | 371 | var element = null; 372 | var target = e.target || e.srcElement; 373 | 374 | while (target.parentElement) { 375 | if ( (!(target instanceof SVGElement)) && target.classList.contains('waves-effect')) { 376 | element = target; 377 | break; 378 | } 379 | target = target.parentElement; 380 | } 381 | 382 | return element; 383 | } 384 | 385 | /** 386 | * Bubble the click and show effect if .waves-effect elem was found 387 | */ 388 | function showEffect(e) { 389 | 390 | // Disable effect if element has "disabled" property on it 391 | // In some cases, the event is not triggered by the current element 392 | // if (e.target.getAttribute('disabled') !== null) { 393 | // return; 394 | // } 395 | 396 | var element = getWavesEffectElement(e); 397 | 398 | if (element !== null) { 399 | 400 | // Make it sure the element has either disabled property, disabled attribute or 'disabled' class 401 | if (element.disabled || element.getAttribute('disabled') || element.classList.contains('disabled')) { 402 | return; 403 | } 404 | 405 | TouchHandler.registerEvent(e); 406 | 407 | if (e.type === 'touchstart' && Effect.delay) { 408 | 409 | var hidden = false; 410 | 411 | var timer = setTimeout(function () { 412 | timer = null; 413 | Effect.show(e, element); 414 | }, Effect.delay); 415 | 416 | var hideEffect = function(hideEvent) { 417 | 418 | // if touch hasn't moved, and effect not yet started: start effect now 419 | if (timer) { 420 | clearTimeout(timer); 421 | timer = null; 422 | Effect.show(e, element); 423 | } 424 | if (!hidden) { 425 | hidden = true; 426 | Effect.hide(hideEvent, element); 427 | } 428 | 429 | removeListeners(); 430 | }; 431 | 432 | var touchMove = function(moveEvent) { 433 | if (timer) { 434 | clearTimeout(timer); 435 | timer = null; 436 | } 437 | hideEffect(moveEvent); 438 | 439 | removeListeners(); 440 | }; 441 | 442 | element.addEventListener('touchmove', touchMove, false); 443 | element.addEventListener('touchend', hideEffect, false); 444 | element.addEventListener('touchcancel', hideEffect, false); 445 | 446 | var removeListeners = function() { 447 | element.removeEventListener('touchmove', touchMove); 448 | element.removeEventListener('touchend', hideEffect); 449 | element.removeEventListener('touchcancel', hideEffect); 450 | }; 451 | } else { 452 | 453 | Effect.show(e, element); 454 | 455 | if (isTouchAvailable) { 456 | element.addEventListener('touchend', Effect.hide, false); 457 | element.addEventListener('touchcancel', Effect.hide, false); 458 | } 459 | 460 | element.addEventListener('mouseup', Effect.hide, false); 461 | element.addEventListener('mouseleave', Effect.hide, false); 462 | } 463 | } 464 | } 465 | 466 | Waves.init = function(options) { 467 | var body = document.body; 468 | 469 | options = options || {}; 470 | 471 | if ('duration' in options) { 472 | Effect.duration = options.duration; 473 | } 474 | 475 | if ('delay' in options) { 476 | Effect.delay = options.delay; 477 | } 478 | 479 | if (isTouchAvailable) { 480 | body.addEventListener('touchstart', showEffect, false); 481 | body.addEventListener('touchcancel', TouchHandler.registerEvent, false); 482 | body.addEventListener('touchend', TouchHandler.registerEvent, false); 483 | } 484 | 485 | body.addEventListener('mousedown', showEffect, false); 486 | }; 487 | 488 | 489 | /** 490 | * Attach Waves to dynamically loaded inputs, or add .waves-effect and other 491 | * waves classes to a set of elements. Set drag to true if the ripple mouseover 492 | * or skimming effect should be applied to the elements. 493 | */ 494 | Waves.attach = function(elements, classes) { 495 | 496 | elements = getWavesElements(elements); 497 | 498 | if (toString.call(classes) === '[object Array]') { 499 | classes = classes.join(' '); 500 | } 501 | 502 | classes = classes ? ' ' + classes : ''; 503 | 504 | var element, tagName; 505 | 506 | for (var i = 0, len = elements.length; i < len; i++) { 507 | 508 | element = elements[i]; 509 | tagName = element.tagName.toLowerCase(); 510 | 511 | if (['input', 'img'].indexOf(tagName) !== -1) { 512 | TagWrapper[tagName](element); 513 | element = element.parentElement; 514 | } 515 | 516 | if (element.className.indexOf('waves-effect') === -1) { 517 | element.className += ' waves-effect' + classes; 518 | } 519 | } 520 | }; 521 | 522 | 523 | /** 524 | * Cause a ripple to appear in an element via code. 525 | */ 526 | Waves.ripple = function(elements, options) { 527 | elements = getWavesElements(elements); 528 | var elementsLen = elements.length; 529 | 530 | options = options || {}; 531 | options.wait = options.wait || 0; 532 | options.position = options.position || null; // default = centre of element 533 | 534 | 535 | if (elementsLen) { 536 | var element, pos, off, centre = {}, i = 0; 537 | var mousedown = { 538 | type: 'mousedown', 539 | button: 1 540 | }; 541 | var hideRipple = function(mouseup, element) { 542 | return function() { 543 | Effect.hide(mouseup, element); 544 | }; 545 | }; 546 | 547 | for (; i < elementsLen; i++) { 548 | element = elements[i]; 549 | pos = options.position || { 550 | x: element.clientWidth / 2, 551 | y: element.clientHeight / 2 552 | }; 553 | 554 | off = offset(element); 555 | centre.x = off.left + pos.x; 556 | centre.y = off.top + pos.y; 557 | 558 | mousedown.pageX = centre.x; 559 | mousedown.pageY = centre.y; 560 | 561 | Effect.show(mousedown, element); 562 | 563 | if (options.wait >= 0 && options.wait !== null) { 564 | var mouseup = { 565 | type: 'mouseup', 566 | button: 1 567 | }; 568 | 569 | setTimeout(hideRipple(mouseup, element), options.wait); 570 | } 571 | } 572 | } 573 | }; 574 | 575 | /** 576 | * Remove all ripples from an element. 577 | */ 578 | Waves.calm = function(elements) { 579 | elements = getWavesElements(elements); 580 | var mouseup = { 581 | type: 'mouseup', 582 | button: 1 583 | }; 584 | 585 | for (var i = 0, len = elements.length; i < len; i++) { 586 | Effect.hide(mouseup, elements[i]); 587 | } 588 | }; 589 | 590 | /** 591 | * Deprecated API fallback 592 | */ 593 | Waves.displayEffect = function(options) { 594 | console.error('Waves.displayEffect() has been deprecated and will be removed in future version. Please use Waves.init() to initialize Waves effect'); 595 | Waves.init(options); 596 | }; 597 | 598 | return Waves; 599 | }); 600 | -------------------------------------------------------------------------------- /webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | export default { 2 | entry: './index.js', 3 | output: { 4 | path: __dirname + '/dist', 5 | filename: 'vue-waves.js', 6 | library: 'VueWaves', 7 | libraryTarget: 'umd', 8 | }, 9 | module: { 10 | rules: [ 11 | { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }, 12 | { test: /\.css$/, loader: "style-loader!css-loader" }, 13 | ] 14 | } 15 | } 16 | --------------------------------------------------------------------------------