fix(ripple-effect): follow MD spec (#16330)

* fix(ripple-effect): follow md spec

* add box-shadow back

* add ripple effect to alert and action-sheet
This commit is contained in:
Manu MA
2018-11-15 16:08:39 +01:00
committed by GitHub
parent 02a266cc85
commit 6d5944613a
14 changed files with 109 additions and 59 deletions

View File

@ -3541,7 +3541,7 @@ export namespace Components {
/** /**
* Adds the ripple effect to the parent element * Adds the ripple effect to the parent element
*/ */
'addRipple': (pageX: number, pageY: number) => void; 'addRipple': (pageX: number, pageY: number) => Promise<() => void>;
} }
interface IonRippleEffectAttributes extends StencilHTMLAttributes {} interface IonRippleEffectAttributes extends StencilHTMLAttributes {}

View File

@ -60,10 +60,6 @@
overflow: hidden; overflow: hidden;
} }
.action-sheet-button.activated {
background: $action-sheet-md-button-background-activated;
}
.action-sheet-icon { .action-sheet-icon {
@include padding(null, null, 4px, null); @include padding(null, null, 4px, null);
@include margin($action-sheet-md-icon-margin-top, $action-sheet-md-icon-margin-end, $action-sheet-md-icon-margin-bottom, $action-sheet-md-icon-margin-start); @include margin($action-sheet-md-icon-margin-top, $action-sheet-md-icon-margin-end, $action-sheet-md-icon-margin-bottom, $action-sheet-md-icon-margin-start);

View File

@ -241,6 +241,7 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
{b.icon && <ion-icon icon={b.icon} lazy={false} class="action-sheet-icon" />} {b.icon && <ion-icon icon={b.icon} lazy={false} class="action-sheet-icon" />}
{b.text} {b.text}
</span> </span>
{this.mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button> </button>
)} )}
</div> </div>

View File

@ -290,10 +290,6 @@
overflow: hidden; overflow: hidden;
} }
.alert-button.activated {
background-color: $alert-md-button-background-color-activated;
}
.alert-button-inner { .alert-button-inner {
justify-content: $alert-md-button-group-justify-content; justify-content: $alert-md-button-group-justify-content;
} }

View File

@ -369,7 +369,6 @@ export class Alert implements ComponentInterface, OverlayInterface {
{i.label} {i.label}
</div> </div>
</div> </div>
{this.mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button> </button>
))} ))}
</div> </div>
@ -429,6 +428,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
<span class="alert-button-inner"> <span class="alert-button-inner">
{button.text} {button.text}
</span> </span>
{this.mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button> </button>
)} )}
</div> </div>

View File

@ -26,6 +26,11 @@
// iOS Solid Button // iOS Solid Button
// -------------------------------------------------- // --------------------------------------------------
:host(.button-solid) {
--background-activated: #{ion-color(primary, shade)};
}
@media (any-hover: hover) { @media (any-hover: hover) {
:host(.button-solid:hover) { :host(.button-solid:hover) {
--opacity: #{$button-ios-opacity-hover}; --opacity: #{$button-ios-opacity-hover};

View File

@ -31,6 +31,7 @@
// -------------------------------------------------- // --------------------------------------------------
:host(.button-solid) { :host(.button-solid) {
--background-activated: var(--background);
--box-shadow: #{$button-md-box-shadow}; --box-shadow: #{$button-md-box-shadow};
} }

View File

@ -73,7 +73,6 @@
// Default Solid Color // Default Solid Color
:host(.button-solid) { :host(.button-solid) {
--background: #{ion-color(primary, base)}; --background: #{ion-color(primary, base)};
--background-activated: #{ion-color(primary, shade)};
--background-focused: #{ion-color(primary, shade)}; --background-focused: #{ion-color(primary, shade)};
--color: #{ion-color(primary, contrast)}; --color: #{ion-color(primary, contrast)};
--color-activated: #{ion-color(primary, contrast)}; --color-activated: #{ion-color(primary, contrast)};
@ -87,8 +86,7 @@
} }
// Focused/Activated Solid Button with Color // Focused/Activated Solid Button with Color
:host(.button-solid.ion-color.focused) .button-native, :host(.button-solid.ion-color.focused) .button-native {
:host(.button-solid.ion-color.activated) .button-native {
background: #{current-color(shade)}; background: #{current-color(shade)};
} }

View File

@ -6,7 +6,7 @@
:host { :host {
--background: #{$fab-md-background-color}; --background: #{$fab-md-background-color};
--background-activated: #{$fab-md-background-color-activated}; --background-activated: var(--background);
--background-focused: var(--background-activated); --background-focused: var(--background-activated);
--color: #{$fab-md-text-color}; --color: #{$fab-md-text-color};
--color-activated: #{$fab-md-text-color}; --color-activated: #{$fab-md-text-color};

View File

@ -8,13 +8,11 @@
:host { :host {
--min-height: #{$item-md-min-height}; --min-height: #{$item-md-min-height};
--background: var(--ion-item-background, transparent); --background: var(--ion-item-background, transparent);
--background-activated: #{$item-md-background-activated}; --background-activated: var(--background);
--border-color: #{$item-md-border-bottom-color}; --border-color: #{$item-md-border-bottom-color};
--color: #{$item-md-color}; --color: #{$item-md-color};
--transition: background-color 300ms cubic-bezier(.4, 0, .2, 1); --transition: background-color 300ms cubic-bezier(.4, 0, .2, 1);
--padding-start: #{$item-md-padding-start}; --padding-start: #{$item-md-padding-start};
--background: #{$item-md-background};
--background-activated: #{$item-md-background-activated};
--color: #{$item-md-color}; --color: #{$item-md-color};
--border-color: #{$item-md-border-bottom-color}; --border-color: #{$item-md-border-bottom-color};
--inner-padding-end: #{$item-md-padding-end}; --inner-padding-end: #{$item-md-padding-end};

View File

@ -8,7 +8,7 @@ The ripple effect component adds the [Material Design ink ripple interaction eff
## Methods ## Methods
### `addRipple(pageX: number, pageY: number) => void` ### `addRipple(pageX: number, pageY: number) => Promise<() => void>`
Adds the ripple effect to the parent element Adds the ripple effect to the parent element
@ -21,7 +21,7 @@ Adds the ripple effect to the parent element
#### Returns #### Returns
Type: `void` Type: `Promise<() => void>`

View File

@ -4,6 +4,11 @@
// Material Design Ripple Effect // Material Design Ripple Effect
// -------------------------------------------------- // --------------------------------------------------
$scale-duration: 225ms;
$fade-in-duration: 75ms;
$fade-out-duration: 150ms;
$opacity-duration: $fade-in-duration + $fade-out-duration;
:host { :host {
@include position(0, 0, 0, 0); @include position(0, 0, 0, 0);
@ -23,23 +28,51 @@
contain: strict; contain: strict;
opacity: 0; opacity: 0;
animation-name: rippleAnimation; animation:
animation-duration: 200ms; $scale-duration rippleAnimation forwards,
animation-timing-function: ease-out; $fade-in-duration fadeInAnimation forwards;
will-change: transform, opacity; will-change: transform, opacity;
pointer-events: none; pointer-events: none;
} }
@keyframes rippleAnimation { .fade-out {
0% { transform: translate(var(--translate-end)) scale(var(--final-scale, 1));
transform: scale(.1); animation: $fade-out-duration fadeOutAnimation forwards;
opacity: .2;
} }
100% { @keyframes rippleAnimation {
from {
animation-timing-function: cubic-bezier(.4, 0, .2, 1);
transform: scale(1); transform: scale(1);
}
to {
transform: translate(var(--translate-end)) scale(var(--final-scale, 1));
}
}
@keyframes fadeInAnimation {
from {
animation-timing-function: linear;
opacity: 0; opacity: 0;
} }
to {
opacity: 0.16;
}
}
@keyframes fadeOutAnimation {
from {
animation-timing-function: linear;
opacity: 0.16;
}
to {
opacity: 0;
}
} }

View File

@ -1,7 +1,6 @@
import { Component, ComponentInterface, Element, Method, Prop, QueueApi } from '@stencil/core'; import { Component, ComponentInterface, Element, Method, Prop, QueueApi } from '@stencil/core';
import { Config } from '../../interface'; import { Config } from '../../interface';
import { rIC } from '../../utils/helpers';
@Component({ @Component({
tag: 'ion-ripple-effect', tag: 'ion-ripple-effect',
@ -20,42 +19,61 @@ export class RippleEffect implements ComponentInterface {
* Adds the ripple effect to the parent element * Adds the ripple effect to the parent element
*/ */
@Method() @Method()
addRipple(pageX: number, pageY: number) { async addRipple(pageX: number, pageY: number) {
if (this.config.getBoolean('animated', true)) { if (this.config.getBoolean('animated', true)) {
rIC(() => this.prepareRipple(pageX, pageY)); return this.prepareRipple(pageX, pageY);
} }
return () => { return; };
} }
private prepareRipple(pageX: number, pageY: number) { private prepareRipple(pageX: number, pageY: number) {
let x: number; return new Promise<() => void>(resolve => {
let y: number;
let size: number;
this.queue.read(() => { this.queue.read(() => {
const rect = this.el.getBoundingClientRect(); const rect = this.el.getBoundingClientRect();
const width = rect.width; const width = rect.width;
const height = rect.height; const height = rect.height;
size = Math.min(Math.sqrt(width * width + height * height) * 2, MAX_RIPPLE_DIAMETER); const hypotenuse = Math.sqrt(width * width + height * height);
x = pageX - rect.left - (size * 0.5); const maxRadius = hypotenuse + PADDING;
y = pageY - rect.top - (size * 0.5); const maxDim = Math.max(height, width);
}); const posX = pageX - rect.left;
const posY = pageY - rect.top;
const initialSize = Math.floor(maxDim * INITIAL_ORIGIN_SCALE);
const finalScale = maxRadius / initialSize;
const x = posX - initialSize * 0.5;
const y = posY - initialSize * 0.5;
const moveX = width * 0.5 - posX;
const moveY = height * 0.5 - posY;
this.queue.write(() => { this.queue.write(() => {
const div = this.win.document.createElement('div'); const div = this.win.document.createElement('div');
div.classList.add('ripple-effect'); div.classList.add('ripple-effect');
const style = div.style; const style = div.style;
const duration = Math.max(RIPPLE_FACTOR * Math.sqrt(size), MIN_RIPPLE_DURATION);
style.top = y + 'px'; style.top = y + 'px';
style.left = x + 'px'; style.left = x + 'px';
style.width = style.height = size + 'px'; style.width = style.height = initialSize + 'px';
style.animationDuration = duration + 'ms'; style.setProperty('--final-scale', `${finalScale}`);
style.setProperty('--translate-end', `${moveX}px, ${moveY}px`);
const container = this.el.shadowRoot || this.el; const container = this.el.shadowRoot || this.el;
container.appendChild(div); container.appendChild(div);
setTimeout(() => div.remove(), duration + 50); setTimeout(() => {
resolve(() => {
removeRipple(div);
});
}, 225 + 100);
});
});
}); });
} }
} }
const RIPPLE_FACTOR = 35; function removeRipple(ripple: HTMLElement) {
const MIN_RIPPLE_DURATION = 260; ripple.classList.add('fade-out');
const MAX_RIPPLE_DIAMETER = 550; setTimeout(() => {
ripple.remove();
}, 200);
}
const PADDING = 10;
const INITIAL_ORIGIN_SCALE = 0.5;

View File

@ -8,6 +8,7 @@ export function startTapClick(doc: Document) {
let scrolling = false; let scrolling = false;
let activatableEle: HTMLElement | undefined; let activatableEle: HTMLElement | undefined;
let activeRipple: Promise<() => void> | undefined;
let activeDefer: any; let activeDefer: any;
const clearDefers = new WeakMap<HTMLElement, any>(); const clearDefers = new WeakMap<HTMLElement, any>();
@ -116,11 +117,14 @@ export function startTapClick(doc: Document) {
const rippleEffect = getRippleEffect(el); const rippleEffect = getRippleEffect(el);
if (rippleEffect && rippleEffect.addRipple) { if (rippleEffect && rippleEffect.addRipple) {
rippleEffect.addRipple(x, y); activeRipple = rippleEffect.addRipple(x, y);
} }
} }
function removeActivated(smooth: boolean) { function removeActivated(smooth: boolean) {
if (activeRipple !== undefined) {
activeRipple.then(remove => remove());
}
const active = activatableEle; const active = activatableEle;
if (!active) { if (!active) {
return; return;