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.

  • Layoutviewport Layout width, which is the width of the page

  • Visualviewport is the width, the width of the browser window, which determines what you can see on a screen on your phone. Visualviewport and layoutviewport

  • The size relationship determines whether or not the scrollbar will appear, which will not appear when the VisualViewPort is larger or exactly equal to layoutViewPort.

  • Idealviewport is a viewport defined by the browser that can be used perfectly on mobile devices. It is fixed, and can be thought of as device-width.

The meta Settings are basically layoutViewPort and VisualViewPort Settings.

  • Width =device-width LayoutViewPort is the same as idealViewPort

  • Initial-scale =1 indicates the page width and the initial scaling ratio of the page width to the device viewport width. The VisualViewPort is determined by this scale, but for LayoutViewPort, it is affected by both properties and then takes the larger of them. User-scale =no Disables scaling

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 is calculated with respect 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. Simply put, the position:fixed element is positioned relative to the nearest ancestor element with a Transform applied, not the window. The reason for this is that the element using transform creates a new stack context. Stacking Context: A Stacking Context is the three-dimensional concept of HTML elements that extend along an imaginary Z-axis relative to the user facing a window (computer screen) or web page, with the HTML elements occupying the space of the cascading Context in order of priority according to their own attributes. The sequence is shown below. In general, the stack context affects the positioning relationship. To further view the uncontrolled 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, which stands for flexible layout, has good mobile compatibility and can meet most layout requirements. Now let’s use Flex to implement the top title bar + middle scroll + bottom navigation bar layout common in H5

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 or returning to the previous page, we just need to add an animation effect for external use. 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

Login to jump

Although this can achieve the jump effect, but need to write the router to add Settings, which is cumbersome; We can use the open source project vue-Navigation to achieve this, which is more convenient and does not need to make unnecessary Settings on the router. NPM i-S Vue-navigation install, import in main.js:

Import Navigation from 'vue-navigation' vue. use(Navigation, {router}) // Router is the 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) {/** * **/ const modules = require.context('./', false, /\w+.vue$/) modules.keys().foreach (fileName => {// Get component configuration const Component = modules(fileName) // Get component name, Remove the file name at the beginning of `. / ` and end the extension of the const name = fileName. Replace (/ ^ \ \ / (. *) \ \ w + $/, '$1') // Register the global component // If the component option is exported via 'export default', then '. Default 'is preferred, // Otherwise it falls back to using the root of the 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

V-model is a syntectic sugar. It is an abbreviation of props and props for monitoring and updating data on component events. Now we use v − m o d e L to implement the number input box, this input box can only input numbers, in the component we only need to define v A L U E to receive the value, However, we use the abbreviation on to listen for events when the input value is enough that we input the bar (input as a number). V-model passes value by default and listens for input events. Now we use V-model to implement the following numeric input box, which can only input numbers. In the component, we only need to define value to accept the passed value, and then use the abbreviation of ON to listen to the event when the input value meets our input condition (the input is a number). V − Model passes value by default. Listen for input events. Now we use v− Model to implement the numeric input box, which can only enter numbers. In the component we only need to define value to accept the passed value, and then use emit to trigger the input event when the value meets our input criteria (the input is 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
    }
  },
  // ...
})

<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 content'});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) {const vm = new Vue({render(h) {// h is createElement, VNode return h(Component, {props})}}).$mount(); / / manual mount document. The body. The appendChild (vm) $el); 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"> Loading </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.

VantUI is a set of 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. Jsmodule. Exports = {rules: [{test: / \. Less $/, use: [other loader configuration {loader: / /... 'less-loader', options: {modifyVars: {// Direct override variable 'text-color': '#111', 'border-color': '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