mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +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
|
||||
*/
|
||||
'addRipple': (pageX: number, pageY: number) => void;
|
||||
'addRipple': (pageX: number, pageY: number) => Promise<() => void>;
|
||||
}
|
||||
interface IonRippleEffectAttributes extends StencilHTMLAttributes {}
|
||||
|
||||
|
||||
@@ -60,10 +60,6 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.action-sheet-button.activated {
|
||||
background: $action-sheet-md-button-background-activated;
|
||||
}
|
||||
|
||||
.action-sheet-icon {
|
||||
@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);
|
||||
|
||||
@@ -241,6 +241,7 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
|
||||
{b.icon && <ion-icon icon={b.icon} lazy={false} class="action-sheet-icon" />}
|
||||
{b.text}
|
||||
</span>
|
||||
{this.mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -290,10 +290,6 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.alert-button.activated {
|
||||
background-color: $alert-md-button-background-color-activated;
|
||||
}
|
||||
|
||||
.alert-button-inner {
|
||||
justify-content: $alert-md-button-group-justify-content;
|
||||
}
|
||||
|
||||
@@ -369,7 +369,6 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
{i.label}
|
||||
</div>
|
||||
</div>
|
||||
{this.mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -429,6 +428,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
<span class="alert-button-inner">
|
||||
{button.text}
|
||||
</span>
|
||||
{this.mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
|
||||
// iOS Solid Button
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.button-solid) {
|
||||
--background-activated: #{ion-color(primary, shade)};
|
||||
}
|
||||
|
||||
@media (any-hover: hover) {
|
||||
:host(.button-solid:hover) {
|
||||
--opacity: #{$button-ios-opacity-hover};
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.button-solid) {
|
||||
--background-activated: var(--background);
|
||||
--box-shadow: #{$button-md-box-shadow};
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
// Default Solid Color
|
||||
:host(.button-solid) {
|
||||
--background: #{ion-color(primary, base)};
|
||||
--background-activated: #{ion-color(primary, shade)};
|
||||
--background-focused: #{ion-color(primary, shade)};
|
||||
--color: #{ion-color(primary, contrast)};
|
||||
--color-activated: #{ion-color(primary, contrast)};
|
||||
@@ -87,8 +86,7 @@
|
||||
}
|
||||
|
||||
// Focused/Activated Solid Button with Color
|
||||
:host(.button-solid.ion-color.focused) .button-native,
|
||||
:host(.button-solid.ion-color.activated) .button-native {
|
||||
:host(.button-solid.ion-color.focused) .button-native {
|
||||
background: #{current-color(shade)};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
:host {
|
||||
--background: #{$fab-md-background-color};
|
||||
--background-activated: #{$fab-md-background-color-activated};
|
||||
--background-activated: var(--background);
|
||||
--background-focused: var(--background-activated);
|
||||
--color: #{$fab-md-text-color};
|
||||
--color-activated: #{$fab-md-text-color};
|
||||
@@ -45,4 +45,4 @@
|
||||
|
||||
:host(.fab-button-in-list) ::slotted(ion-icon) {
|
||||
font-size: $fab-md-list-button-icon-font-size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,11 @@
|
||||
:host {
|
||||
--min-height: #{$item-md-min-height};
|
||||
--background: var(--ion-item-background, transparent);
|
||||
--background-activated: #{$item-md-background-activated};
|
||||
--background-activated: var(--background);
|
||||
--border-color: #{$item-md-border-bottom-color};
|
||||
--color: #{$item-md-color};
|
||||
--transition: background-color 300ms cubic-bezier(.4, 0, .2, 1);
|
||||
--padding-start: #{$item-md-padding-start};
|
||||
--background: #{$item-md-background};
|
||||
--background-activated: #{$item-md-background-activated};
|
||||
--color: #{$item-md-color};
|
||||
--border-color: #{$item-md-border-bottom-color};
|
||||
--inner-padding-end: #{$item-md-padding-end};
|
||||
|
||||
@@ -8,7 +8,7 @@ The ripple effect component adds the [Material Design ink ripple interaction eff
|
||||
|
||||
## Methods
|
||||
|
||||
### `addRipple(pageX: number, pageY: number) => void`
|
||||
### `addRipple(pageX: number, pageY: number) => Promise<() => void>`
|
||||
|
||||
Adds the ripple effect to the parent element
|
||||
|
||||
@@ -21,7 +21,7 @@ Adds the ripple effect to the parent element
|
||||
|
||||
#### Returns
|
||||
|
||||
Type: `void`
|
||||
Type: `Promise<() => void>`
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
// Material Design Ripple Effect
|
||||
// --------------------------------------------------
|
||||
|
||||
$scale-duration: 225ms;
|
||||
$fade-in-duration: 75ms;
|
||||
$fade-out-duration: 150ms;
|
||||
$opacity-duration: $fade-in-duration + $fade-out-duration;
|
||||
|
||||
:host {
|
||||
@include position(0, 0, 0, 0);
|
||||
|
||||
@@ -23,23 +28,51 @@
|
||||
|
||||
contain: strict;
|
||||
opacity: 0;
|
||||
animation-name: rippleAnimation;
|
||||
animation-duration: 200ms;
|
||||
animation-timing-function: ease-out;
|
||||
animation:
|
||||
$scale-duration rippleAnimation forwards,
|
||||
$fade-in-duration fadeInAnimation forwards;
|
||||
|
||||
will-change: transform, opacity;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes rippleAnimation {
|
||||
0% {
|
||||
transform: scale(.1);
|
||||
.fade-out {
|
||||
transform: translate(var(--translate-end)) scale(var(--final-scale, 1));
|
||||
animation: $fade-out-duration fadeOutAnimation forwards;
|
||||
}
|
||||
|
||||
opacity: .2;
|
||||
@keyframes rippleAnimation {
|
||||
from {
|
||||
animation-timing-function: cubic-bezier(.4, 0, .2, 1);
|
||||
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
to {
|
||||
transform: translate(var(--translate-end)) scale(var(--final-scale, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInAnimation {
|
||||
from {
|
||||
animation-timing-function: linear;
|
||||
|
||||
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 { Config } from '../../interface';
|
||||
import { rIC } from '../../utils/helpers';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-ripple-effect',
|
||||
@@ -20,42 +19,61 @@ export class RippleEffect implements ComponentInterface {
|
||||
* Adds the ripple effect to the parent element
|
||||
*/
|
||||
@Method()
|
||||
addRipple(pageX: number, pageY: number) {
|
||||
async addRipple(pageX: number, pageY: number) {
|
||||
if (this.config.getBoolean('animated', true)) {
|
||||
rIC(() => this.prepareRipple(pageX, pageY));
|
||||
return this.prepareRipple(pageX, pageY);
|
||||
}
|
||||
return () => { return; };
|
||||
}
|
||||
|
||||
private prepareRipple(pageX: number, pageY: number) {
|
||||
let x: number;
|
||||
let y: number;
|
||||
let size: number;
|
||||
return new Promise<() => void>(resolve => {
|
||||
this.queue.read(() => {
|
||||
const rect = this.el.getBoundingClientRect();
|
||||
const width = rect.width;
|
||||
const height = rect.height;
|
||||
const hypotenuse = Math.sqrt(width * width + height * height);
|
||||
const maxRadius = hypotenuse + PADDING;
|
||||
const maxDim = Math.max(height, width);
|
||||
const posX = pageX - rect.left;
|
||||
const posY = pageY - rect.top;
|
||||
|
||||
this.queue.read(() => {
|
||||
const rect = this.el.getBoundingClientRect();
|
||||
const width = rect.width;
|
||||
const height = rect.height;
|
||||
size = Math.min(Math.sqrt(width * width + height * height) * 2, MAX_RIPPLE_DIAMETER);
|
||||
x = pageX - rect.left - (size * 0.5);
|
||||
y = pageY - rect.top - (size * 0.5);
|
||||
});
|
||||
this.queue.write(() => {
|
||||
const div = this.win.document.createElement('div');
|
||||
div.classList.add('ripple-effect');
|
||||
const style = div.style;
|
||||
const duration = Math.max(RIPPLE_FACTOR * Math.sqrt(size), MIN_RIPPLE_DURATION);
|
||||
style.top = y + 'px';
|
||||
style.left = x + 'px';
|
||||
style.width = style.height = size + 'px';
|
||||
style.animationDuration = duration + 'ms';
|
||||
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;
|
||||
|
||||
const container = this.el.shadowRoot || this.el;
|
||||
container.appendChild(div);
|
||||
setTimeout(() => div.remove(), duration + 50);
|
||||
this.queue.write(() => {
|
||||
const div = this.win.document.createElement('div');
|
||||
div.classList.add('ripple-effect');
|
||||
const style = div.style;
|
||||
style.top = y + 'px';
|
||||
style.left = x + 'px';
|
||||
style.width = style.height = initialSize + 'px';
|
||||
style.setProperty('--final-scale', `${finalScale}`);
|
||||
style.setProperty('--translate-end', `${moveX}px, ${moveY}px`);
|
||||
|
||||
const container = this.el.shadowRoot || this.el;
|
||||
container.appendChild(div);
|
||||
setTimeout(() => {
|
||||
resolve(() => {
|
||||
removeRipple(div);
|
||||
});
|
||||
}, 225 + 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const RIPPLE_FACTOR = 35;
|
||||
const MIN_RIPPLE_DURATION = 260;
|
||||
const MAX_RIPPLE_DIAMETER = 550;
|
||||
function removeRipple(ripple: HTMLElement) {
|
||||
ripple.classList.add('fade-out');
|
||||
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 activatableEle: HTMLElement | undefined;
|
||||
let activeRipple: Promise<() => void> | undefined;
|
||||
let activeDefer: any;
|
||||
|
||||
const clearDefers = new WeakMap<HTMLElement, any>();
|
||||
@@ -116,11 +117,14 @@ export function startTapClick(doc: Document) {
|
||||
|
||||
const rippleEffect = getRippleEffect(el);
|
||||
if (rippleEffect && rippleEffect.addRipple) {
|
||||
rippleEffect.addRipple(x, y);
|
||||
activeRipple = rippleEffect.addRipple(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
function removeActivated(smooth: boolean) {
|
||||
if (activeRipple !== undefined) {
|
||||
activeRipple.then(remove => remove());
|
||||
}
|
||||
const active = activatableEle;
|
||||
if (!active) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user