可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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,
回答1:
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/
回答2:
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
}
}
}
回答3:
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.
回答4:
I was looking for a solution for this and came here but I couldn't find what I needed:
- Asynchronous imports to only load into the bundle what was needed based on the viewport.
- 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
回答5:
A bit late for this but, in case if any of you are looking to bind watchers for the viewport size,
and/or check whenever the page loads.
Its a lot of boilerplate to write but can be useful for small applications.
export default {
data: () => ({
isMobile: false
}),
beforeDestroy () {
if (typeof window !== 'undefined') {
window.removeEventListener('resize', this.onResize, { passive: true })
}
},
mounted () {
this.onResize()
window.addEventListener('resize', this.onResize, { passive: true })
},
methods: {
onResize () {
this.isMobile = window.innerWidth < 600
}
}
}
回答6:
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()
}
}
})`
回答7:
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
}
}
]
回答8:
I have a better solution.In src/main.js:
if (condition) {
require('./pc/main)
}else {
require('./mobile/main')
}