mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-10 00:27:41 +08:00
refactor(refresher): add new ios 14 pull to refresh style (#22398)
resolves #22783 Co-authored-by: Liam DeBeasi <liamdebeasi@icloud.com>
This commit is contained in:
@ -30,22 +30,27 @@ ion-refresher.refresher-native {
|
||||
display: block;
|
||||
|
||||
z-index: 1;
|
||||
|
||||
|
||||
ion-spinner {
|
||||
@include margin(0, auto, 0, auto);
|
||||
}
|
||||
}
|
||||
|
||||
.refresher-native {
|
||||
.refresher-refreshing ion-spinner {
|
||||
--refreshing-rotation-duration: 2s;
|
||||
display: none;
|
||||
animation: var(--refreshing-rotation-duration) ease-out refresher-rotate forwards;
|
||||
}
|
||||
.refresher-refreshing {
|
||||
display: none;
|
||||
animation: 250ms linear refresher-pop forwards;
|
||||
}
|
||||
.refresher-native .refresher-refreshing ion-spinner {
|
||||
--refreshing-rotation-duration: 2s;
|
||||
display: none;
|
||||
animation: var(--refreshing-rotation-duration) ease-out refresher-rotate forwards;
|
||||
}
|
||||
.refresher-native .refresher-refreshing {
|
||||
display: none;
|
||||
animation: 250ms linear refresher-pop forwards;
|
||||
}
|
||||
|
||||
.refresher-native ion-spinner {
|
||||
width: #{$refresher-ios-native-spinner-width};
|
||||
height: #{$refresher-ios-native-spinner-height};
|
||||
|
||||
color: #{$refresher-ios-native-spinner-color};
|
||||
}
|
||||
|
||||
.refresher-native.refresher-refreshing,
|
||||
@ -67,6 +72,12 @@ ion-refresher.refresher-native {
|
||||
}
|
||||
}
|
||||
|
||||
.refresher-native.refresher-completing ion-refresher-content .refresher-refreshing-icon {
|
||||
transform: scale(0) rotate(180deg);
|
||||
|
||||
transition: 300ms;
|
||||
}
|
||||
|
||||
@keyframes refresher-pop {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
@ -88,4 +99,4 @@ ion-refresher.refresher-native {
|
||||
to {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,16 @@
|
||||
@import "../../themes/ionic.globals.ios";
|
||||
|
||||
/// @prop - Color of the refresher icon
|
||||
$refresher-ios-icon-color: $text-color !default;
|
||||
$refresher-ios-icon-color: $text-color !default;
|
||||
|
||||
/// @prop - Text color of the refresher content
|
||||
$refresher-ios-text-color: $text-color !default;
|
||||
$refresher-ios-text-color: $text-color !default;
|
||||
|
||||
/// @prop - Color of the native refresher spinner
|
||||
$refresher-ios-native-spinner-color: var(--ion-color-step-450, #747577) !default;
|
||||
|
||||
/// @prop - Width of the native refresher spinner
|
||||
$refresher-ios-native-spinner-width: 32px !default;
|
||||
|
||||
/// @prop - Height of the native refresher spinner
|
||||
$refresher-ios-native-spinner-height: 32px !default;
|
||||
|
||||
@ -112,4 +112,4 @@ ion-refresher-content .arrow-container {
|
||||
.refresher-pulling-text, .refresher-refreshing-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,17 @@ import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
|
||||
import { clamp, componentOnReady, getElementRoot, raf } from '../../utils/helpers';
|
||||
import { hapticImpact } from '../../utils/native/haptic';
|
||||
|
||||
import { createPullingAnimation, createSnapBackAnimation, getRefresherAnimationType, handleScrollWhilePulling, handleScrollWhileRefreshing, setSpinnerOpacity, shouldUseNativeRefresher, transitionEndAsync, translateElement } from './refresher.utils';
|
||||
import {
|
||||
createPullingAnimation,
|
||||
createSnapBackAnimation,
|
||||
getRefresherAnimationType,
|
||||
handleScrollWhilePulling,
|
||||
handleScrollWhileRefreshing,
|
||||
setSpinnerOpacity,
|
||||
shouldUseNativeRefresher,
|
||||
transitionEndAsync,
|
||||
translateElement
|
||||
} from './refresher.utils';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-refresher',
|
||||
@ -147,7 +157,7 @@ export class Refresher implements ComponentInterface {
|
||||
this.state = state;
|
||||
|
||||
if (getIonMode(this) === 'ios') {
|
||||
await translateElement(el, undefined);
|
||||
await translateElement(el, undefined, 300);
|
||||
} else {
|
||||
await transitionEndAsync(this.el.querySelector('.refresher-refreshing-icon'), 200);
|
||||
}
|
||||
@ -190,7 +200,6 @@ export class Refresher implements ComponentInterface {
|
||||
return;
|
||||
}
|
||||
|
||||
writeTask(() => setSpinnerOpacity(pullingSpinner, 0));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -206,11 +215,16 @@ export class Refresher implements ComponentInterface {
|
||||
}
|
||||
}
|
||||
|
||||
// delay showing the next tick marks until user has pulled 30px
|
||||
const opacity = clamp(0, Math.abs(scrollTop) / refresherHeight, 0.99);
|
||||
const pullAmount = this.progress = clamp(0, (Math.abs(scrollTop) - 30) / MAX_PULL, 1);
|
||||
const currentTickToShow = clamp(0, Math.floor(pullAmount * NUM_TICKS), NUM_TICKS - 1);
|
||||
const shouldShowRefreshingSpinner = this.state === RefresherState.Refreshing || currentTickToShow === NUM_TICKS - 1;
|
||||
/**
|
||||
* We want to delay the start of this gesture by ~30px
|
||||
* when initially pulling down so the refresher does not
|
||||
* overlap with the content. But when letting go of the
|
||||
* gesture before the refresher completes, we want the
|
||||
* refresher tick marks to quickly fade out.
|
||||
*/
|
||||
const offset = (this.didStart) ? 30 : 0;
|
||||
const pullAmount = this.progress = clamp(0, (Math.abs(scrollTop) - offset) / MAX_PULL, 1);
|
||||
const shouldShowRefreshingSpinner = this.state === RefresherState.Refreshing || pullAmount === 1;
|
||||
|
||||
if (shouldShowRefreshingSpinner) {
|
||||
if (this.pointerDown) {
|
||||
@ -232,7 +246,7 @@ export class Refresher implements ComponentInterface {
|
||||
}
|
||||
} else {
|
||||
this.state = RefresherState.Pulling;
|
||||
handleScrollWhilePulling(pullingSpinner, ticks, opacity, currentTickToShow);
|
||||
handleScrollWhilePulling(ticks, NUM_TICKS, pullAmount);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { writeTask } from '@stencil/core';
|
||||
|
||||
import { createAnimation } from '../../utils/animation/animation';
|
||||
import { componentOnReady } from '../../utils/helpers';
|
||||
import { clamp, componentOnReady } from '../../utils/helpers';
|
||||
import { isPlatform } from '../../utils/platform';
|
||||
|
||||
// MD Native Refresher
|
||||
@ -124,14 +124,26 @@ export const setSpinnerOpacity = (spinner: HTMLElement, opacity: number) => {
|
||||
};
|
||||
|
||||
export const handleScrollWhilePulling = (
|
||||
spinner: HTMLElement,
|
||||
ticks: NodeListOf<SVGElement>,
|
||||
opacity: number,
|
||||
currentTickToShow: number
|
||||
numTicks: number,
|
||||
pullAmount: number
|
||||
) => {
|
||||
const max = 1;
|
||||
writeTask(() => {
|
||||
setSpinnerOpacity(spinner, opacity);
|
||||
ticks.forEach((el, i) => el.style.setProperty('opacity', (i <= currentTickToShow) ? '0.99' : '0'));
|
||||
ticks.forEach((el, i) => {
|
||||
/**
|
||||
* Compute the opacity of each tick
|
||||
* mark as a percentage of the pullAmount
|
||||
* offset by max / numTicks so
|
||||
* the tick marks are shown staggered.
|
||||
*/
|
||||
const min = i * (max / numTicks);
|
||||
const range = max - min;
|
||||
const start = pullAmount - min;
|
||||
const progression = clamp(0, start / range, 1);
|
||||
|
||||
el.style.setProperty('opacity', progression.toString());
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -146,13 +158,13 @@ export const handleScrollWhileRefreshing = (
|
||||
});
|
||||
};
|
||||
|
||||
export const translateElement = (el?: HTMLElement, value?: string) => {
|
||||
export const translateElement = (el?: HTMLElement, value?: string, duration = 200) => {
|
||||
if (!el) { return Promise.resolve(); }
|
||||
|
||||
const trans = transitionEndAsync(el, 200);
|
||||
const trans = transitionEndAsync(el, duration);
|
||||
|
||||
writeTask(() => {
|
||||
el.style.setProperty('transition', '0.2s all ease-out');
|
||||
el.style.setProperty('transition', `${duration}ms all ease-out`);
|
||||
|
||||
if (value === undefined) {
|
||||
el.style.removeProperty('transform');
|
||||
|
||||
Reference in New Issue
Block a user