Frontend Development 11 min read

Implementing Native‑App‑Like Page Transitions in a Vue.js Single‑Page Application

This article explains how to achieve native‑app‑style page transition effects in a Vue.js SPA by using the vue‑navigation component to listen to router events, applying absolute positioning, managing a custom page cache, recording scroll positions, optimizing animation performance, and handling inter‑page communication with an EventBus.

HomeTech
HomeTech
HomeTech
Implementing Native‑App‑Like Page Transitions in a Vue.js Single‑Page Application

Introduction

In the competitive landscape of lightweight applications, mini‑programs have become a focal point for super‑apps. AutoHome, for example, launched its own mini‑program that assembles components freely to provide comprehensive "content + data + service" packages. This article demonstrates how to implement native‑app‑like transition effects in a Vue.js single‑page application (SPA).

Transition Effect Implementation

In an SPA, page navigation is controlled by the router API. By listening to router events, we can determine whether to apply an entrance (slide‑in) or exit (slide‑out) animation.

this.$navigation.on('forward', (to, from) => {
  // set transitionName to 'slide-in'
});
this.$navigation.once('back', (to, from) => {
  // set transitionName to 'slide-out'
});
this.$navigation.on('replace', (to, from) => {
  // handle replace navigation
});
this.$navigation.off('refresh', (to, from) => {
  // no animation for refresh
});
this.$navigation.on('reset', () => {
  // reset transitionName
});

When a router change is detected, we set the transition component’s className to trigger the appropriate CSS animation. The transition component must be absolutely positioned, and the z-index is adjusted to ensure the animated component stays on top.

Page Cache Strategy

To avoid re‑rendering pages and to preserve component state, we replace the built‑in keep-alive with a custom <navigation> component that manages a cache based on route keys.

export default (keyName) => {
  return {
    name: 'navigation',
    abstract: true,
    data() { return { routes: Routes } },
    created() { this.cache = {} },
    destroyed() { /* destroy cached vnodes */ },
    render() {
      const vnode = this.$slots.default ? this.$slots.default[0] : null;
      if (vnode) {
        vnode.key = vnode.key || `__navigation-${key}-${vnode.key}`;
        vnode.data.keepAlive = true;
        return vnode;
      }
    }
  };
};

Scroll Position Recording

Because the page‑view component is absolutely positioned, Vue‑router’s default scroll‑position handling cannot be used. We record the scroll offset in router.beforeEach and restore it in the page‑view component’s activated hook.

router.beforeEach((to, from, next) => {
  const wrapScroll = document.querySelector('#contentScrollWrap');
  if (wrapScroll) {
    window._VUE_PAGE_VIEW_SCROLL_[from.query.VNK] = wrapScroll.scrollTop;
  }
  if (!window._VUE_ROUTER_INDEX_) {
    window._VUE_ROUTER_INDEX_ = to.query.VNK || from.query.VNK;
  }
  next();
});

export default {
  activated() {
    const scrollTop = window._VUE_PAGE_VIEW_SCROLL_[this.$route.query.VNK];
    if (scrollTop) {
      this.$refs.contentScrollWrap.scrollTop = scrollTop;
    }
  }
};

Transition Animation Performance Optimization

If an API request finishes within the 300 ms transition duration, rendering the DOM can interfere with the animation. The solution is to delay rendering until the animation completes and show a loading component in the meantime.

Page‑to‑Page Communication

Forward navigation (push/replace) passes parameters via the query string, while backward navigation uses an EventBus to notify the source page. The EventBus is installed globally.

// event-bus.js
import Vue from 'vue';
export const EventBus = new Vue();
export default {
  install(Vue) {
    if (this.installed && _Vue === Vue) return;
    this.installed = true;
    Vue.prototype.$eventBus = new Vue();
    _Vue = Vue;
  }
};

// main.js
import { EventBus } from '@/global';
Vue.use(EventBus);

Example usage in component A:

created() {
  this.$eventBus.$on(`updateSpec-${this.VNK}`, (item) => {
    this.specid = item.id;
    this.getSpecInfo();
  });
},
beforeDestroy() {
  this.$eventBus.$off(`updateSpec-${this.VNK}`);
},
handleClick() {
  this.$router.push({ name: 'SelectSpec', query: { seriesid: this.seriesid, onSell: 1, emitName: `updateSpec-${this.VNK}` } });
}

Component B emits the event and navigates back:

handleItemClick(item) {
  if (this.$route.query && this.$route.query.emitName) {
    this.$eventBus.$emit(this.$route.query.emitName, item);
  }
  this.$router.back();
}

Conclusion

The article covered implementing transition animations, page state caching, scroll‑position restoration, performance tuning, and inter‑page communication in a Vue.js SPA using vue‑navigation , vue‑router , keep‑alive , and an EventBus . Future work will continue to refine these techniques and share more project insights.

CacheSPAVue.jsnavigationEventBuspage transitionScroll Position
HomeTech
Written by

HomeTech

HomeTech tech sharing

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.