Because there is an H5 page to be developed next. Although it is H5, it is generally in the form of WebApp. In order to facilitate the following development work, I spent 3-4 days simply encapsulating a set of shelves suitable for the project, and recorded the process, hoping to make progress together with everyone.

Three versions were written, with the case code at the bottom:

  1. vue2-rem– Vue2 + Vant + Rem adaptation
  2. vue2-viewport– Vue2 + Vant + Viewport adaptation scheme
  3. vue3-ts-viewport– Vue3 + TypeScript + Vant + Viewport adaptation scheme

Mobile adaptation

Screen adaptation is the primary issue in mobile page development.

Interface development on mobile is not as simple as that on PC. For example, when developing on PC, we may set a fixed safe width for a certain area, which may be 1200px or 1100px. This design does not have a great impact on the display content of the website. Because of the variety of mobile devices, if we had a fixed width for the mobile interface, we would have horizontal scrollbars on small phones and extra white space on large phones. You can imagine how bad the user experience would be.

At this point, some people may say: you can use responsive layout! While this solution solves the above problem, a new problem arises. Since the font size does not change at all screen sizes, some elements can be misaligned at small sizes. At this point we can make adjustments through @media media query, but it will add extra work.

Let alone in the mobile terminal there are ordinary screen and retina screen, so in the efforts of predecessors, there are a variety of mobile terminal adaptation schemes, the following two commonly used schemes are introduced: Rem layout and Viewport layout.

I recommend reading the following article. It introduces the difference between a normal screen and a retina screen, as well as some basic concepts related to mobile screens.

Use Flexible to achieve terminal adaptation of H5 page

Rem layout

Rem layout is solved by using Rem units and the lib-flexible library.

Rem is a relative unit of CSS that dynamically calculates the size of an HTML tag based on its font size. See the following example:

html {
  font-size: 14px;
}
p {
  font-size: 1rem; Font-size: 14px, 1rem = 14px */
}
h1 {
  font-size: 2rem; Font-size: 28px, 2rem = 14* 2px */
}
Copy the code

The idea is that we can use REM units and dynamically change the font size of the HTML on different mobile screens to keep the content consistent across all screens.

The lib-flexible library has already done the calculations for us. If you want to know more about the underlying information, please refer to the above article. Here is how to use it:

$ yarn add amfe-flexible
Copy the code
// public/index.html // Replace the meta tag with the following code<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
Copy the code
// main.js
import 'amfe-flexible'
Copy the code

Postcss-pxtorem automatically converts PX units into REM units.

$yarn add postcss-pxtorem@^5.1.1 -dCopy the code

The latest installed version is 6.0.0 and an error will be reported in the project.

// .postcssrc.js
module.exports = {
  plugins: {
    / / https://github.com/cuth/postcss-pxtorem#readme English document
    'postcss-pxtorem': {
      rootValue: 37.5.// Width of UI design draft / 10
      unitPrecision: 6.// The precision of the conversion, i.e. the number of decimal places
      propList: [The '*'].// Specify the unit of the CSS property to be converted. * indicates that all CSS properties are converted
      selectorBlackList: [].// Specifies the class name that will not be converted to window units
      replace: true.// Whether to replace the attribute value directly after conversion
      mediaQuery: true.// Whether the media query is also converted in the CSS code, the default is false
      minPixelValue: 1.// Default value 0, < or equal to 0px does not convert
      // exclude: /node_modules/ I // Sets the file to be ignored and matches the directory name with the re}}}Copy the code

In practice, you just need to change the value of rootValue, for example: the width of the design is 750px, rootValue is 75, and the width of the window in the browser is 750px to develop according to the design.

Viewport layout

This project uses viewport as the solution and vH and VW as the adaptation.

The following is an excerpt from MDN’s introduction to viewport:

Viewports represent the currently visible area of computer graphics. In Web browser terms, usually the same as a browser window, excluding the browser UI, menu bar, etc.

We can get the width and height of the viewport by using the innerHeight and innerWidth.

Viewports are divided into layout viewports and visual viewports. The following viewports correspond to layout viewports. The following is the corresponding relationship between VH, VW and viewport width and height:

  • 100vhIs equal to theWindow.innerHeight1vhEqual to 1% layout viewport height.
  • 100vwIs equal to theWindow.innerWidth1vwEqual to 1% layout viewport width.

Since the width and height of the layout viewport will automatically change according to the size of the browser window, compared with REM, we do not need to introduce an additional JS to assist calculation, and now the device support is very high, so we choose this method.

Here is the process:

// public/index.html // Replace the meta tag with the following code<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
Copy the code

You can use meta to set viewport properties. Here are a few:

  • Width Specifies the viewport width. Device-width specifies the screen width of the device

  • Initial-scale Indicates the initial scaling scale

  • Maximum-scale Indicates the maximum scaling ratio

  • Minimum-scale Specifies the minimum scale

  • Whether user-scalable allows users to scale (two fingers for scaling)

Here we use the postcss-px-to-viewPort plugin, which automatically converts PX to VH and VW units. The configuration items are in the.postcsrc.

$ yarn add postcss-px-to-viewport -D
Copy the code
// .postcssrc.js
module.exports = {
  plugins: {
    / / https://github.com/evrone/postcss-px-to-viewport/blob/HEAD/README_CN.md Chinese documents
    'postcss-px-to-viewport': {
      unitToConvert: 'px'.// The unit to convert
      viewportWidth: 375.// Width of UI design draft
      unitPrecision: 6.// The precision of the conversion, i.e. the number of decimal places
      propList: [The '*'].// Specify the unit of the CSS property to be converted. * indicates that all CSS properties are converted
      viewportUnit: 'vw'.// Specify the window unit to convert to, default vw
      fontViewportUnit: 'vw'.// Specify the window unit to convert the font to, default vw
      selectorBlackList: [].// Specify the class name that is not converted to window units,
      minPixelValue: 1.// The default value is 1, and the conversion is not performed if the value is less than or equal to 1px
      mediaQuery: true.// Whether the media query is also converted in the CSS code, the default is false
      replace: true.// Whether to replace the attribute value directly after conversion
      // exclude: [/node_modules/], // ignores files and matches directory names with regex
      landscape: false // Whether to handle landscape}}}Copy the code

The default design width in the configuration file is 375 (which can be changed in the configuration file). To restore the design, just set the browser window width to 375 px, and then the 1:1 design will be restored.

<div class="box"></div>

<style>
  .box {
    width: 375px;
    height: 375px;
    font-size: 12PX;
  }
</style>Will be automatically converted to<style>
  .box {
    width: 100vw;
    height: 100vh;
    font-size: 12PX;
  }
</style>
Copy the code

We can change px to PX if we don’t want some units to automatically convert to viewport units.

interface

Layout

Because projects tend to be webApp oriented, apps usually have two basic master pages:

  • TabBar pp.
    • The page has NavBar at the top and TabBar at the bottom;
    • NavBar has no return key.
  • Inside pages
    • Only NavBar is on the page;
    • NavBar will have a return key.

In order to reduce the repetitive workload, the TabBar page and the inner page are separated into a Layout component in the project, which is associated with the Router. Vuex is used for unified management, and methods are used to set the Layout display content.

The following directories are files related to this feature:

├ ─ ─ configs# set│ ├ ─ ─ app. Json# webApp default Settings├ ─ ─ the plugins# plug-in│ ├ ─ ─ nav - bar. Js# navBar related methods│ ├ ─ ─ TAB - bar. Js# tabBar related methods├ ─ ─ layout# Page layout│ ├ ─ ─ BaseLayout. Vue# based Layout├ ─ ─ the router# routing│ ├ ─ ─ index. Js# the routing table├ ─ ─ store# vuex│ ├ ─ ─ modules# vuex module│ │ ├ ─ ─ app. Js# app module, used to store the relevant variables currently displayed│ ├ ─ ─ getters. Js#│ ├ ─ ─ index. Js#├ ─ ─ the main jsMount the methods in Core to Vue Prototype
Copy the code

Configs /app.json allows you to set some default values for Layout in configs/app.json.

// configs/app.json
{
  "navBar": {
    "show": true.// Whether to display nav bar
    "title": "Default title".// The default nav bar title
    "leftText": "".// The text displayed to the left of the default nav bar
    "leftArrow": true.// Whether to display the return arrow by default
    "rightText": "".// The text displayed to the right of the default nav bar
    "rightIcon": "".// The icon displayed to the right of the default nav bar
    "border": true.// Whether to display border by default
    "safeAreaInsetTop": false // 
  },
  "tabBar": {
    "show": true.// Whether to display TAB bar
    "border": true.// Whether to display TAB bar border
    "activeColor": "#1989fa".// TAB bar activates the color
    "inactiveColor": "#7d7e80".// The color of TAB bar when it is not activated
    "safeAreaInsetBottom": false.// 
    "list": [ // TAB bar data entry
      {
        "key": "HOME".// Each item will be set according to the key
        "title": "Home page".// TAB bar The title of each item
        "icon": "home-o".// TAB bar the icon of each item
        "path": "/home" // TAB bar Binds the route path of each entry
      },
      {
        "key": "ABOUT"."title": "I"."icon": "setting-o"."path": "/about"}}}]Copy the code

After that, the app Module in VUEX is used to associate the configuration with the Layout. Since bidirectional binding is convenient, the configuration can be dynamically set using Vuex.

import appConfig from '@/configs/app.json'

const state = () = > {
  return {
    navBar: appConfig.navBar, // nav bar default setting
    currPageNavBar: {}, // The nav bar setting of the current page is merged with getters and navBar
    tabBar: appConfig.tabBar
  }
}
Copy the code

Here we use currPageNavBar to set the page’s navBar separately and then merge it in getters to avoid conflicts with the default configuration and make it easier to reset each page.

// layout/BaseLayout.vue

watch: {
    // Every time the route changes, do some processing
    $route: {
      handler (route) {
        // Reset the nav bar Settings first
        this.$navBar.resetConfig()
        // Perform other operations...
      },
      immediate: true}},Copy the code

The this.$navbar.resetConfig () method above is defined in plugins/nav-bar.js and is mounted to the Vue prototype as a plug-in so that each instance can easily use this method.

// plugins/nav-bar.js
const navBar = {// Some methods}

export default {
  install: (Vue) = > {
    Vue.prototype.$navBar = navBar
  }
}
Copy the code
// main.js
import Vue from 'vue'
import navBar from '@/plugins/nav-bar'

Vue.use(navBar)
Copy the code

In plugins/tab-bat.js, you can see how to manipulate a TAB bar.

The individual configuration of each page can be defined individually in the routing table using meta.

const routes = [
  {
    path: '/'.component: BaseLayout,
    name: 'BaseLayout'.redirect: '/home'.children: [{path: '/home'.name: 'Home'.component: () = > import(/* webpackChunkName: "Home" */ '@/views/Home.vue'),
        meta: { 
          title: 'home'.// nav bar Displays the title
          showTabBar: true.// Is it a TabBar page
          hiddenNavBar: false.// Whether to hide the nav bar. The default value is false
          navBarLeftText: ' '.// Text to the left of nav bar
          navBarHiddenLeftArrow: ' '.// // Whether to hide the nav bar return arrow
          navBarRightText: ' '.// Text to the right of nav bar
          navBarRightIcon: ' ' // Nav bar icon to the right}}]}]Copy the code

For each page of the nav bar the left side and the right side of the nav bar click after the event, each page must not be the same. I did this by storing a function in VUex and calling it in a callback event, which enabled me to use a different event handler for each page.

// The main method
onClickLeft () {
  let flag = true
  const cb = this.navBar.handleLeftClick
  const next = (bool = true) = > (flag = bool)
  cb && cb(next)
  flag && this.$router.go(-1)
},
onClickRight () {
  const cb = this.navBar.handleRightClick
  cb && cb()
}

// Set the different callbacks through the methods
this.$navBar.onLeftClick(function (next) {
  next(false)
  console.log('Left click to cancel default return')})Copy the code

Abnormal layout of special size

If the layout fails to reach the desired effect in some sizes, we can manually adjust it by querying @media.

/* Document width less than 300 px */
@media screen and (max-width: 300px) {
    body {
        background-color:lightblue; }}Copy the code

@ media reference manual: www.runoob.com/cssref/css3…

1 px border

Before saying the problem, let’s understand a few basic concepts (the top hand Amoy H5 article introduced very clearly) :

  1. Physical pixel: The resolution of the device, where each pixel is a point. For example, 1080×1920 means 1080 points in the vertical direction and 1920 points in the horizontal direction;
  2. Device independent pixels: composed of CSS pixels, eventually converted into device pixels through the underlying system;
  3. CSS pixel: A unit of CSS length, usually px, used primarily in browsers;
  4. Device Pixel ratio (DPR) :Physical pixels/device-independent pixelsThe result is the device pixel ratio.

The relationship between them, using iphone6 for example: Its physical pixel is 750×1134, and its device-independent pixel is 375×667. According to the calculation, its pixel ratio is 750/375 = 2DPR, that is, on iphone6, one independent pixel will be converted into two physical pixels.

So 1px is actually a lot thicker than you want it to be, and there are usually four solutions:

  1. 0.5 px border
  2. border-image
  3. Box-shadow simulates the border
  4. Pseudo-element + transform, which is used in Vant. The following code is based on Vant modifications:
// styles/mixins.less

// 1px border solution
.hairline(@dir.@radius: 0.@width: @border-width-base.@color: @border-color) {
  position: relative;

  &::after {
    z-index: 9;
    position: absolute;
    box-sizing: border-box;
    content: "";
    pointer-events: none;
    top: -50%;
    right: -50%;
    bottom: -50%;
    left: -50%;
    border: 0 solid @color;
    .forOutputBorderWidth(@dir.@width);
    border-radius: @radius * 2;
    transform: scale(0.5); }}.forOutputBorderWidth(@list.@width) {
  .loop(@i:1) when (@i =< length(@list)) {
    @value: extract(@list.@i);
    border-@{value}-width: @width;
    .loop(@i + 1);
  }
  .loop(a); }/ / use
@import 'mixins.less';

.hairline(a);// Full border by default
.hairline(left); // Single use
.hairline(@dir: left, right); // Multi-lateral use
.hairline(@dir: left, right; @color: '# 333'; @radius: 20px); // Pass in any arguments
Copy the code

The TabBar is jacked up by the keyboard

At the beginning of the project, TabBar used position: Fixed at the bottom, but it was jacked up by the keyboard.

This is a common error when Using Position: Fixed on Android. If you want to know more about it, please baidu.

After Baidu, flex + fixed height could be used to solve this problem, but it did not feel good to use fixed height in the project, so the problem was not solved. But looking at the Dachang project, they all avoid the keyboard popping when TabBar is present.

engineering

Catalog design

Directory design is an important part of project engineering, so that people can understand the role of each folder at a glance.

Don’t take too long with catalog design. Sometimes you don’t know how to design at first, but when the project gets to a certain point, you know how to design naturally.

In addition, we can refer to some specifications in dalao open source project. After all, standing on the shoulders of giants, we can see further, such as VUe-element-admin and ant-Design-Vue-Pro.

├ ─ ─ the public# static resources│ │ ─ ─ the favicon. Ico# the favicon icon│ └ ─ ─ index. HTML# HTML templates├ ─ ─ the SRC# source code│ ├ ─ ─ API# all requests│ ├ ─ ─ assetsStatic resources such as theme fonts│ ├ ─ ─ the components# Global common component│ ├ ─ ─ configs# global configuration│ ├ ─ ─ constants# fixed constant│ ├ ─ ─ layout# the overall layout│ ├ ─ ─ the plugins# plug-in│ ├ ─ ─ the router# routing│ ├ ─ ─ store# Global Store management│ ├ ─ ─ styles# global style│ ├ ─ ─ utilsGlobal public method│ ├ ─ ─ views# Views all pages│ ├ ─ ─ App. Vue# Entry page│ └ ─ ─ the main js# entry file load component initialization etc├ ─ ─. Eslintrc. Js# esLint configuration item├ ─ ─ babelrc# Babel - loader configuration├ ─ ─ vue. Config. Js# vue - cli configuration├ ─ ─. Postcssrc. Js# postcss configuration└ ─ ─ package. Json# package.json
Copy the code

Less application

Since the Vant component library is used in the project and their theme is changed through the variable system of LESS, this project is also written in LESS for CSS.

The advantage of using less is that it improves development efficiency and CSS maintainability. Less is not difficult to use, although it has many functions, but we mainly use four of them:

  1. We can split the CSS and import it into the less files we need to use;
@import '~normalize.css'; // If NPM package style is introduced, need to be preceded by ~
@import './var.less';
Copy the code
  1. Mixing can be used to extract and encapsulate commonly used effects;
// single line omission
.ellipsis() {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/ / use
.txt {
  .ellipsis()}Copy the code
  1. Variables can be used to unify the style of the project as a whole;
@width: 20px;
@height: 20px;

.box {
  width: @width;
  height: @height;
  line-height: @height;
}
Copy the code

The Vant variable files in the project are defined in styles/var.less.

  1. You can use nesting to make CSS writing more efficient.
.box {
  width: 20px;
  height: 20px;

  .txt {
    font-size: 12px;
  }

  &__line {
    height: 1px;
    background-color: red; }}/ / is equivalent to

.box {
  width: 20px;
  height: 20px;
}
.box .txt {
  font-size: 12px;
}
.box__line {
  height: 1px;
  background-color: red;
}
Copy the code

Style initialization

In Web development, there are subtle differences in the browser’s default rendering styles due to differences in the browser’s kernel, which can affect how we look in different browsers.

So we started with a style style, resetting all browser styles to the one we wanted, and then developed it so that it would be consistent across all browsers.

Currently, there are two commonly used solutions:

  • Normalize-normalize.css tends to fix browser default bugs and consistency, but retains the default style of elements.
  • Reset-reset. CSS tends to completely Reset the browser’s default style and is more controllable.

The two schemes have their own advantages and disadvantages. You can learn about them by Baidu and use them together in this project.

// styles/index.less
@import '~normalize.css'; // Fix browser bugs
@import './reset.less'; // Initialize the default style of the element
Copy the code

API encapsulation And unified error handling

For the front and back end interactions in the project we use the axios plug-in, based on which a layer of encapsulation is made available to our project:

// utils/request.js
import axios from 'axios'

const service = axios.create({
  timeout: 8000
})

// Request interceptor, which does some processing before the request is sent
service.interceptors.request.use(
  config= > {
    // General projects have permission management, such as the use of token, can be unified in this place to add token
    const token = 'xxxxx'
    if (token) {
      config.headers.Authorization = token
    }

    return config
  }, error= > {
    console.log(error)
    return Promise.reject(error)
  })

// Response interceptor, which can do some processing after receiving the response result
service.interceptors.response.use(
  response= > {
    const res = response.data
    return res
  },
  error= > {
    console.log(error)
    return Promise.reject(error)
  })

export default service
Copy the code
// apis/demo.js
import request from '@/utils/request'

export function demo (data) {
  return request({
    url: 'xxxx'.methods: 'post',
    data
  })
}
Copy the code

Unified error handling, we usually write in the response interceptor, and need to define a front and back end interaction specification, now more commonly used are the following two specifications:

// Customize the code style
// Determine the interface status through user-defined code codes
service.interceptors.response(
  response= > {
    const res = response.data
    
    // If code is not 0, the interface is faulty
    if(res.code ! = =0) {
      // Perform error handling
      console.log(res.error)
      return Promise.reject(res.error)
    }
    
    // Return data if correct
    return res
  },
  error= > {
    console.log(error)
    return Promise.reject(error)
  })
Copy the code
/ / resultful style
service.interceptors.response(
  response= > {
    // Return data directly if correct
    const res = response.data
    return res
  },
  error= > {
    console.log(error)
    // Error processing
    const { status, response } = error.request
    console.log('Status code', status)
    console.log('Error message returned', response.error)
    return Promise.reject(error)
  })
Copy the code

Code specification

The code specification you can refer to the Vue official style guide here is very detailed.

Here are some of the specifications developed during the project:

  1. Naming conventions
    • The overall use of small camel name (JS variable names, function names, etc.);
    • Component and class names are big camel names;
    • The files are all lowercase and words are separated by a dash;
    • The names of variables, methods, and files are kept semantic so that their effects can be determined at a glance.
  2. Annotation specifications
    • The purpose of writing comments is to make the code easier to read, so there is no need to write comments for every line. Most of the code can be read by using standard names.
    • Function, algorithm-related functions must be annotated, using JSDoc specification.
  3. The development of specification
    • When writing code, make it readable first.
    • In function development, follow the principle of single function (each part is only responsible for one function), split more to ensure maintainability;
    • Component development, the general, common components are split to ensure maintainability.

Case code

Example code: VUE-webapp-h5-template

Welcome to point out the problem, I hope you don’t mean the hands of small 🌟🌟, but also hope to progress with you.