🛰 Shared component across routes with animations

Related tags

Vue.js vue-starport
Overview


Vue Starport

Shared Vue component across routes with animations

NPM version

Live Demo



Why?

It's quite common you might have a same component used in different routes (pages) with a bit different sizes and positions. Sometimes you might want to animate them when user navigates between routes to provide a smooth UX. While such animation is common to be seen in native apps, it's could be a bit challenging to do it in Web.

Vue's component structure is presented as a tree, and the child components are in different branches with their own instances. Meaning when users navigate between routes, the components are not shared across routes.

By that means you can't directly animate the changes because they are in two different instances. The good news is, there is a technique called FLIP to enumerate the transitions between them.

However, FLIP only solves the problem of transitions, the components are still not the same. During the navigation, the internal state of the component will lost.

Thus I started this new approach Starport to experiment with a better solution to fit this requirement.

How?

So since we can't share the components across different branches in the component tree, we could actually hoist them to the root so they become independent from the routes.

To allow each page to still have control of the components, we introduced a Proxy component to present the expected size and position of that component. The proxy will pass the props and position infomation to the actual component and let it "fly over" the proxy with animations.

When the transition ends and it arrived to the expected position, it will then "land down" to the actual component using the <Teleport/> component.

With this "landing" mechanism, the DOM tree will be preserved as what you will have with the original tree structure. When navigating to another route, the component then will "lift off" back to the floating state, "fly" to the new proxy's position and "land" again.

This is very similar to Terran's Buildings in StarCraft (able to leave the ground and fly to new locations). It's also the inspiration source of the project name Starport.

Install

⚗️ Experimental

npm i vue-starport

Vue Starport only works for Vue 3

Usage

At root App.vue, add <StarportCarrier> component from vue-starport at the end of the dom. <StarportCarrier> will be the place to store all the flying components.

<script setup>
import { StarportCarrier } from 'vue-starport'
</script>

<template>
  <RouterView />
  <StarportCarrier /> <!-- here -->
</template>

In routes, wrap the component with the <Starport> component.

<!-- PageA.vue -->
<script setup>
import { Starport } from 'vue-starport'
</script>

<template>
  <div>
    <!-- ... -->
    <Starport port="my-id" style="height:400px"> 
      <MyComponent :prop="value"/>
    </Starport>
  </div>
</template>

On the other page, we do the same thing with the same port id to identify the instance.

<!-- PageB.vue -->
<script setup>
import { Starport } from 'vue-starport'
</script>

<template>
  <div>
    <!-- ... -->
    <Starport port="my-id" style="height:600px">
      <MyComponent :prop="value"/>
    </Starport>
  </div>
</template>

Note that you might need to apply some styles to <Starport> to make it have a defined size indicating the area for the "floating starcraft" to land.

Checkout the Playground for more examples.

Register Components Globally

// main.ts
import StarportPlugin from 'vue-starport'

app.use(StarportPlugin())

And then you can use Starport and StarportCarrier components without importing.

Keep Alive

By default, when navigating to a page without a corresponding <Starport> proxy to land, the component will be destroyed. If you want to keep the component alive even when it's not presented in the current route, you can set keepAlive to true for that specific instance.

<Starport keep-alive port="my-id">
  <MyComponent />
</Starport>

To configure it globally, you can pass options to the plugin:

// main.ts
import StarportPlugin from 'vue-starport'

app.use(StarportPlugin({ keepAlive: true }))

Special Thanks

Thanks to @hangsman who helped to provide the initial solution of proper teleport the element and made this idea valid. Also thanks to the viewers of my live-streaming on Bilibli, those who spend time with me to working on this idea and provided useful feedback during the live.

You can check the recordings of my live-streams (in Chinese), where I wrote this project from scratch.

你可以在哔哩哔哩观看我实现此项目的 直播录像

Sponsors

License

MIT License © 2022 Anthony Fu

Comments
  • Auto import of Starport and StarportCarrier with Vitesse

    Auto import of Starport and StarportCarrier with Vitesse

    Hi,

    I can get Starport to work with Vitesse if I do a manual import of Startport in each page and StarportCarrier in App.vue, however I'd like to use the unplugin-auto-import way, and I tried adding the following to AutoImport in vite.config.ts like so:

    {
              'vue-starport': [
                'Starport',
                'StarportCarrier',
              ],
            },
    

    But that didn't seem to work, so I tried the global way in main.ts like so:

    import StarportPlugin from 'vue-starport'
    app.use(StarportPlugin({ keepAlive: true }))
    

    But again that didn't work.

    And I noticed that in the Starport playground demo it just has the following in main.ts:

    import Starport from 'vue-starport'
    ...
    app.use(Starport({ keepAlive: true }))
    

    And I tried this using this in ViteSSG like so:

    import StarportPlugin from 'vue-starport'
    ...
    (ctx) => {
        ctx.app.use(StarportPlugin({ keepAlive: true }))
    ...
    

    But that also didn't seem to work.

    Am I missing a step with the auto import configuration somewhere? I wold rather auto import than manually do so in each page/App.

    opened by Youdaman 8
  • Prevent video or audio or animated components from reloading upon landing

    Prevent video or audio or animated components from reloading upon landing

    I've created a Starport with a video element like so:

    <Starport port="video">
      <video width="640" height="480" autoplay controls poster="https://bitmovin.com/wp-content/uploads/2016/06/sintel-poster.jpg">
        <source src="https://upload.wikimedia.org/wikipedia/commons/transcoded/f/f1/Sintel_movie_4K.webm/Sintel_movie_4K.webm.1080p.vp9.webm" type="video/webm">
      </video>
    </Starport>
    

    The animation/transition between pages works fine (I used the playground demo as the foundation to avoid issues I was having with integrating Starport with Vitesse).

    The problem is that the video restarts from 0 upon loading the new route, i.e. it doesn't just keep playing from where it was up to (whether started via autoplay or played manually).

    So while the component/content is correctly being transported to the next page, the element/component itself is being "reset". Is this something that can be overcome? It would also be useful for animated visualisations if they continued rather than reset.

    My guess is the route change triggers the various event listeners for the enclosed element or at least the component/element "thinks" that it has freshly appeared on a new page due to the URL changing rather than being transported from another page.

    opened by Youdaman 4
  • Transitions not working in nuxt 3

    Transitions not working in nuxt 3

    • Operating System: Linux/Windows
    • Node Version: v16.14.2
    • Nuxt Version: 3.0.0-rc.4-27588443.cf25525
    • Package Manager: [email protected]
    • Builder: vite

    Reproduction: https://stackblitz.com/edit/nuxt-starter-ekdzqf?file=tsconfig.json

    Description: On nuxt 3 everyting works except for the transitions. Probably this is due to nuxt wrapping the app in suspense.

    opened by davidmarkl 2
  • fix: proxy margin and padding from rect

    fix: proxy margin and padding from rect

    Starport-start-A => Starport-end-B

    If A has margin or padding, but B does not. Misalignment occurs before or at the end of the transition animation. Because left/top does not add margin and padding.

    eg: gif fixed-margin

    opened by scTaoFelix 2
  • Cannot read properties of undefined (reading 'getInstance')

    Cannot read properties of undefined (reading 'getInstance')

    In the latest version(v0.2.9), It still doesn't work with vite-ssg, the error is as follow image

    the reason has already been mentioned here: injecting state before providing it

    I try to provide a reproduction or test by using router.isReady in this commit and try to fix by AsyncComponent

    opened by tangdaoyuan 2
  • Nuxt3 Support? 🤓

    Nuxt3 Support? 🤓

    I installed the package in my nuxt3 demo app and got this error:

    [plugin:vite:import-analysis] Failed to resolve entry for package "vue-starport". The package may have incorrect main/module/exports specified in its package.json: Failed to resolve entry for package "vue-starport". The package may have incorrect main/module/exports specified in its package.json.
    

    Also VS Code show an error of missing types?

    Cannot find module 'vue-starport' or its corresponding type declarations.ts(2307)
    

    Will there be nuxt3/SSR support?

    Anyway, thanks for your amazing work!

    opened by Clex1o1 2
  • @intlify/vite-plugin-vue-i18n unmet peer dependencies with fresh Vitesse project

    @intlify/vite-plugin-vue-i18n unmet peer dependencies with fresh Vitesse project

    As per https://github.com/antfu/vitesse#clone-to-local I setup a fresh Vitesse project:

    npx degit antfu/vitesse my-vitesse-app
    cd my-vitesse-app
    pnpm i # If you don't have pnpm installed, run: npm install -g pnpm
    

    Then tried installing Starport:

    pnpm i vue-starport
    Packages: +1
    +
    Progress: resolved 953, reused 933, downloaded 0, added 1, done
    
    dependencies:
    + vue-starport 0.2.8
    
     ERR_PNPM_PEER_DEP_ISSUES  Unmet peer dependencies
    
    .
    └─┬ @intlify/vite-plugin-vue-i18n
      └── ✕ unmet peer vue-i18n@next: found 9.1.9
    

    So I tried installing vue-i18n@next as suggested:

    pnpm i vue-i18n@next
    Packages: +6 -11
    ++++++-----------
    Progress: resolved 948, reused 928, downloaded 0, added 6, done
    
    dependencies:
    - vue-i18n 9.1.9
    + vue-i18n 9.2.0-beta.35
    - vue-starport 0.2.8
    
    devDependencies:
    - @intlify/vite-plugin-vue-i18n 4.0.0
    + @intlify/vite-plugin-vue-i18n 4.0.0
    
     ERR_PNPM_PEER_DEP_ISSUES  Unmet peer dependencies
    
    .
    └─┬ @intlify/vite-plugin-vue-i18n
      └── ✕ unmet peer vue-i18n@next: found 9.2.0-beta.35
    

    Found this issue that seems related: https://github.com/intlify/bundle-tools/issues/83

    So I then tried:

    pnpm i -D @intlify/[email protected]
    pnpm i vue-starport
    

    And there were no errors 😄

    opened by Youdaman 1
  • slot 插槽传不进去,被Starport包着的情况下

    slot 插槽传不进去,被Starport包着的情况下

    Describe the bug

    自定义组件被Starport 包着的话slot没生效

    <Starport>
        <MyComponent>
            <!-- 往这里传插槽传不进去 -->
           <div>xxxx</div>
       </MyComponent>
    </Starport>
    

    在线预览

    Reproduction

    https://stackblitz.com/edit/vue-qjey8t?file=src%2FApp.vue

    System Info

    mac 系统
    google chrome
    

    Used Package Manager

    pnpm

    Validations

    • [X] Follow our Code of Conduct
    • [X] Read the Contributing Guide.
    • [X] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
    • [X] Check that this is a concrete bug. For Q&A, please open a GitHub Discussion instead.
    • [X] The provided reproduction is a minimal reproducible of the bug.
    opened by h1y2k3 0
  • vue-starport and vue's keepAlive are mutually exclusive

    vue-starport and vue's keepAlive are mutually exclusive

    Describe the bug

    Hello, I have a conflict between vue-starport and vue's KeepAlive. I cannot use keepalive when I can use vue-starport, and vice versa.

    image

    Reproduction

    System Info

    System:
        OS: macOS 12.6
        CPU: (10) arm64 Apple M1 Pro
        Memory: 116.19 MB / 16.00 GB
        Shell: 5.8.1 - /bin/zsh
      Binaries:
        Node: 16.14.1 - /usr/local/bin/node
        Yarn: 1.22.19 - /usr/local/bin/yarn
        npm: 8.19.3 - /usr/local/bin/npm
      Browsers:
        Chrome: 107.0.5304.121
        Safari: 16.0
    

    Used Package Manager

    yarn

    Validations

    • [X] Follow our Code of Conduct
    • [X] Read the Contributing Guide.
    • [X] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
    • [X] Check that this is a concrete bug. For Q&A, please open a GitHub Discussion instead.
    • [X] The provided reproduction is a minimal reproducible of the bug.
    opened by bilibili-ayang 3
  • Template Refs on vue components break when in Starport

    Template Refs on vue components break when in Starport

    Simple Reproduction link, check console when hitting refresh button: https://stackblitz.com/edit/vitejs-vite-qe124z?file=src/App.vue

    If you have a vue component that you're trying to bind via template ref, doing it inside of a Starport will prevent the ref from attaching properly.

    opened by gabrielthomasjacobs 0
  • problem about scroll

    problem about scroll

    The width of the scroll bar of the demo is 0. If the scroll bar is displayed, the animation will jump when switching routes. demo的滚动条的宽度是0,如果显示滚动条的话,切换路由的时候会导致动画跳变。

    opened by hnustwjj 0
Releases(v0.3.0)
Owner
Anthony Fu
A ship in harbor is safe, but that is not what ships are built for.
Anthony Fu
📓 The UI component explorer. Develop, document, & test React, Vue, Angular, Web Components, Ember, Svelte & more!

Build bulletproof UI components faster Storybook is a development environment for UI components. It allows you to browse a component library, view the

Storybook 75.9k Jan 9, 2023
🐉 Material Component Framework for Vue

Supporting Vuetify Vuetify is a MIT licensed project that is developed and maintained full-time by John Leider and Heather Leider; with support from t

vuetify 36.2k Jan 3, 2023
Universal select/multiselect/tagging component for Vue.js

vue-multiselect Probably the most complete selecting solution for Vue.js 2.0, without jQuery. Documentation Visit: vue-multiselect.js.org Sponsors Gol

Damian Dulisz 6.3k Jan 6, 2023
Everything you wish the HTML