From 602e9d872f9ff1bda7d6dfe75447ffa01aa7a05e Mon Sep 17 00:00:00 2001 From: Max Lynch Date: Sun, 19 Jul 2015 20:52:58 -0500 Subject: [PATCH] Ripple --- ionic/components/button/button.scss | 3 + ionic/components/material/ripple.scss | 2 +- ionic/components/material/ripple.ts | 174 +++++++++++++++++++++++++- 3 files changed, 177 insertions(+), 2 deletions(-) diff --git a/ionic/components/button/button.scss b/ionic/components/button/button.scss index b12dd13c35..fb066bd449 100644 --- a/ionic/components/button/button.scss +++ b/ionic/components/button/button.scss @@ -27,12 +27,15 @@ $button-fab-size: 56px; button, [button] { + position: relative; display: inline-flex; flex-shrink: 0; flex-flow: row nowrap; align-items: center; justify-content: center; + will-change: transform; + margin: $button-margin; line-height: 1; diff --git a/ionic/components/material/ripple.scss b/ionic/components/material/ripple.scss index 447e4afcd1..5759d4ccb3 100644 --- a/ionic/components/material/ripple.scss +++ b/ionic/components/material/ripple.scss @@ -20,7 +20,7 @@ $ripple-animation-curve: cubic-bezier(0, 0, 0.2, 1) !default; width: 50px; overflow: hidden; - &.is-animating { + &.md-ripple-animating { transition: transform 0.3s $ripple-animation-curve; width: 0.3s $ripple-animation-curve; height: 0.3s $ripple-animation-curve; diff --git a/ionic/components/material/ripple.ts b/ionic/components/material/ripple.ts index 9ea7dc8354..8fc8d8ae16 100644 --- a/ionic/components/material/ripple.ts +++ b/ionic/components/material/ripple.ts @@ -1,3 +1,9 @@ +/** + * Portions lovingly adapted from Material Design Lite + * Copyright Google, 2015, Licensed under the Apache 2 license. + * https://github.com/google/material-design-lite + */ + import {Directive, ElementRef} from 'angular2/angular2'; @Directive({ @@ -9,9 +15,175 @@ import {Directive, ElementRef} from 'angular2/angular2'; export class MaterialRipple { constructor(elementRef: ElementRef) { this.elementRef = elementRef; + this.element = this.elementRef.nativeElement; + console.log('Ripple', elementRef); + + var rippleContainer = document.createElement('span'); + rippleContainer.classList.add('md-ripple-container'); + 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 = ''; + + + //this.boundRippleBlurHandler = this.blurHandler.bind(this); + //this.rippleElement_.addEventListener('mouseup', this.boundRippleBlurHandler); + + this.elementRef.nativeElement.appendChild(rippleContainer); + + this._initRipple(); } - elementClicked(event) { + _initRipple() { + this.frameCount = 0; + this.rippleSize = 0; + this.x = 0; + this.y = 0; + + // 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; + + this.boundDownHandler = this.downHandler.bind(this); + this.element.addEventListener('mousedown', this.boundDownHandler); + this.element.addEventListener('touchstart', this.boundDownHandler); + + 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) { + this.rippleElement.classList.remove('is-visible'); + } + } + + + getFrameCount() { + return this.frameCount; + } + + setFrameCount(fC) { + this.frameCount = fC; + } + + getRippleElement() { + return this.rippleElement; + } + + setRippleXY(newX, newY) { + this.x = newX; + this.y = newY; + } + + 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'); + } + } + } + + animFrameHandler() { + if (this.frameCount-- > 0) { + window.requestAnimationFrame(this.animFrameHandler.bind(this)); + } else { + this.setRippleStyles(false); + } + } + + elementClicked(event) {} }