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:
Hans Krywalsky
2021-02-17 22:14:03 +01:00
committed by GitHub
parent 75458ac7fb
commit 84d86397a7
5 changed files with 79 additions and 33 deletions

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -112,4 +112,4 @@ ion-refresher-content .arrow-container {
.refresher-pulling-text, .refresher-refreshing-text {
display: none;
}
}
}

View File

@ -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);
}
});
};

View File

@ -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');