diff --git a/ionic/components/app/activator.ts b/ionic/components/app/activator.ts index ae76edd35c..9233f8d319 100644 --- a/ionic/components/app/activator.ts +++ b/ionic/components/app/activator.ts @@ -1,4 +1,5 @@ import {raf, pointerCoord, hasPointerMoved} from '../../util/dom'; +import * as ripple from '../material/ripple'; export class Activator { @@ -22,6 +23,8 @@ export class Activator { self.disableClickLimit = 2500; self.tapPolyfill = config.setting('tapPolyfill'); + self.mdRipple = config.setting('mdRipple'); + function bindDom(type, listener, useCapture) { document.addEventListener(type, listener, useCapture); @@ -237,15 +240,24 @@ export class Activator { raf(function(){ // activate all elements in the queue for (var key in self.queue) { - if (self.queue[key]) { - self.queue[key].classList.add(self.activatedClass); - self.active[key] = self.queue[key]; - } + self.activate(key, self.queue[key]); } self.queue = {}; }); } + activate(key, ele) { + if (ele) { + ele.classList.add(this.activatedClass); + + if (this.mdRipple) { + ripple.start(key, ele, this.start.x, this.start.y); + } + + this.active[key] = ele; + } + } + queueDeactivate() { const self = this; @@ -275,6 +287,9 @@ export class Activator { if (self.active[key]) { self.active[key].classList.remove(self.activatedClass); } + if (self.mdRipple) { + ripple.end(key); + } delete self.active[key]; } }); diff --git a/ionic/components/material/button.ts b/ionic/components/material/button.ts deleted file mode 100644 index 0a1587059f..0000000000 --- a/ionic/components/material/button.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {ElementRef, Directive} from 'angular2/angular2'; - -import {IonicConfig} from '../../config/config'; -import {MaterialRippleEffect} from '../material/ripple'; - -/** - * TODO - */ -@Directive({ - selector: 'button,[button]' -}) -export class MaterialButton { - /** - * TODO - * @param {ElementRef} elementRef TODO - * @param {IonicConfig} config TODO - */ - constructor(elementRef: ElementRef, config: IonicConfig) { - this.elementRef = elementRef; - this.config = config; - } - - onInit() { - if (this.config.setting('mdRipple')) { - //this.ripple = new MaterialRippleEffect(this); - } - } -} diff --git a/ionic/components/material/ripple.scss b/ionic/components/material/ripple.scss index 7125febc08..041a353fd6 100644 --- a/ionic/components/material/ripple.scss +++ b/ionic/components/material/ripple.scss @@ -1,32 +1,32 @@ -/** - * Portions lovingly adapted from Material Design Lite - * Copyright Google, 2015, Licensed under the Apache 2 license. - * https://github.com/google/material-design-lite - */ -$ripple-bg-color: white; -$ripple-animation-curve: cubic-bezier(0, 0, 0.2, 1) !default; -.md-ripple { - background: $ripple-bg-color; - border-radius: 50%; - height: 50px; - left: 0; - opacity: 0; - pointer-events: none; +// Material Design Ripple Effect +// -------------------------------------------------- + +$ripple-bg-color: white !default; +$ripple-animation-curve: cubic-bezier(0, 0, 0.2, 1) !default; + + +md-ripple { position: absolute; - top: 0; - transform: translate(-50%, -50%); - width: 50px; + z-index: -1; + display: block; + border-radius: 50%; + + background: $ripple-bg-color; + opacity: 0.3; + overflow: hidden; + pointer-events: none; - &.md-ripple-animating { - transition: transform 0.3s $ripple-animation-curve; - width: 0.3s $ripple-animation-curve; - height: 0.3s $ripple-animation-curve; - opacity: 0.6s $ripple-animation-curve; - } - - &.is-visible { - opacity: 0.3; - } + transform: scale(0.01) translateZ(0); + transition: transform 300ms $ripple-animation-curve, opacity 400ms $ripple-animation-curve; +} + +md-ripple.ripple-animate { + transform: scale(1) translateZ(0); +} + +md-ripple.ripple-animate-out { + transform: scale(1) translateZ(0); + opacity: 0; } diff --git a/ionic/components/material/ripple.ts b/ionic/components/material/ripple.ts index 84afe05e76..3fce46c267 100644 --- a/ionic/components/material/ripple.ts +++ b/ionic/components/material/ripple.ts @@ -1,223 +1,67 @@ -/** - * Lovingly Adapted from Material Design Lite - * Copyright Google, 2015, Licensed under the Apache 2 license. - * https://github.com/google/material-design-lite - */ - -import {ElementRef, forwardRef} from 'angular2/angular2'; - -import {MaterialButton} from './button'; - -/** - * TODO - */ -export class MaterialRippleEffect { - /** - * TODO - * @param {MaterialButton} button TODO - */ - constructor(button: MaterialButton) { - this.elementRef = button.elementRef; - this.element = this.elementRef.nativeElement; - - var rippleContainer = document.createElement('span'); - rippleContainer.classList.add('md-ripple-container'); - rippleContainer.setAttribute('aria-hidden', 'true'); - this.rippleElement = document.createElement('span'); - this.rippleElement.classList.add('md-ripple'); - rippleContainer.appendChild(this.rippleElement); - - this.recentering = false;//this.element.classList.contains(this.CssClasses_.RIPPLE_CENTER); - - this.INITIAL_SCALE = 'scale(0.0001, 0.0001)'; - this.INITIAL_SIZE = '1px'; - this.INITIAL_OPACITY = '0.4'; - this.FINAL_OPACITY = '0'; - this.FINAL_SCALE = ''; +import * as dom from '../../util/dom'; - //this.boundRippleBlurHandler = this.blurHandler.bind(this); - //this.rippleElement_.addEventListener('mouseup', this.boundRippleBlurHandler); +export function start(key, ele, pointerX, pointerY) { + // ensure this key is cleaned up + removeRipple(key); - this.elementRef.nativeElement.appendChild(rippleContainer); + // throttle how many ripples can happen quickly + if (lastRipple + 150 > Date.now()) return; + lastRipple = Date.now(); - this._initRipple(); - } + let r = ele.getBoundingClientRect(); + let rippleSize = Math.sqrt(r.width * r.width + r.height * r.height) * 2 + 2; - /** - * @private - * TODO - */ - _initRipple() { - this.frameCount = 0; - this.rippleSize = 0; - this.x = 0; - this.y = 0; + let rippleEle = activeElements[key] = document.createElement('md-ripple'); + rippleEle.style.width = rippleSize + 'px'; + rippleEle.style.height = rippleSize + 'px'; + rippleEle.style.marginTop = -(rippleSize / 2) + 'px'; + rippleEle.style.marginLeft = -(rippleSize / 2) + 'px'; + rippleEle.style.left = (pointerX - r.left) + 'px'; + rippleEle.style.top = (pointerY - r.top) + 'px'; - // Touch start produces a compat mouse down event, which would cause a - // second ripples. To avoid that, we use this property to ignore the first - // mouse down after a touch start. - this.ignoringMouseDown = false; + let centerX = (r.width / 2); + let hitX = (pointerX - r.left); + let distanceFromCenter = Math.abs(centerX - hitX); + let percent = 1 - (distanceFromCenter / centerX); + let duration = 300 + (300 * percent); + rippleEle.style.transitionDuration = rippleEle.style.webkitTransitionDuration = (duration + 'ms'); - this.boundDownHandler = this.downHandler.bind(this); - this.element.addEventListener('mousedown', this.boundDownHandler); - this.element.addEventListener('touchstart', this.boundDownHandler); + ele.classList.add('ripple-wave'); + ele.appendChild(rippleEle); - this.boundUpHandler = this.upHandler.bind(this); - this.element.addEventListener('mouseup', this.boundUpHandler); - this.element.addEventListener('mouseleave', this.boundUpHandler); - this.element.addEventListener('touchend', this.boundUpHandler); - this.element.addEventListener('blur', this.boundUpHandler); - } - - /** - * Handle mouse / finger down on element. - * @param {Event} event The event that fired. - * @private - */ - downHandler(event) { - 'use strict'; - - if (!this.rippleElement.style.width && !this.rippleElement.style.height) { - var rect = this.element.getBoundingClientRect(); - this.boundHeight = rect.height; - this.boundWidth = rect.width; - this.rippleSize = Math.sqrt(rect.width * rect.width + rect.height * rect.height) * 2 + 2; - this.rippleElement.style.width = this.rippleSize + 'px'; - this.rippleElement.style.height = this.rippleSize + 'px'; - } - - this.rippleElement.classList.add('is-visible'); - - if (event.type === 'mousedown' && this.ignoringMouseDown) { - this.ignoringMouseDown = false; - } else { - if (event.type === 'touchstart') { - this.ignoringMouseDown = true; - } - var frameCount = this.getFrameCount(); - if (frameCount > 0) { - return; - } - this.setFrameCount(1); - var bound = event.currentTarget.getBoundingClientRect(); - var x; - var y; - // Check if we are handling a keyboard click. - if (event.clientX === 0 && event.clientY === 0) { - x = Math.round(bound.width / 2); - y = Math.round(bound.height / 2); - } else { - var clientX = event.clientX ? event.clientX : event.touches[0].clientX; - var clientY = event.clientY ? event.clientY : event.touches[0].clientY; - x = Math.round(clientX - bound.left); - y = Math.round(clientY - bound.top); - } - this.setRippleXY(x, y); - this.setRippleStyles(true); - window.requestAnimationFrame(this.animFrameHandler.bind(this)); - } - } - - /** - * Handle mouse / finger up on element. - * @param {Event} event The event that fired. - * @private - */ - upHandler(event) { - 'use strict'; - - // Don't fire for the artificial "mouseup" generated by a double-click. - if (event && event.detail !== 2) { - setTimeout(function() { - this.rippleElement.classList.remove('is-visible'); - }.bind(this), 150); - } - } - - /** - * TODO - * @returns {TODO} TODO - */ - getFrameCount() { - return this.frameCount; - } - - /** - * TODO - * @param {TODO} fC TODO - */ - setFrameCount(fC) { - this.frameCount = fC; - } - - /** - * TODO - * @return {Element} TODO - */ - getRippleElement() { - return this.rippleElement; - } - - /** - * TODO - * @param {number} newX TODO - * @param {number} newY TODO - */ - setRippleXY(newX, newY) { - this.x = newX; - this.y = newY; - } - - /** - * TODO - * @param {TODO} start TODO - */ - setRippleStyles(start) { - if (this.rippleElement !== null) { - var transformString; - var scale; - var size; - var offset = 'translate(' + this.x + 'px, ' + this.y + 'px)'; - - if (start) { - scale = this.INITIAL_SCALE; - size = this.INITIAL_SIZE; - } else { - scale = this.FINAL_SCALE; - size = this.rippleSize + 'px'; - if (this.recentering) { - offset = 'translate(' + this.boundWidth / 2 + 'px, ' + this.boundHeight / 2 + 'px)'; - } - } - - transformString = 'translate(-50%, -50%) ' + offset + scale; - - this.rippleElement.style.webkitTransform = transformString; - this.rippleElement.style.msTransform = transformString; - this.rippleElement.style.transform = transformString; - - if (start) { - this.rippleElement.classList.remove('md-ripple-animating'); - } else { - this.rippleElement.classList.add('md-ripple-animating'); - } - } - } - - /** - * TODO - */ - animFrameHandler() { - if (this.frameCount-- > 0) { - window.requestAnimationFrame(this.animFrameHandler.bind(this)); - } else { - this.setRippleStyles(false); - } - } - - /** - * TODO - */ - elementClicked(event) {} + // kick off animaiton + setTimeout(function() { + rippleEle.classList.add('ripple-animate'); + }, 32); } + +export function end(key) { + let rippleEle = activeElements[key]; + + if (rippleEle) { + rippleEle.classList.add('ripple-animate-out'); + rippleEle.classList.remove('ripple-animate'); + + removeElements[key] = rippleEle; + setTimeout(function() { + removeRipple(key); + }, 500); + } + + delete activeElements[key]; +} + +function removeRipple(key) { + let rippleEle = removeElements[key]; + + if (rippleEle) { + rippleEle.parentNode.removeChild(rippleEle); + } + + delete removeElements[key]; +} + +let lastRipple = 0; +let activeElements = {}; +let removeElements = {};