Mobile adaptation

Compared to PC, mobile device resolution is a variety of different, for every developer, mobile adaptation is the first problem we need to face in mobile development.

On mobile we often see this code in the head tag:

<meta name='viewport' content='width=device-width,initial-scale=1,user-scale=no' />
Copy the code

The meta tag sets the viewport to define the zoom ratio of the page. To understand what these parameters mean, we need to know what a few viewport widths mean.

  • layoutviewportThe width of the layout is the width of the page
  • visualviewportBut the width, which is the width of the browser window, determines what we can see on a screen;visualviewportandlayoutviewportThe size relationship determines whether the scroll bar will appear whenvisualviewportGreater or exactly equal tolayoutviewportThere will be no scroll bar.
  • idealviewportA viewport defined for the browser to fit perfectly on mobile is fixed and can be considered the width of the device viewportdevice-width.

The meta Settings are basically layoutViewPort and VisualViewPort Settings.

  • width=device-widthPage widthlayoutviewportAnd device viewport widthidealviewportconsistent
  • initial-scale=1Represents the page width and the initial scaling ratio of the page width to the viewport width of the device,visualviewportDepending on the ratio, but forlayoutviewportFor example, it is affected by both attributes and takes the larger of them.
  • user-scale=noBan zoom

So now we know what this code, which is common on mobile, means to set visualViewPort and LayoutViewPort to the value of idealViewPort; In this way, we will not have a scroll bar on the mobile terminal, and the content of the web page can be better displayed. On this premise, we will consider the adaptation of the page.

UI graphics usually have a fixed width, and the width of our actual mobile devices is not the same, but if the scale of the page elements is the same as the scale of the page width, the page will look the same on different sizes of devices.

Use relative units

rem

Rem does the calculation relative to the font size of the root element HTML. Upon initial page load time, usually through to the document. The documentElement. Style.css. FontSize Settings. Generally we set the font size of the root HTML element to 1/10 of the width. The width varies from device to device, but the REM ratio of the same value is the same as the ratio of the width of the device.

document.documentElement.style.fontSize = document.documentElement.clientWidth / 10 + 'px';
Copy the code

In a real project we don’t have to do the conversion ourselves during development, we can use PXtoREM to convert PX to REM on output.

Viewport unit

Divide the viewport width window.innerWidth and viewport height window.innerHeight (layoutViewPort) into 100 equal portions.

Vw: 1vw is 1% of the viewport width vh: 1vh is 1% of the viewport height Vmin: smaller value in VW and vh vmax: larger value in VW and VH

Compared with REM, viewport unit does not need to use JS to set the root element, which is less compatible, but most devices already support it. Similarly, there is no need to convert the unit when developing, but directly use the related plug-in PostCSS-px-to-viewport to convert the output.

Modify the viewport

We mentioned earlier that the layoutViewPort layout width is not actually a fixed value, but is set by the meta property. The value is calculated by the idealViewPort, and we can fix the LayoutViewPort to a certain value by controlling the meta property. A typical layout is 750px wide, so our goal now is to set layoutViewPort to 750px; Layoutviewport is affected by two properties, width we set to 750, and initial-scale should be the width of idealViewPort /750; When we did not change the meta tag attributes layoutviewport idealviewport value, is in fact the value. So I can through the document body. ClientWidth or Windows. The innerWidth to obtain.

; (function () {
    const width = document.body.clientWidth || window.innerWidth
    const scale = width / 750
    const content = 'width=750, initial-scale=' + scale + ', minimum-scale=' + scale + ', maximum-scale=' + scale + ', viewport-fit=cover'
    document.querySelector('meta[name="viewport"]').content = content
})()
Copy the code

Once set, layoutViewPort will always be 750px on different devices, so we can use the layout size directly when developing.

The layout style

Layouts can come in a variety of ways, but for compatibility reasons, some styles are best avoided, and hard problems are left unaddressed.

Fixed needs to be treated with caution

Position: Fixed is very common in everyday page layouts and plays a key role in many layouts. Position: Fixed specifies the position of the element relative to the position of the screen viewport. And the position of the element does not change as the screen scrolls. However, in many specific occasions, the performance of position: Fixed is quite different from what we expect.

  1. IOS pop-up keyboard; When the soft keyboard is aroused, the fixed element on the page will become invalid (iOS thinks that the user wants the element to move with the scroll, that is, become absolute position). Since it is absolute, the invalid fixed element will follow the scroll when the page is more than one screen.
  2. When the transform attribute of an element ancestor is not None, the location container is changed from viewport to ancestor. To put it simply, it’sposition:fixedThe element is positioned relative to the nearest ancestor element that has the Transform applied, not the window. The reason for this is that the element using transform creates a new stack context. Stacking Context: A stacked context is the three-dimensional concept of HTML elements that extend along an imaginary Z-axis relative to the user facing a window or web page, occupying the space of the stacked context in order of priority according to their own attributes. The sequence is shown below. In general, the stack context affects the positioning relationship. You can check it out for further informationOut of control position:fixed.

Keyboard pop-ups and use of the transform property are common on mobile, so use position: Fixed with caution.

Flex is recommended

flex-direction: column;
flex: 1;

html, body { padding: 0; margin: 0; } .page { position: absolute; top: 0; bottom: 0; width: 100%; display: flex; flex-direction: column; .page-content { flex: 1; overflow-y: auto; }}Copy the code

Then to realize the bottom title bar, the bottom title bar is generally composed of the center title and left and right operation area; To center the middle section title, we should keep the left and right sections the same width.

<template> <div class="header"> <div class="header__left"> </div> <div class="header__main"> <slot>Title</slot> </div> <div class="header__right"> <div> right </div> </div> </template> <script> export default {name: 'HeadBar' } </script> <style lang="scss" scoped> .header { display: flex; width: 100%; line-height: 88px; height: 88px; font-size: 36px; background-color: #42b983; z-index: 999; color: #fff; transition: background-color .5s ease; .header__main { flex: 1; display: flex; justify-content: center; align-items: center; text-align: center; } .header__left, .header__right { padding: 0 16px; width: 120px; } .header__left { text-align: left; } .header__right { text-align: right; } } </style>Copy the code

The main part of the bottom navigation bar is actually a single navigation option bisecting the navigation bar; For each navigation option, one direction is Flex-direction: column; Align-items: Center; , vertical to justify-content: space-around;

<template> <div class="taber"> <div class="taber-item"> <div class="icon"></div> <span> Option 1</ SPAN ></div> <div Class ="taber-item"> <div class="icon"></div> <span> Option 2</span> </div> <div class="taber-item"> </div> < span > option 3 < / span > < / div > < div class = "taber - item" > < div class = "icon" > < / div > < span > option 4 < / span > < / div > < / div > < / template > <script> export default { name: 'BottomTaber', data () { return { } } } </script> <style lang="scss" scoped> .taber { background-color: #42b983; color: #fff; height: 88px; display: flex; .taber-item { flex: 1; display: flex; flex-direction: column; justify-content: space-around; align-items: center; } .icon { width: 36px; height: 36px; background-color: #fff; } } </style>Copy the code

Page jump

Animated transitions

In VUE, we manage routes through vue-Router. Each route is similar to switching between different pages. From a user-friendly perspective, it is best to add a transition effect every time you switch pages. If the transition animation does not distinguish between opening a new page and returning to the previous page, we just need to add an animation using < Transition > outside of

. However, open and return are usually animated differently, so we need to distinguish between forward and backward routes when switching routes. In order to distinguish the route action, we set meta to a number in the route file. Meta indicates the depth of the route. Then we listen for $route and set different jump animations according to the size of to and from meta values. If applied to a variety of jump animation, can be based on the details, specific application.

<template>
  <transition :name="transitionName">
    <router-view></router-view>
  </transition>
</template>

<script>
export default {
  name: 'app'.data () {
    return {
      transitionName: 'fade'
    }
  },
  watch: {
    '$route' (to, from) {
      let toDepth = to.meta
      let fromDepth = from.meta
      if (fromDepth > toDepth) {
        this.transitionName = 'fade-left'
      } else if (fromDepth < toDepth) {
        this.transitionName = 'fade-right'
      } else {
        this.transitionName = 'fade'
      }
    }
  }
}
</script>
Copy the code

vue-navigation
npm i -S vue-navigation

import Navigation from 'vue-navigation'Vue.use(Navigation, {router}) // Router is a routing fileCopy the code

Set in app.vue:

this.$navigation.on('forward', (to, from) => {
    this.transitionName = 'fade-right'
 })
 this.$navigation.on('back', (to, from) => {
    this.transitionName = 'fade-left'
 })
 this.$navigation.on('replace', (to, from) => {
    this.transitionName = 'fade'
 })
Copy the code

Another important function of vue-Navigation plug-in is to save the page state, which is similar to keep-alive, but keep-alive cannot identify the forward and backward routes. In practical application, we want to save the page state when returning to the page, and want to obtain new data when entering the page. This can be done with vue-Navigation. Please refer to vue-Navigation for detailed instructions and cases. Vue-page-stack can also be tried, both projects can achieve the effect we need. Vue-page-stack uses vue-Navigation for reference and also realizes more functions, and has been updated recently.

PS: Here the animation effects are referenced from animate. SCSS;

Bottom navigation bar

We’ve already implemented the basic style of the bottom navigation bar, so let’s do a few more things here. When the page routing path matches the router-link route, router-link will be activated. You can set active-class to specify the class name applied to the path activation. The default is router-link-active. The router-link-exact-active class name is specified by exact-active class. The router-link-exact-active class name is also the name of the class applied when the path is activated. Active-class and exact-active-class are determined by the route matching mode.

Generally, routes are matched in inclusive mode. For example, if the current path starts with /a, the CSS class name will also be set. According to this rule, each route is activated, and exact matches can be used with the exact attribute. Exact matching is only enabled if the routes are identical.

Routing guard

Generally speaking, the route guard of mobile terminal is not too complicated. It mainly depends on the judgment of login permission. We set a route whitelist and put all routes that do not need login permission into it. For routing need to log in to do judgment, jump to the login page without a login, require users to log in on a visit, if need to return to the original route after login the target page routing passed as a parameter to the login page, judge after login again, if there is a target page parameters jump target page, did not jump page.

If your application has permissions, specify the required permissions for each route and set roles in the meta. Roles is an array to hold the required permissions. To determine whether the user has the permissions, compare them with roles from the background interface.

const whiteList = ['/login']
router.beforeEach((to, from, next) = > {
  const hasToken = store.getters.auth
  if (hasToken) {
    if (to.path === '/login') {
      next({ path: '/'})}else {
      const needRoles = to.meta && to.meta.roles && to.meta.roles.length > 0
      if (needRoles) {
        const hasRoles = store.state.user.roles.some(role= > to.meta.roles.includes(role))
        if (hasRoles) {
          next()
        } else {
          next('/ 403')}}else {
        next()
      }
    }
  } else {
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next('/login')}}})Copy the code

component

Automatic loading

In our projects, many components are often used, and the components that are commonly used are generally used as global components in order to avoid the tedious of repeated import. To register global components, we first need to import components, and then use Vue.com Ponent to register; This is a repetitive effort that we do every time we create a component, and if our project is built using Webpack (vue-CLI also uses Webpack), we can automatically register the component globally via require.context. Create components/index.js file:

export default function registerComponent (Vue) {
  /** * Parameter description: * 1. Relative path of component directory * 2. Query subdirectory * 3. Match the regular expression **/ of the basic component file name
  const modules = require.context('/'.false, /\w+.vue$/)
  modules.keys().forEach(fileName= > {
    // Get the component configuration
    const component = modules(fileName)
    // Get the component name, removing the './ 'at the beginning of the filename and the extension at the end of the filename
    const name = fileName.replace(/^\.\/(.*)\.\w+$/.'$1')
    // Register the global component
    // If the component option is exported via 'export default',
    // Then '.default 'is preferred,
    // Otherwise fall back to the root of the use module.
    Vue.component(name, component.default || component)
  })
}
Copy the code

Then import the registration module in main.js for registration. Using require.context, we can also import the vue plug-in and global filter.

import registerComponent from './components'
registerComponent(Vue)
Copy the code

Bind data via v-Model

The v-model is a syntactically modified version of the props and $ON listener event. The V-model passes value by default and listens for input events. Now we use the V-Model to implement the numeric input field, which can only enter numbers. In the component we need to define value to accept the input value, and then use $emit to trigger the input event when the value meets our input criteria (enter a number).

<template>
  <div>
    <input type="text" :value="value" @input="onInput">
  </div>
</template>
<script>
export default {
  name: 'NumberInput',
  props: {
    value: String
  },
  methods: {
    onInput (event) {
      if (/^\d+$/.test(event.target.value)) {
        this.$emit('input', event.target.value)
      } else {
        event.target.value = this.value
      }
    }
  }
}
</script>
Copy the code

To use it, we just need to use the V-Model binding value. By default, v-Model uses a prop named value and an event named input, but many times we want to use different prop and listen for different events, which we can modify using the Model option.

Vue.component('my-checkbox', {
  model: {
    prop: 'checked'.event: 'change'
  },
  props: {
    // this allows using the `value` prop for a different purpose
    value: String.// use `checked` as the prop which take the place of `value`
    checked: {
      type: Number.default: 0}},// ...
})
Copy the code
<my-checkbox v-model="foo" value="some value"></my-checkbox>
Copy the code

The above code is equivalent to:

<my-checkbox
  :checked="foo"
  @change="val => { foo = val }"
  value="some value">
</my-checkbox>
Copy the code

Use components as plug-ins

In many third-party component libraries, we often see components called directly by plug-ins, such as VantUI’s Dialog popup component, which can be used not only as a component, but also as a plug-in.

this.$dialog.alert({
  message: 'Popover contents'
});
Copy the code

The principle behind using a component as a plug-in isn’t really that complicated, it’s to manually mount Vue component instances.

import Vue from 'vue';
export default function create(Component, props) {
    // Create an instance first
    const vm = new Vue({
        render(h) {
            // h is createElement, which returns VNode
            return h(Component, {props})
        }
    }).$mount();
    // Manually mount
    document.body.appendChild(vm.$el);
    // Destruction method
    const comp = vm.$children[0];
    comp.remove = function() {
        document.body.removeChild(vm.$el);
        vm.$destroy();
    }
    return comp;
}
Copy the code

The create call passes in the component and props parameters to get an instance of the component, from which we can call various functions of the component.

<template>
  <div class="loading-wrapper" v-show="visible"</div> </template> <script>export default {
  name: 'Loading'.data () {
    return {
      visible: false
    }
  },
  methods: {
    show () {
      this.visible = true
    },
    hide () {
      this.visible = false
    }
  }
}
</script>
<style lang="css"scoped> .loading-wrapper { position: absolute; top: 0; bottom: 0; width: 100%; background-color: rgba(0, 0, 0, .4); z-index: 999; } </style> <! --> const loading = create(loading, {}) load.show () // display load.hide () // closeCopy the code

Third-party Components

Mobile components and plug-ins are relatively complete, and it is unwise to repeat the wheel in project development; We can use third-party components and plug-ins to improve our development efficiency when developing projects.

Common component library

VantUI is an open source, lightweight and reliable mobile Vue component library. Support on-demand introduction, theme customization, SSR, in addition to common components, there are special business components for the e-commerce scene, if you are developing e-commerce projects, recommended use. The official documentation for theme customization is set in webpack.config.js:

// webpack.config.js
module.exports = {
  rules: [{test: /\.less$/.use: [
        / /... Other Loader Configurations
        {
          loader: 'less-loader'.options: {
            modifyVars: {
              // Override variables directly
              'text-color': '# 111'.'border-color': '#eee'
              // Or you can override it with less files (the file path is absolute)
              'hack': `true; @import "your-less-file-path.less"; `}}}]};Copy the code

But our project may be built using vue-CLI, in which case we need to set it in vue.config.js:

module.exports = {
  css: {
    loaderOptions: {
      less: {
        modifyVars: {
          'hack': `true; @import "~@/assets/less/vars.less"; `
        }
      }
    }
  }
}
Copy the code

Vux and Mint-UI are also good choices.

Common plug-in

Better Scroll is a plugin that provides silky scrolling effect for various mobile terminal scrolling scenes. If used in VUE, please refer to the author’s article when better Scroll meets Vue.

Swiper is a rotation map plug-in. Vue-awesome-swiper can be used directly in VUE. Vue-awesome-swiper is based on Swiper4.