Display different Vuejs components for mobile brow

2020-01-30 07:34发布

I am developing an SPA using Vue 2.0. The components developed so far are for the "desktop" browsers, for example, I have

Main.vue, ProductList.vue, ProductDetail.vue,

I want another set of components for the mobile browsers, such as MainMobile.vue, ProductListMobile.vue, ProductDetailMobile.vue,

My question is, where and how do I make my SPA render the mobile version of components when viewing in a mobile browser?

Please note that I explicitly want to avoid making my components responsive. I want to keep two separate versions of them.

Thanks,

标签: vue.js vuejs2
8条回答
虎瘦雄心在
2楼-- · 2020-01-30 08:10

I have an idea, use a mixin which detects is the browser mobile or desktop (example for js code in this answer ).. then use v-if, for example

<production-list v-if="!isMobile()"></production-list>
<production-list-mobile v-else></production-list-mobile>

so here is an example on https://jsfiddle.net/Ldku0xec/

查看更多
干净又极端
3楼-- · 2020-01-30 08:16

I was looking for a solution for this and came here but I couldn't find what I needed:

  1. Asynchronous imports to only load into the bundle what was needed based on the viewport.
  2. Capability to serve a different layout if the layout was resized

I mixed and matched a few things I read online including answers here so I thought I'd just come back and put all my learnings into one function for anyone else looking:

/**
 * Breakpoint configuration to be in line with element-ui's standards
 * @type {{LABELS: string[], VALUES: number[]}}
 */
const BREAKPOINTS = {
    LABELS: ['xs', 'sm', 'md', 'lg', 'xl'],
    VALUES: [0, 768, 992, 1200, 1920, Infinity]
};


/**
 * @typedef ViewFactory
 * @type function
 * A function which returns a promise which resolves to a view. Used to dynamically fetch a view file on the fly during
 * run time on a need basis
 */


/**
 * A helper to get a responsive route factory which renders different views based on the current view point
 * @param {{xs:[ViewFactory],sm:[ViewFactory],md:[ViewFactory],lg:[ViewFactory]}} map - A map of breakpoint key to a ViewFactory
 * @returns {ViewFactory} - A view factory which invokes and returns an item supplied in the map based on the current viewport size
 */
export default function responsiveRoute(map) {
    return function getResponsiveView() {
        const screenWidth = document.documentElement.clientWidth;

        // Find the matching index for the current screen width
        const matchIndex = BREAKPOINTS.VALUES.findIndex((item, idx) => {
            if (idx === 0) {
                return false;
            }
            return screenWidth >= BREAKPOINTS.VALUES[idx - 1] && screenWidth < BREAKPOINTS.VALUES[idx];
        }) - 1;


        if (map[BREAKPOINTS.LABELS[matchIndex]]) {
            // Perfect match, use it
            return map[BREAKPOINTS.LABELS[matchIndex]]();
        } else {
            // Go down the responsive break points list until a match is found
            let counter = matchIndex;
            while (counter-- > 0) {
                if (map[BREAKPOINTS.LABELS[counter]]) {
                    return map[BREAKPOINTS.LABELS[counter]]();
                }
            }
            return Promise.reject({
                code: 500,
                info: 'No component matched the breakpoint - probably a configuration error'
            });
        }
    };
} 

Usage:

const router = new Router({
    mode: 'history',
    base: process.env.BASE_URL,
    routes:[{
      path: '/login',
      name: 'login',
      component: responsiveRoute({
          // route level code-splitting
          // this generates a separate chunk (login-xs.[hash].js) for this route
          // which is lazy-loaded when the route is visited.
          xs: () => import(/* webpackChunkName: "login-xs" */ './views/Login/Login-xs.vue'),
          // sm key is missing, it falls back to xs
          md: () => import(/* webpackChunkName: "login-md" */ './views/Login/Login-md.vue')
          // lg, xl keys are missing falls back to md
      }) 
  }]
}); 

How it works:

Vue Router supports defining the component key as a function which returns a promise to support async routes. The most common way being to use the webpack import() function which returns a promise. The function which returns the promise is only invoked when the route is about to be rendered ensuring we can lazy load our components

The responsiveRoute function accepts a map of these functions with keys set for different breakpoints and returns a function which, when invoked, checks the available viewport size and returns invokes the correct promise factory and return's the promise returned by it.

Notes:

I like this method because it does not require the application architecture or route configurations to be in a certain way. It's pretty plug and play using Vue Router capabilities provided out of the box. It also does not force you to define a view for every breakpoint-route combination. You can define a route as usual without this(lazy loaded or not) along side other routes that use this without any problems.

This method does not use user agent sniffing but uses the available width of the document.documentElement instead. Other methods I saw recommended things like window.screen.width which gives the exact device screen size regardless of the window size or a more robust window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth. Mix and match as needed.

My break points are (number and their values) are based on element-ui breakpoints as I used that for normal responsive design. This can again be configured as needed by changing the constants at the top

查看更多
狗以群分
4楼-- · 2020-01-30 08:18

An extended version of Beaudinn Greves answer:

  • Use named router views
  • listen on window resize and set isMobile true if width smaller then xy
  • in router.js use "components" instead of "component" and import (you may use require) the desktop and mobile components

App.vue:

<template>
  <div id="app" class="container grid-lg">
    <router-view v-if="!store.app.isMobile"/>
    <router-view v-else name="mobile"/>
  </div>
</template>
...
name: "app",
  data: function() {
  return {
    store: this.$store.state
  };
},
mounted () {
  this.onResize()
  window.addEventListener('resize', this.onResize, { passive: true })
},
methods: {
  onResize () {
    this.store.app.isMobile = window.innerWidth < 600
  }
},
beforeDestroy () {
  if (typeof window !== 'undefined') {
    window.removeEventListener('resize', this.onResize, { passive: true })
  }
}

router.js:

routes: [
  {
    path: '/',
    name: 'home',
    components: {
      default: Home,
      mobile: HomeMobile
    }
  }
]
查看更多
我命由我不由天
5楼-- · 2020-01-30 08:19

A bit late for this but, in case if any of you are looking for I handled the situation like this: I added meta to my router:

const router = new Router({
     routes: [{
      path: '/main-view
      name: 'mainView',
      component: MainView,
      meta: {
        'hasMobileView': true
      }
     },
    {
     path: '/mobile-view',
      name: 'mobileView',
      component: mobileView,
      meta: {
        'hasDesktopView': true
      }
     },
    }]
})

then on beforeeach function 

router.beforeEach((to, from, next) => {
  const hasMobileView = to.matched.some((route) => route.meta.hasMobileView)
  if (hasMobileView) {
    if (navigator.userAgent.match(/Android/i) ||
      navigator.userAgent.match(/webOS/i) ||
      navigator.userAgent.match(/iPhone/i) ||
      navigator.userAgent.match(/iPad/i) ||
      navigator.userAgent.match(/iPod/i) ||
      navigator.userAgent.match(/BlackBerry/i) ||
      navigator.userAgent.match(/Windows Phone/i)) {
      next('/mobile-view')
    } else {
      next()
    }
  }
})`

查看更多
兄弟一词,经得起流年.
6楼-- · 2020-01-30 08:24

I have a better solution.In src/main.js:

 if (condition) {
    require('./pc/main)
 }else {
    require('./mobile/main')
}
查看更多
\"骚年 ilove
7楼-- · 2020-01-30 08:25

I have simple solution for Vue.js:

<div v-if="!isMobile()">
  <desktop>
  </desktop>
</div>
<div v-else>
  <mobile>
  </mobile>
</div>

And methods:

methods: {
 isMobile() {
   if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
     return true
   } else {
     return false
   }
 }
}
查看更多
登录 后发表回答