3 * Copyright 2015 Google Inc. All Rights Reserved.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
22 * Class constructor for Ripple MDL component.
23 * Implements MDL component design pattern defined at:
24 * https://github.com/jasonmayes/mdl-component-design-pattern
27 * @param {HTMLElement} element The element that will be upgraded.
29 var MaterialRipple = function MaterialRipple(element) {
30 this.element_ = element;
32 // Initialize instance.
35 window['MaterialRipple'] = MaterialRipple;
38 * Store constants in one place so they can be updated easily.
40 * @enum {string | number}
43 MaterialRipple.prototype.Constant_ = {
44 INITIAL_SCALE: 'scale(0.0001, 0.0001)',
46 INITIAL_OPACITY: '0.4',
52 * Store strings for class names defined by this component that are used in
53 * JavaScript. This allows us to simply change it in one place should we
54 * decide to modify at a later date.
59 MaterialRipple.prototype.CssClasses_ = {
60 RIPPLE_CENTER: 'mdl-ripple--center',
61 RIPPLE_EFFECT_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
63 IS_ANIMATING: 'is-animating',
64 IS_VISIBLE: 'is-visible'
68 * Handle mouse / finger down on element.
70 * @param {Event} event The event that fired.
73 MaterialRipple.prototype.downHandler_ = function(event) {
74 if (!this.rippleElement_.style.width && !this.rippleElement_.style.height) {
75 var rect = this.element_.getBoundingClientRect();
76 this.boundHeight = rect.height;
77 this.boundWidth = rect.width;
78 this.rippleSize_ = Math.sqrt(rect.width * rect.width +
79 rect.height * rect.height) * 2 + 2;
80 this.rippleElement_.style.width = this.rippleSize_ + 'px';
81 this.rippleElement_.style.height = this.rippleSize_ + 'px';
84 this.rippleElement_.classList.add(this.CssClasses_.IS_VISIBLE);
86 if (event.type === 'mousedown' && this.ignoringMouseDown_) {
87 this.ignoringMouseDown_ = false;
89 if (event.type === 'touchstart') {
90 this.ignoringMouseDown_ = true;
92 var frameCount = this.getFrameCount();
96 this.setFrameCount(1);
97 var bound = event.currentTarget.getBoundingClientRect();
100 // Check if we are handling a keyboard click.
101 if (event.clientX === 0 && event.clientY === 0) {
102 x = Math.round(bound.width / 2);
103 y = Math.round(bound.height / 2);
105 var clientX = event.clientX !== undefined ? event.clientX : event.touches[0].clientX;
106 var clientY = event.clientY !== undefined ? event.clientY : event.touches[0].clientY;
107 x = Math.round(clientX - bound.left);
108 y = Math.round(clientY - bound.top);
110 this.setRippleXY(x, y);
111 this.setRippleStyles(true);
112 window.requestAnimationFrame(this.animFrameHandler.bind(this));
117 * Handle mouse / finger up on element.
119 * @param {Event} event The event that fired.
122 MaterialRipple.prototype.upHandler_ = function(event) {
123 // Don't fire for the artificial "mouseup" generated by a double-click.
124 if (event && event.detail !== 2) {
125 // Allow a repaint to occur before removing this class, so the animation
126 // shows for tap events, which seem to trigger a mouseup too soon after
128 window.setTimeout(function() {
129 this.rippleElement_.classList.remove(this.CssClasses_.IS_VISIBLE);
135 * Initialize element.
137 MaterialRipple.prototype.init = function() {
140 this.element_.classList.contains(this.CssClasses_.RIPPLE_CENTER);
141 if (!this.element_.classList.contains(
142 this.CssClasses_.RIPPLE_EFFECT_IGNORE_EVENTS)) {
143 this.rippleElement_ = this.element_.querySelector('.' +
144 this.CssClasses_.RIPPLE);
145 this.frameCount_ = 0;
146 this.rippleSize_ = 0;
150 // Touch start produces a compat mouse down event, which would cause a
151 // second ripples. To avoid that, we use this property to ignore the first
152 // mouse down after a touch start.
153 this.ignoringMouseDown_ = false;
155 this.boundDownHandler = this.downHandler_.bind(this);
156 this.element_.addEventListener('mousedown',
157 this.boundDownHandler);
158 this.element_.addEventListener('touchstart',
159 this.boundDownHandler);
161 this.boundUpHandler = this.upHandler_.bind(this);
162 this.element_.addEventListener('mouseup', this.boundUpHandler);
163 this.element_.addEventListener('mouseleave', this.boundUpHandler);
164 this.element_.addEventListener('touchend', this.boundUpHandler);
165 this.element_.addEventListener('blur', this.boundUpHandler);
168 * Getter for frameCount_.
169 * @return {number} the frame count.
171 this.getFrameCount = function() {
172 return this.frameCount_;
176 * Setter for frameCount_.
177 * @param {number} fC the frame count.
179 this.setFrameCount = function(fC) {
180 this.frameCount_ = fC;
184 * Getter for rippleElement_.
185 * @return {Element} the ripple element.
187 this.getRippleElement = function() {
188 return this.rippleElement_;
192 * Sets the ripple X and Y coordinates.
193 * @param {number} newX the new X coordinate
194 * @param {number} newY the new Y coordinate
196 this.setRippleXY = function(newX, newY) {
202 * Sets the ripple styles.
203 * @param {boolean} start whether or not this is the start frame.
205 this.setRippleStyles = function(start) {
206 if (this.rippleElement_ !== null) {
210 var offset = 'translate(' + this.x_ + 'px, ' + this.y_ + 'px)';
213 scale = this.Constant_.INITIAL_SCALE;
214 size = this.Constant_.INITIAL_SIZE;
216 scale = this.Constant_.FINAL_SCALE;
217 size = this.rippleSize_ + 'px';
219 offset = 'translate(' + this.boundWidth / 2 + 'px, ' +
220 this.boundHeight / 2 + 'px)';
224 transformString = 'translate(-50%, -50%) ' + offset + scale;
226 this.rippleElement_.style.webkitTransform = transformString;
227 this.rippleElement_.style.msTransform = transformString;
228 this.rippleElement_.style.transform = transformString;
231 this.rippleElement_.classList.remove(this.CssClasses_.IS_ANIMATING);
233 this.rippleElement_.classList.add(this.CssClasses_.IS_ANIMATING);
239 * Handles an animation frame.
241 this.animFrameHandler = function() {
242 if (this.frameCount_-- > 0) {
243 window.requestAnimationFrame(this.animFrameHandler.bind(this));
245 this.setRippleStyles(false);
252 // The component registers itself. It can assume componentHandler is available
253 // in the global scope.
254 componentHandler.register({
255 constructor: MaterialRipple,
256 classAsString: 'MaterialRipple',
257 cssClass: 'mdl-js-ripple-effect',