mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 19:21:34 +08:00
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:
2
core/src/components.d.ts
vendored
2
core/src/components.d.ts
vendored
@ -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 {}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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};
|
||||||
|
@ -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};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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};
|
||||||
|
@ -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};
|
||||||
|
@ -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>`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
Reference in New Issue
Block a user