You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

344 lines
11 KiB

2 years ago
  1. /*!
  2. * Waves v0.6.5
  3. * http://fian.my.id/Waves
  4. *
  5. * Copyright 2014 Alfiana E. Sibuea and other contributors
  6. * Released under the MIT license
  7. * https://github.com/fians/Waves/blob/master/LICENSE
  8. */
  9. ; (function (window, factory) {
  10. "use strict";
  11. // AMD. Register as an anonymous module. Wrap in function so we have access
  12. // to root via `this`.
  13. if (typeof define === "function" && define.amd) {
  14. define([], function () {
  15. return factory.apply(window);
  16. });
  17. }
  18. // Node. Does not work with strict CommonJS, but only CommonJS-like
  19. // environments that support module.exports, like Node.
  20. else if (typeof exports === "object") {
  21. module.exports = factory.call(window);
  22. }
  23. // Browser globals.
  24. else {
  25. window.Waves = factory.call(window);
  26. }
  27. })(typeof global === "object" ? global : this, function () {
  28. "use strict";
  29. var Waves = Waves || {};
  30. var $$ = document.querySelectorAll.bind(document);
  31. // Find exact position of element
  32. function isWindow(obj) {
  33. return obj !== null && obj === obj.window;
  34. }
  35. function getWindow(elem) {
  36. return isWindow(elem) ? elem : elem.nodeType === 9 && elem.defaultView;
  37. }
  38. function offset(elem) {
  39. var docElem, win,
  40. box = { top: 0, left: 0 },
  41. doc = elem && elem.ownerDocument;
  42. docElem = doc.documentElement;
  43. if (typeof elem.getBoundingClientRect !== typeof undefined) {
  44. box = elem.getBoundingClientRect();
  45. }
  46. win = getWindow(doc);
  47. return {
  48. top: box.top + win.pageYOffset - docElem.clientTop,
  49. left: box.left + win.pageXOffset - docElem.clientLeft
  50. };
  51. }
  52. function convertStyle(obj) {
  53. var style = '';
  54. for (var a in obj) {
  55. if (obj.hasOwnProperty(a)) {
  56. style += (a + ':' + obj[a] + ';');
  57. }
  58. }
  59. return style;
  60. }
  61. var Effect = {
  62. // Effect delay
  63. duration: 750,
  64. show: function (e, element) {
  65. // Disable right click
  66. if (e.button === 2) {
  67. return false;
  68. }
  69. var el = element || this;
  70. // Create ripple
  71. var ripple = document.createElement('div');
  72. ripple.className = 'waves-ripple';
  73. el.appendChild(ripple);
  74. // Get click coordinate and element witdh
  75. var pos = offset(el);
  76. var relativeY = (e.pageY - pos.top);
  77. var relativeX = (e.pageX - pos.left);
  78. var scale = 'scale(' + ((el.clientWidth / 100) * 3) + ')';
  79. // Support for touch devices
  80. if ('touches' in e) {
  81. relativeY = (e.touches[0].pageY - pos.top);
  82. relativeX = (e.touches[0].pageX - pos.left);
  83. }
  84. // Attach data to element
  85. ripple.setAttribute('data-hold', Date.now());
  86. ripple.setAttribute('data-scale', scale);
  87. ripple.setAttribute('data-x', relativeX);
  88. ripple.setAttribute('data-y', relativeY);
  89. // Set ripple position
  90. var rippleStyle = {
  91. 'top': relativeY + 'px',
  92. 'left': relativeX + 'px'
  93. };
  94. ripple.className = ripple.className + ' waves-notransition';
  95. ripple.setAttribute('style', convertStyle(rippleStyle));
  96. ripple.className = ripple.className.replace('waves-notransition', '');
  97. // Scale the ripple
  98. rippleStyle['-webkit-transform'] = scale;
  99. rippleStyle['-moz-transform'] = scale;
  100. rippleStyle['-ms-transform'] = scale;
  101. rippleStyle['-o-transform'] = scale;
  102. rippleStyle.transform = scale;
  103. rippleStyle.opacity = '1';
  104. rippleStyle['-webkit-transition-duration'] = Effect.duration + 'ms';
  105. rippleStyle['-moz-transition-duration'] = Effect.duration + 'ms';
  106. rippleStyle['-o-transition-duration'] = Effect.duration + 'ms';
  107. rippleStyle['transition-duration'] = Effect.duration + 'ms';
  108. ripple.setAttribute('style', convertStyle(rippleStyle));
  109. },
  110. hide: function (e) {
  111. TouchHandler.touchup(e);
  112. var el = this;
  113. var width = el.clientWidth * 1.4;
  114. // Get first ripple
  115. var ripple = null;
  116. var ripples = el.getElementsByClassName('waves-ripple');
  117. if (ripples.length > 0) {
  118. ripple = ripples[ripples.length - 1];
  119. } else {
  120. return false;
  121. }
  122. var relativeX = ripple.getAttribute('data-x');
  123. var relativeY = ripple.getAttribute('data-y');
  124. var scale = ripple.getAttribute('data-scale');
  125. // Get delay beetween mousedown and mouse leave
  126. var diff = Date.now() - Number(ripple.getAttribute('data-hold'));
  127. var delay = 350 - diff;
  128. if (delay < 0) {
  129. delay = 0;
  130. }
  131. // Fade out ripple after delay
  132. setTimeout(function () {
  133. var style = {
  134. 'top': relativeY + 'px',
  135. 'left': relativeX + 'px',
  136. 'opacity': '0',
  137. // Duration
  138. '-webkit-transition-duration': Effect.duration + 'ms',
  139. '-moz-transition-duration': Effect.duration + 'ms',
  140. '-o-transition-duration': Effect.duration + 'ms',
  141. 'transition-duration': Effect.duration + 'ms',
  142. '-webkit-transform': scale,
  143. '-moz-transform': scale,
  144. '-ms-transform': scale,
  145. '-o-transform': scale,
  146. 'transform': scale,
  147. };
  148. ripple.setAttribute('style', convertStyle(style));
  149. setTimeout(function () {
  150. try {
  151. el.removeChild(ripple);
  152. } catch (e) {
  153. return false;
  154. }
  155. }, Effect.duration);
  156. }, delay);
  157. },
  158. // Little hack to make <input> can perform waves effect
  159. wrapInput: function (elements) {
  160. for (var a = 0; a < elements.length; a++) {
  161. var el = elements[a];
  162. if (el.tagName.toLowerCase() === 'input') {
  163. var parent = el.parentNode;
  164. // If input already have parent just pass through
  165. if (parent.tagName.toLowerCase() === 'i' && parent.className.indexOf('waves-effect') !== -1) {
  166. continue;
  167. }
  168. // Put element class and style to the specified parent
  169. var wrapper = document.createElement('i');
  170. wrapper.className = el.className + ' waves-input-wrapper';
  171. var elementStyle = el.getAttribute('style');
  172. if (!elementStyle) {
  173. elementStyle = '';
  174. }
  175. wrapper.setAttribute('style', elementStyle);
  176. el.className = 'waves-button-input';
  177. el.removeAttribute('style');
  178. // Put element as child
  179. parent.replaceChild(wrapper, el);
  180. wrapper.appendChild(el);
  181. }
  182. }
  183. }
  184. };
  185. /**
  186. * Disable mousedown event for 500ms during and after touch
  187. */
  188. var TouchHandler = {
  189. /* uses an integer rather than bool so there's no issues with
  190. * needing to clear timeouts if another touch event occurred
  191. * within the 500ms. Cannot mouseup between touchstart and
  192. * touchend, nor in the 500ms after touchend. */
  193. touches: 0,
  194. allowEvent: function (e) {
  195. var allow = true;
  196. if (e.type === 'touchstart') {
  197. TouchHandler.touches += 1; //push
  198. } else if (e.type === 'touchend' || e.type === 'touchcancel') {
  199. setTimeout(function () {
  200. if (TouchHandler.touches > 0) {
  201. TouchHandler.touches -= 1; //pop after 500ms
  202. }
  203. }, 500);
  204. } else if (e.type === 'mousedown' && TouchHandler.touches > 0) {
  205. allow = false;
  206. }
  207. return allow;
  208. },
  209. touchup: function (e) {
  210. TouchHandler.allowEvent(e);
  211. }
  212. };
  213. /**
  214. * Delegated click handler for .waves-effect element.
  215. * returns null when .waves-effect element not in "click tree"
  216. */
  217. function getWavesEffectElement(e) {
  218. if (TouchHandler.allowEvent(e) === false) {
  219. return null;
  220. }
  221. var element = null;
  222. var target = e.target || e.srcElement;
  223. while (target.parentElement !== null) {
  224. if (!(target instanceof SVGElement) && target.className.indexOf('waves-effect') !== -1) {
  225. element = target;
  226. break;
  227. } else if (target.classList.contains('waves-effect')) {
  228. element = target;
  229. break;
  230. }
  231. target = target.parentElement;
  232. }
  233. return element;
  234. }
  235. /**
  236. * Bubble the click and show effect if .waves-effect elem was found
  237. */
  238. function showEffect(e) {
  239. var element = getWavesEffectElement(e);
  240. if (element !== null) {
  241. Effect.show(e, element);
  242. if ('ontouchstart' in window) {
  243. element.addEventListener('touchend', Effect.hide, false);
  244. element.addEventListener('touchcancel', Effect.hide, false);
  245. }
  246. element.addEventListener('mouseup', Effect.hide, false);
  247. element.addEventListener('mouseleave', Effect.hide, false);
  248. }
  249. }
  250. Waves.displayEffect = function (options) {
  251. options = options || {};
  252. if ('duration' in options) {
  253. Effect.duration = options.duration;
  254. }
  255. //Wrap input inside <i> tag
  256. Effect.wrapInput($$('.waves-effect'));
  257. if ('ontouchstart' in window) {
  258. document.body.addEventListener('touchstart', showEffect, false);
  259. }
  260. document.body.addEventListener('mousedown', showEffect, false);
  261. };
  262. /**
  263. * Attach Waves to an input element (or any element which doesn't
  264. * bubble mouseup/mousedown events).
  265. * Intended to be used with dynamically loaded forms/inputs, or
  266. * where the user doesn't want a delegated click handler.
  267. */
  268. Waves.attach = function (element) {
  269. //FUTURE: automatically add waves classes and allow users
  270. // to specify them with an options param? Eg. light/classic/button
  271. if (element.tagName.toLowerCase() === 'input') {
  272. Effect.wrapInput([element]);
  273. element = element.parentElement;
  274. }
  275. if ('ontouchstart' in window) {
  276. element.addEventListener('touchstart', showEffect, false);
  277. }
  278. element.addEventListener('mousedown', showEffect, false);
  279. };
  280. return Waves;
  281. });