Display different Vuejs components for mobile browsers

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
   }
 }
}

I had this same problem, I solved it using a neutral and no layout vue file (Init.vue) that will be accessed by mobile and desktop, and this file redirects to the correct file.

Let's suppose that I have the Main.vue and the MainMobile.vue. I will add an Init.vue that will redirect. So my router/index.js is that:

import Router from 'vue-router'
import Vue from 'vue'
import Main from '@/components/Main'
import MainMobile from '@/components/MainMobile'
import Init from '@/components/Init'

Vue.use(Router)

export default new Router({
  routes: [
     {
        path: '/',
        name: 'Root',
        component: Init
     },
    {
      path: '/Main',
      name: 'Main',
      component: Main
    },
    {
      path: '/MainMobile',
      name: 'MainMobile',
      component: MainMobile
    },
  ]
})

At the Init.vue file, the mobile/desktop detection will happen:

<template>
</template>
<script>
    export default {
        name: 'Init',
        methods: {
            isMobile() {
                if( screen.width <= 760 ) {
                    return true;
                }
                else {
                    return false;
                }
            }
        },
        created() {
            if (this.isMobile()) {
                this.$router.push('/MainMobile');
            }
            else {
                this.$router.push('/Main');
            }
        }
    }
</script>
<style scoped>
</style>

The isMobile() function used is very simple, you can change to any other.


I have an idea, use a mixin which detects if the browser is opened on 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/


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

Tags:

Vue.Js

Vuejs2