mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(vue): hide layout shift on ion-page components (#22254)
resolves #22052
This commit is contained in:
@@ -7,7 +7,7 @@ export const IonPage = defineComponent({
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
['class']: 'ion-page',
|
||||
['class']: 'ion-page ion-page-invisible',
|
||||
...attrs,
|
||||
ref: 'ionPage'
|
||||
},
|
||||
|
||||
@@ -125,24 +125,32 @@ export const IonRouterOutlet = defineComponent({
|
||||
progressAnimation: boolean,
|
||||
animationBuilder?: AnimationBuilder
|
||||
) => {
|
||||
if (skipTransition) {
|
||||
skipTransition = false;
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
if (skipTransition) {
|
||||
skipTransition = false;
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
if (enteringEl === leavingEl) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
if (enteringEl === leavingEl) {
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
enteringEl.classList.add('ion-page-invisible');
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(async () => {
|
||||
enteringEl.classList.add('ion-page-invisible');
|
||||
|
||||
return ionRouterOutlet.value.commit(enteringEl, leavingEl, {
|
||||
deepWait: true,
|
||||
duration: direction === undefined || direction === 'root' || direction === 'none' ? 0 : undefined,
|
||||
direction,
|
||||
showGoBack,
|
||||
progressAnimation,
|
||||
animationBuilder
|
||||
const result = await ionRouterOutlet.value.commit(enteringEl, leavingEl, {
|
||||
deepWait: true,
|
||||
duration: direction === undefined || direction === 'root' || direction === 'none' ? 0 : undefined,
|
||||
direction,
|
||||
showGoBack,
|
||||
progressAnimation,
|
||||
animationBuilder
|
||||
});
|
||||
|
||||
return resolve(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -152,6 +160,9 @@ export const IonRouterOutlet = defineComponent({
|
||||
|
||||
const enteringViewItem = viewStacks.findViewItemByRouteInfo(routeInfo, id);
|
||||
const leavingViewItem = viewStacks.findLeavingViewItemByRouteInfo(routeInfo, id);
|
||||
const enteringEl = enteringViewItem.ionPageElement;
|
||||
|
||||
if (enteringViewItem === leavingViewItem) return;
|
||||
|
||||
if (enteringViewItem === leavingViewItem) return;
|
||||
|
||||
@@ -159,7 +170,6 @@ export const IonRouterOutlet = defineComponent({
|
||||
|
||||
if (leavingViewItem) {
|
||||
let animationBuilder = routerAnimation;
|
||||
const enteringEl = enteringViewItem.ionPageElement;
|
||||
const leavingEl = leavingViewItem.ionPageElement;
|
||||
|
||||
fireLifecycle(leavingViewItem.vueComponent, LIFECYCLE_WILL_LEAVE);
|
||||
@@ -207,6 +217,14 @@ export const IonRouterOutlet = defineComponent({
|
||||
}
|
||||
|
||||
fireLifecycle(leavingViewItem.vueComponent, LIFECYCLE_DID_LEAVE);
|
||||
} else {
|
||||
/**
|
||||
* If there is no leaving element, just show
|
||||
* the entering element. Wrap it in an raf
|
||||
* in case ion-content's fullscreen callback
|
||||
* is running. Otherwise we'd have a flicker.
|
||||
*/
|
||||
requestAnimationFrame(() => enteringEl.classList.remove('ion-page-invisible'));
|
||||
}
|
||||
|
||||
fireLifecycle(enteringViewItem.vueComponent, LIFECYCLE_DID_ENTER);
|
||||
|
||||
20
packages/vue/test-app/package-lock.json
generated
20
packages/vue/test-app/package-lock.json
generated
@@ -1306,27 +1306,27 @@
|
||||
}
|
||||
},
|
||||
"@ionic/core": {
|
||||
"version": "0.6.0-dev.202010051922.f5d657c",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-0.6.0-dev.202010051922.f5d657c.tgz",
|
||||
"integrity": "sha512-sIwN9lV4Fep+vTYfeso+D4BL6APKiYuQ6J2H5+0pV0Azep59TwOompR/SB9U3AYsHKMD+MQj9i2cxYFXIPtd3Q==",
|
||||
"version": "0.6.0-dev.202010051957.440e9e7",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-0.6.0-dev.202010051957.440e9e7.tgz",
|
||||
"integrity": "sha512-WeMzpVFwNSK0M6tMAfYKr63OSB80x3JJbusF0bieT3Owh4c+zt25S0pfm6Tu8a11iYTn4vteBEYuKeu67Qv+rg==",
|
||||
"requires": {
|
||||
"ionicons": "^5.1.2",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"@ionic/vue": {
|
||||
"version": "0.6.0-dev.202010051922.f5d657c",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-0.6.0-dev.202010051922.f5d657c.tgz",
|
||||
"integrity": "sha512-HuMk3JUqt8DkVbgJYWgmmB+G5kALUoABl8n6fSFwFk8+vzqF0mJMoHcyspbL2hCGviTjHKQ9R2/WAbdq0Q/fNQ==",
|
||||
"version": "0.6.0-dev.202010051957.440e9e7",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-0.6.0-dev.202010051957.440e9e7.tgz",
|
||||
"integrity": "sha512-+tCCF1IC1dyz5mmItMtWo8z6UWbRwoS8UsPCX6dAdIGkALGYY1bjqcOrkj+GK4n8Cu8slpwSoesow/No2jCXog==",
|
||||
"requires": {
|
||||
"@ionic/core": "0.6.0-dev.202010051922.f5d657c",
|
||||
"@ionic/core": "0.6.0-dev.202010051957.440e9e7",
|
||||
"ionicons": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"@ionic/vue-router": {
|
||||
"version": "0.6.0-dev.202010051922.f5d657c",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/vue-router/-/vue-router-0.6.0-dev.202010051922.f5d657c.tgz",
|
||||
"integrity": "sha512-524eNmjFuEJh3OpuLBZXd1+k3R4/jruNxLt53Ro/Ux8SsI4FgPgm/Igv8SRjd1knvx9LFN+nlA8JTdHNB58SrA=="
|
||||
"version": "0.6.0-dev.202010051957.440e9e7",
|
||||
"resolved": "https://registry.npmjs.org/@ionic/vue-router/-/vue-router-0.6.0-dev.202010051957.440e9e7.tgz",
|
||||
"integrity": "sha512-4bRlHoLZdwsU4VwEP8WPudHkZy4uX/rqUvXg5Hn4YMxEGR/AfYm0TAyeQgh2I18GIcnu2Vjff3B46UkZj/5OjA=="
|
||||
},
|
||||
"@jest/console": {
|
||||
"version": "24.9.0",
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
"cypress": "node_modules/.bin/cypress run --headless --browser chrome"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ionic/vue": "0.6.0-dev.202010051922.f5d657c",
|
||||
"@ionic/vue-router": "0.6.0-dev.202010051922.f5d657c",
|
||||
"@ionic/vue": "0.6.0-dev.202010051957.440e9e7",
|
||||
"@ionic/vue-router": "0.6.0-dev.202010051957.440e9e7",
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^3.0.0-0",
|
||||
"vue-router": "^4.0.0-0"
|
||||
|
||||
43
packages/vue/test-app/src/components/ExploreContainer.vue
Normal file
43
packages/vue/test-app/src/components/ExploreContainer.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div id="container">
|
||||
<strong>{{ name }}</strong>
|
||||
<p>Explore <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
export default {
|
||||
name: 'ExploreContainer',
|
||||
props: {
|
||||
name: String
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#container {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
#container strong {
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
#container p {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
color: #8c8c8c;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#container a {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
@@ -46,6 +46,28 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () => import('@/views/NestedChildTwo.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/tabs/',
|
||||
component: () => import('@/views/Tabs.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirect: '/tabs/tab1'
|
||||
},
|
||||
{
|
||||
path: 'tab1',
|
||||
component: () => import('@/views/Tab1.vue')
|
||||
},
|
||||
{
|
||||
path: 'tab2',
|
||||
component: () => import('@/views/Tab2.vue')
|
||||
},
|
||||
{
|
||||
path: 'tab3',
|
||||
component: () => import('@/views/Tab3.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
<ion-item router-link="/nested" id="nested">
|
||||
<ion-label>Nested Router Outlet</ion-label>
|
||||
</ion-item>
|
||||
<ion-item router-link="/tabs" id="tabs">
|
||||
<ion-label>Tabs</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
</ion-content>
|
||||
|
||||
28
packages/vue/test-app/src/views/Tab1.vue
Normal file
28
packages/vue/test-app/src/views/Tab1.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Tab 1</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content :fullscreen="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">Tab 1</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ExploreContainer name="Tab 1 page" />
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/vue';
|
||||
import ExploreContainer from '@/components/ExploreContainer.vue';
|
||||
|
||||
export default {
|
||||
name: 'Tab1',
|
||||
components: { ExploreContainer, IonHeader, IonToolbar, IonTitle, IonContent, IonPage }
|
||||
}
|
||||
</script>
|
||||
28
packages/vue/test-app/src/views/Tab2.vue
Normal file
28
packages/vue/test-app/src/views/Tab2.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Tab 2</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content :fullscreen="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">Tab 2</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ExploreContainer name="Tab 2 page" />
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/vue';
|
||||
import ExploreContainer from '@/components/ExploreContainer.vue';
|
||||
|
||||
export default {
|
||||
name: 'Tab2',
|
||||
components: { ExploreContainer, IonHeader, IonToolbar, IonTitle, IonContent, IonPage }
|
||||
}
|
||||
</script>
|
||||
28
packages/vue/test-app/src/views/Tab3.vue
Normal file
28
packages/vue/test-app/src/views/Tab3.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Tab 3</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content :fullscreen="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">Tab 3</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ExploreContainer name="Tab 3 page" />
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/vue';
|
||||
import ExploreContainer from '@/components/ExploreContainer.vue';
|
||||
|
||||
export default {
|
||||
name: 'Tab3',
|
||||
components: { ExploreContainer, IonHeader, IonToolbar, IonTitle, IonContent, IonPage }
|
||||
}
|
||||
</script>
|
||||
41
packages/vue/test-app/src/views/Tabs.vue
Normal file
41
packages/vue/test-app/src/views/Tabs.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-content>
|
||||
<ion-tabs>
|
||||
<ion-tab-bar slot="bottom">
|
||||
<ion-tab-button tab="tab1" href="/tabs/tab1">
|
||||
<ion-icon :icon="triangle" />
|
||||
<ion-label>Tab 1</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="tab2" href="/tabs/tab2">
|
||||
<ion-icon :icon="ellipse" />
|
||||
<ion-label>Tab 2</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="tab3" href="/tabs/tab3">
|
||||
<ion-icon :icon="square" />
|
||||
<ion-label>Tab 3</ion-label>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
</ion-tabs>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { IonTabBar, IonTabButton, IonTabs, IonContent, IonLabel, IonIcon, IonPage } from '@ionic/vue';
|
||||
import { ellipse, square, triangle } from 'ionicons/icons';
|
||||
|
||||
export default {
|
||||
name: 'Tabs',
|
||||
components: { IonContent, IonLabel, IonTabs, IonTabBar, IonTabButton, IonIcon, IonPage },
|
||||
setup() {
|
||||
return {
|
||||
ellipse,
|
||||
square,
|
||||
triangle,
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user