PageRenderTime 58ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/client/lib/paper-ripple/paper-ripple.html

https://gitlab.com/bazmati/crowdify-coin
HTML | 757 lines | 554 code | 125 blank | 78 comment | 0 complexity | c44508d7d7fb0a611811ef680a77ffc4 MD5 | raw file
  1. <!--
  2. Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
  3. This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
  4. The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
  5. The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
  6. Code distributed by Google as part of the polymer project is also
  7. subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
  8. -->
  9. <link rel="import" href="../polymer/polymer.html">
  10. <link rel="import" href="../iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
  11. <!--
  12. Material design: [Surface reaction](https://www.google.com/design/spec/animation/responsive-interaction.html#responsive-interaction-surface-reaction)
  13. `paper-ripple` provides a visual effect that other paper elements can
  14. use to simulate a rippling effect emanating from the point of contact. The
  15. effect can be visualized as a concentric circle with motion.
  16. Example:
  17. <div style="position:relative">
  18. <paper-ripple></paper-ripple>
  19. </div>
  20. Note, it's important that the parent container of the ripple be relative position, otherwise
  21. the ripple will emanate outside of the desired container.
  22. `paper-ripple` listens to "mousedown" and "mouseup" events so it would display ripple
  23. effect when touches on it. You can also defeat the default behavior and
  24. manually route the down and up actions to the ripple element. Note that it is
  25. important if you call `downAction()` you will have to make sure to call
  26. `upAction()` so that `paper-ripple` would end the animation loop.
  27. Example:
  28. <paper-ripple id="ripple" style="pointer-events: none;"></paper-ripple>
  29. ...
  30. downAction: function(e) {
  31. this.$.ripple.downAction({x: e.x, y: e.y});
  32. },
  33. upAction: function(e) {
  34. this.$.ripple.upAction();
  35. }
  36. Styling ripple effect:
  37. Use CSS color property to style the ripple:
  38. paper-ripple {
  39. color: #4285f4;
  40. }
  41. Note that CSS color property is inherited so it is not required to set it on
  42. the `paper-ripple` element directly.
  43. By default, the ripple is centered on the point of contact. Apply the `recenters`
  44. attribute to have the ripple grow toward the center of its container.
  45. <paper-ripple recenters></paper-ripple>
  46. You can also center the ripple inside its container from the start.
  47. <paper-ripple center></paper-ripple>
  48. Apply `circle` class to make the rippling effect within a circle.
  49. <paper-ripple class="circle"></paper-ripple>
  50. @group Paper Elements
  51. @element paper-ripple
  52. @hero hero.svg
  53. @demo demo/index.html
  54. -->
  55. <dom-module id="paper-ripple">
  56. <!--
  57. Fired when the animation finishes. This is useful if you want to wait until the ripple
  58. animation finishes to perform some action.
  59. @event transitionend
  60. @param {Object} detail
  61. @param {Object} detail.node The animated node
  62. -->
  63. <template>
  64. <style>
  65. :host {
  66. display: block;
  67. position: absolute;
  68. border-radius: inherit;
  69. overflow: hidden;
  70. top: 0;
  71. left: 0;
  72. right: 0;
  73. bottom: 0;
  74. /* See PolymerElements/paper-behaviors/issues/34. On non-Chrome browsers,
  75. * creating a node (with a position:absolute) in the middle of an event
  76. * handler "interrupts" that event handler (which happens when the
  77. * ripple is created on demand) */
  78. pointer-events: none;
  79. }
  80. :host([animating]) {
  81. /* This resolves a rendering issue in Chrome (as of 40) where the
  82. ripple is not properly clipped by its parent (which may have
  83. rounded corners). See: http://jsbin.com/temexa/4
  84. Note: We only apply this style conditionally. Otherwise, the browser
  85. will create a new compositing layer for every ripple element on the
  86. page, and that would be bad. */
  87. -webkit-transform: translate(0, 0);
  88. transform: translate3d(0, 0, 0);
  89. }
  90. #background,
  91. #waves,
  92. .wave-container,
  93. .wave {
  94. pointer-events: none;
  95. position: absolute;
  96. top: 0;
  97. left: 0;
  98. width: 100%;
  99. height: 100%;
  100. }
  101. #background,
  102. .wave {
  103. opacity: 0;
  104. }
  105. #waves,
  106. .wave {
  107. overflow: hidden;
  108. }
  109. .wave-container,
  110. .wave {
  111. border-radius: 50%;
  112. }
  113. :host(.circle) #background,
  114. :host(.circle) #waves {
  115. border-radius: 50%;
  116. }
  117. :host(.circle) .wave-container {
  118. overflow: hidden;
  119. }
  120. </style>
  121. <div id="background"></div>
  122. <div id="waves"></div>
  123. </template>
  124. </dom-module>
  125. <script>
  126. (function() {
  127. var Utility = {
  128. distance: function(x1, y1, x2, y2) {
  129. var xDelta = (x1 - x2);
  130. var yDelta = (y1 - y2);
  131. return Math.sqrt(xDelta * xDelta + yDelta * yDelta);
  132. },
  133. now: window.performance && window.performance.now ?
  134. window.performance.now.bind(window.performance) : Date.now
  135. };
  136. /**
  137. * @param {HTMLElement} element
  138. * @constructor
  139. */
  140. function ElementMetrics(element) {
  141. this.element = element;
  142. this.width = this.boundingRect.width;
  143. this.height = this.boundingRect.height;
  144. this.size = Math.max(this.width, this.height);
  145. }
  146. ElementMetrics.prototype = {
  147. get boundingRect () {
  148. return this.element.getBoundingClientRect();
  149. },
  150. furthestCornerDistanceFrom: function(x, y) {
  151. var topLeft = Utility.distance(x, y, 0, 0);
  152. var topRight = Utility.distance(x, y, this.width, 0);
  153. var bottomLeft = Utility.distance(x, y, 0, this.height);
  154. var bottomRight = Utility.distance(x, y, this.width, this.height);
  155. return Math.max(topLeft, topRight, bottomLeft, bottomRight);
  156. }
  157. };
  158. /**
  159. * @param {HTMLElement} element
  160. * @constructor
  161. */
  162. function Ripple(element) {
  163. this.element = element;
  164. this.color = window.getComputedStyle(element).color;
  165. this.wave = document.createElement('div');
  166. this.waveContainer = document.createElement('div');
  167. this.wave.style.backgroundColor = this.color;
  168. this.wave.classList.add('wave');
  169. this.waveContainer.classList.add('wave-container');
  170. Polymer.dom(this.waveContainer).appendChild(this.wave);
  171. this.resetInteractionState();
  172. }
  173. Ripple.MAX_RADIUS = 300;
  174. Ripple.prototype = {
  175. get recenters() {
  176. return this.element.recenters;
  177. },
  178. get center() {
  179. return this.element.center;
  180. },
  181. get mouseDownElapsed() {
  182. var elapsed;
  183. if (!this.mouseDownStart) {
  184. return 0;
  185. }
  186. elapsed = Utility.now() - this.mouseDownStart;
  187. if (this.mouseUpStart) {
  188. elapsed -= this.mouseUpElapsed;
  189. }
  190. return elapsed;
  191. },
  192. get mouseUpElapsed() {
  193. return this.mouseUpStart ?
  194. Utility.now () - this.mouseUpStart : 0;
  195. },
  196. get mouseDownElapsedSeconds() {
  197. return this.mouseDownElapsed / 1000;
  198. },
  199. get mouseUpElapsedSeconds() {
  200. return this.mouseUpElapsed / 1000;
  201. },
  202. get mouseInteractionSeconds() {
  203. return this.mouseDownElapsedSeconds + this.mouseUpElapsedSeconds;
  204. },
  205. get initialOpacity() {
  206. return this.element.initialOpacity;
  207. },
  208. get opacityDecayVelocity() {
  209. return this.element.opacityDecayVelocity;
  210. },
  211. get radius() {
  212. var width2 = this.containerMetrics.width * this.containerMetrics.width;
  213. var height2 = this.containerMetrics.height * this.containerMetrics.height;
  214. var waveRadius = Math.min(
  215. Math.sqrt(width2 + height2),
  216. Ripple.MAX_RADIUS
  217. ) * 1.1 + 5;
  218. var duration = 1.1 - 0.2 * (waveRadius / Ripple.MAX_RADIUS);
  219. var timeNow = this.mouseInteractionSeconds / duration;
  220. var size = waveRadius * (1 - Math.pow(80, -timeNow));
  221. return Math.abs(size);
  222. },
  223. get opacity() {
  224. if (!this.mouseUpStart) {
  225. return this.initialOpacity;
  226. }
  227. return Math.max(
  228. 0,
  229. this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVelocity
  230. );
  231. },
  232. get outerOpacity() {
  233. // Linear increase in background opacity, capped at the opacity
  234. // of the wavefront (waveOpacity).
  235. var outerOpacity = this.mouseUpElapsedSeconds * 0.3;
  236. var waveOpacity = this.opacity;
  237. return Math.max(
  238. 0,
  239. Math.min(outerOpacity, waveOpacity)
  240. );
  241. },
  242. get isOpacityFullyDecayed() {
  243. return this.opacity < 0.01 &&
  244. this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
  245. },
  246. get isRestingAtMaxRadius() {
  247. return this.opacity >= this.initialOpacity &&
  248. this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS);
  249. },
  250. get isAnimationComplete() {
  251. return this.mouseUpStart ?
  252. this.isOpacityFullyDecayed : this.isRestingAtMaxRadius;
  253. },
  254. get translationFraction() {
  255. return Math.min(
  256. 1,
  257. this.radius / this.containerMetrics.size * 2 / Math.sqrt(2)
  258. );
  259. },
  260. get xNow() {
  261. if (this.xEnd) {
  262. return this.xStart + this.translationFraction * (this.xEnd - this.xStart);
  263. }
  264. return this.xStart;
  265. },
  266. get yNow() {
  267. if (this.yEnd) {
  268. return this.yStart + this.translationFraction * (this.yEnd - this.yStart);
  269. }
  270. return this.yStart;
  271. },
  272. get isMouseDown() {
  273. return this.mouseDownStart && !this.mouseUpStart;
  274. },
  275. resetInteractionState: function() {
  276. this.maxRadius = 0;
  277. this.mouseDownStart = 0;
  278. this.mouseUpStart = 0;
  279. this.xStart = 0;
  280. this.yStart = 0;
  281. this.xEnd = 0;
  282. this.yEnd = 0;
  283. this.slideDistance = 0;
  284. this.containerMetrics = new ElementMetrics(this.element);
  285. },
  286. draw: function() {
  287. var scale;
  288. var translateString;
  289. var dx;
  290. var dy;
  291. this.wave.style.opacity = this.opacity;
  292. scale = this.radius / (this.containerMetrics.size / 2);
  293. dx = this.xNow - (this.containerMetrics.width / 2);
  294. dy = this.yNow - (this.containerMetrics.height / 2);
  295. // 2d transform for safari because of border-radius and overflow:hidden clipping bug.
  296. // https://bugs.webkit.org/show_bug.cgi?id=98538
  297. this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' + dy + 'px)';
  298. this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy + 'px, 0)';
  299. this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')';
  300. this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)';
  301. },
  302. /** @param {Event=} event */
  303. downAction: function(event) {
  304. var xCenter = this.containerMetrics.width / 2;
  305. var yCenter = this.containerMetrics.height / 2;
  306. this.resetInteractionState();
  307. this.mouseDownStart = Utility.now();
  308. if (this.center) {
  309. this.xStart = xCenter;
  310. this.yStart = yCenter;
  311. this.slideDistance = Utility.distance(
  312. this.xStart, this.yStart, this.xEnd, this.yEnd
  313. );
  314. } else {
  315. this.xStart = event ?
  316. event.detail.x - this.containerMetrics.boundingRect.left :
  317. this.containerMetrics.width / 2;
  318. this.yStart = event ?
  319. event.detail.y - this.containerMetrics.boundingRect.top :
  320. this.containerMetrics.height / 2;
  321. }
  322. if (this.recenters) {
  323. this.xEnd = xCenter;
  324. this.yEnd = yCenter;
  325. this.slideDistance = Utility.distance(
  326. this.xStart, this.yStart, this.xEnd, this.yEnd
  327. );
  328. }
  329. this.maxRadius = this.containerMetrics.furthestCornerDistanceFrom(
  330. this.xStart,
  331. this.yStart
  332. );
  333. this.waveContainer.style.top =
  334. (this.containerMetrics.height - this.containerMetrics.size) / 2 + 'px';
  335. this.waveContainer.style.left =
  336. (this.containerMetrics.width - this.containerMetrics.size) / 2 + 'px';
  337. this.waveContainer.style.width = this.containerMetrics.size + 'px';
  338. this.waveContainer.style.height = this.containerMetrics.size + 'px';
  339. },
  340. /** @param {Event=} event */
  341. upAction: function(event) {
  342. if (!this.isMouseDown) {
  343. return;
  344. }
  345. this.mouseUpStart = Utility.now();
  346. },
  347. remove: function() {
  348. Polymer.dom(this.waveContainer.parentNode).removeChild(
  349. this.waveContainer
  350. );
  351. }
  352. };
  353. Polymer({
  354. is: 'paper-ripple',
  355. behaviors: [
  356. Polymer.IronA11yKeysBehavior
  357. ],
  358. properties: {
  359. /**
  360. * The initial opacity set on the wave.
  361. *
  362. * @attribute initialOpacity
  363. * @type number
  364. * @default 0.25
  365. */
  366. initialOpacity: {
  367. type: Number,
  368. value: 0.25
  369. },
  370. /**
  371. * How fast (opacity per second) the wave fades out.
  372. *
  373. * @attribute opacityDecayVelocity
  374. * @type number
  375. * @default 0.8
  376. */
  377. opacityDecayVelocity: {
  378. type: Number,
  379. value: 0.8
  380. },
  381. /**
  382. * If true, ripples will exhibit a gravitational pull towards
  383. * the center of their container as they fade away.
  384. *
  385. * @attribute recenters
  386. * @type boolean
  387. * @default false
  388. */
  389. recenters: {
  390. type: Boolean,
  391. value: false
  392. },
  393. /**
  394. * If true, ripples will center inside its container
  395. *
  396. * @attribute recenters
  397. * @type boolean
  398. * @default false
  399. */
  400. center: {
  401. type: Boolean,
  402. value: false
  403. },
  404. /**
  405. * A list of the visual ripples.
  406. *
  407. * @attribute ripples
  408. * @type Array
  409. * @default []
  410. */
  411. ripples: {
  412. type: Array,
  413. value: function() {
  414. return [];
  415. }
  416. },
  417. /**
  418. * True when there are visible ripples animating within the
  419. * element.
  420. */
  421. animating: {
  422. type: Boolean,
  423. readOnly: true,
  424. reflectToAttribute: true,
  425. value: false
  426. },
  427. /**
  428. * If true, the ripple will remain in the "down" state until `holdDown`
  429. * is set to false again.
  430. */
  431. holdDown: {
  432. type: Boolean,
  433. value: false,
  434. observer: '_holdDownChanged'
  435. },
  436. /**
  437. * If true, the ripple will not generate a ripple effect
  438. * via pointer interaction.
  439. * Calling ripple's imperative api like `simulatedRipple` will
  440. * still generate the ripple effect.
  441. */
  442. noink: {
  443. type: Boolean,
  444. value: false
  445. },
  446. _animating: {
  447. type: Boolean
  448. },
  449. _boundAnimate: {
  450. type: Function,
  451. value: function() {
  452. return this.animate.bind(this);
  453. }
  454. }
  455. },
  456. get target () {
  457. var ownerRoot = Polymer.dom(this).getOwnerRoot();
  458. var target;
  459. if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE
  460. target = ownerRoot.host;
  461. } else {
  462. target = this.parentNode;
  463. }
  464. return target;
  465. },
  466. keyBindings: {
  467. 'enter:keydown': '_onEnterKeydown',
  468. 'space:keydown': '_onSpaceKeydown',
  469. 'space:keyup': '_onSpaceKeyup'
  470. },
  471. attached: function() {
  472. // Set up a11yKeysBehavior to listen to key events on the target,
  473. // so that space and enter activate the ripple even if the target doesn't
  474. // handle key events. The key handlers deal with `noink` themselves.
  475. this.keyEventTarget = this.target;
  476. this.listen(this.target, 'up', 'uiUpAction');
  477. this.listen(this.target, 'down', 'uiDownAction');
  478. },
  479. detached: function() {
  480. this.unlisten(this.target, 'up', 'uiUpAction');
  481. this.unlisten(this.target, 'down', 'uiDownAction');
  482. },
  483. get shouldKeepAnimating () {
  484. for (var index = 0; index < this.ripples.length; ++index) {
  485. if (!this.ripples[index].isAnimationComplete) {
  486. return true;
  487. }
  488. }
  489. return false;
  490. },
  491. simulatedRipple: function() {
  492. this.downAction(null);
  493. // Please see polymer/polymer#1305
  494. this.async(function() {
  495. this.upAction();
  496. }, 1);
  497. },
  498. /**
  499. * Provokes a ripple down effect via a UI event,
  500. * respecting the `noink` property.
  501. * @param {Event=} event
  502. */
  503. uiDownAction: function(event) {
  504. if (!this.noink) {
  505. this.downAction(event);
  506. }
  507. },
  508. /**
  509. * Provokes a ripple down effect via a UI event,
  510. * *not* respecting the `noink` property.
  511. * @param {Event=} event
  512. */
  513. downAction: function(event) {
  514. if (this.holdDown && this.ripples.length > 0) {
  515. return;
  516. }
  517. var ripple = this.addRipple();
  518. ripple.downAction(event);
  519. if (!this._animating) {
  520. this.animate();
  521. }
  522. },
  523. /**
  524. * Provokes a ripple up effect via a UI event,
  525. * respecting the `noink` property.
  526. * @param {Event=} event
  527. */
  528. uiUpAction: function(event) {
  529. if (!this.noink) {
  530. this.upAction(event);
  531. }
  532. },
  533. /**
  534. * Provokes a ripple up effect via a UI event,
  535. * *not* respecting the `noink` property.
  536. * @param {Event=} event
  537. */
  538. upAction: function(event) {
  539. if (this.holdDown) {
  540. return;
  541. }
  542. this.ripples.forEach(function(ripple) {
  543. ripple.upAction(event);
  544. });
  545. this.animate();
  546. },
  547. onAnimationComplete: function() {
  548. this._animating = false;
  549. this.$.background.style.backgroundColor = null;
  550. this.fire('transitionend');
  551. },
  552. addRipple: function() {
  553. var ripple = new Ripple(this);
  554. Polymer.dom(this.$.waves).appendChild(ripple.waveContainer);
  555. this.$.background.style.backgroundColor = ripple.color;
  556. this.ripples.push(ripple);
  557. this._setAnimating(true);
  558. return ripple;
  559. },
  560. removeRipple: function(ripple) {
  561. var rippleIndex = this.ripples.indexOf(ripple);
  562. if (rippleIndex < 0) {
  563. return;
  564. }
  565. this.ripples.splice(rippleIndex, 1);
  566. ripple.remove();
  567. if (!this.ripples.length) {
  568. this._setAnimating(false);
  569. }
  570. },
  571. animate: function() {
  572. var index;
  573. var ripple;
  574. this._animating = true;
  575. for (index = 0; index < this.ripples.length; ++index) {
  576. ripple = this.ripples[index];
  577. ripple.draw();
  578. this.$.background.style.opacity = ripple.outerOpacity;
  579. if (ripple.isOpacityFullyDecayed && !ripple.isRestingAtMaxRadius) {
  580. this.removeRipple(ripple);
  581. }
  582. }
  583. if (!this.shouldKeepAnimating && this.ripples.length === 0) {
  584. this.onAnimationComplete();
  585. } else {
  586. window.requestAnimationFrame(this._boundAnimate);
  587. }
  588. },
  589. _onEnterKeydown: function() {
  590. this.uiDownAction();
  591. this.async(this.uiUpAction, 1);
  592. },
  593. _onSpaceKeydown: function() {
  594. this.uiDownAction();
  595. },
  596. _onSpaceKeyup: function() {
  597. this.uiUpAction();
  598. },
  599. // note: holdDown does not respect noink since it can be a focus based
  600. // effect.
  601. _holdDownChanged: function(newVal, oldVal) {
  602. if (oldVal === undefined) {
  603. return;
  604. }
  605. if (newVal) {
  606. this.downAction();
  607. } else {
  608. this.upAction();
  609. }
  610. }
  611. });
  612. })();
  613. </script>