Using vue family barrel to make blog website

Keywords: Javascript Vue axios Mobile iOS

Front words

The author is doing a complete blog online project, including Reception,Backstage,Back-end interface Server configuration. This article will introduce in detail the blog website made with vue family bucket.

 

Summary

This project is a set of blog front page based on Vue family bucket (vue, vue-router, vuex, vue SSR). Its main functions include home page display, authentication system, article management, comment management and comment management.

[Access Address]

Domain name: https://xiaohuochai.cc

  Github: https://github.com/littlematch0123/blog-client

Or two-dimensional code access can be scanned directly

[Project Introduction]

The content of this project is based on more than 600 blogs written by the author in the process of self-learning front-end, which may be helpful to the students who are also learning front-end. Many blogs have DEMO s that can be operated directly, and the understanding of knowledge may be more intuitive.

The mobile side and the desktop side can be adapted with the mobile-first responsive layout; the font size is in em units, and the text on the desktop side becomes larger accordingly; the mobile side can use sliding screen operation, and the desktop side can enhance the interactive experience through cursor settings, custom scroll bars, carriage return determination, etc.

The whole station adopts server-side rendering SSR, which is beneficial to SEO and reduces the rendering time of the first screen. The offline caching and adding functions of the PWA scheme to the desktop are realized by using service worker and manifest.

According to HTML tag content model, semantic tags are used to minimize tag hierarchy and semantically-free div tags.

CSS uses class selector extensively to minimize selector level, uses CSS module and postCSS in vue component, uses styleLint to standardize CSS code, writes code according to the order of layout class attribute, box model attribute, text class attribute and modifying class attribute, and uses order plug-in to verify it.

Using esLint specification JS code, code style refers to airbnb specification, all naming uses hump writing, common components are prefixed with Base, event function is prefixed with on, async function is suffixed with async, Boolean value is basically prefixed with do or is.

Instead of referring to third-party component libraries, such as bootstrap or element components, they developed their own common components needed in the project. Under the common directory, it encapsulates such components as head image, full screen, loading, mask, search box, linkage selection and so on, which is convenient for development.

Using configuration data, data and application are separated and stored in the constants directory as a constant.

Using Aliyun's short message module, the function of short message verification is realized.

There are two hidden eggs in this project. One is the shake-and-shake function, which can be directly shaken to the background page. The other is the gyroscope function. When shaking the mobile phone up and down, the head will rotate.

The project has been optimized and the final optimization score is as follows

 

Function demonstration

The main functions include homepage display, authentication system, article management, comment management and comment management.

[Home page display]

Home page includes drag-and-drop rotation map, topic recommendation, article recommendation and category recommendation

[Authentication System]

Authentication system includes user registration, user login, short message verification

1. When the user is not logged in, he can read the article, but he can't comment or comment, otherwise the login box will pop up.

2. User Registration

3. User login

[Article Management]

Article management includes browsing recommended articles, filtering by category, searching for articles, and viewing by directory.

1. Browse Recommended Articles

2. Articles Screening

3. Article Search

4. View by directory

[Praise management]

[Comment Management]

Comment management includes viewing comments, adding comments, modifying comments and deleting comments

 

directory structure

src directory, including assets (static resources), common (public components), components (functional components), constants (constant configuration), router (routing), store(vuex) and utils (tool methods) seven directories

- assets // Store static resources, mainly pictures
    -imgs
      css.png // CSS Article Background Map
     ...
- common // Store common components
    -SVG // Deposit VUE Icon component
        SVGAdd.vue // "add to"Button
        SVGBack.vue // "Return"Button
        ...
    BaseArticle.vue // Article component
    BaseAvatar.vue // Head component
    ...
- components // Storage Functional Components
    -Post // Article component      
      module.js //Article Status Management    
      Post.vue // Article Display Component
      PostContent.vue // Article Catalog Component
      PostList.vue // Article List Component
      SearchPost.vue // Search article components
      ...
- constants // Storage Constant Configuration
    API.js // Deposit API Calling address
- router // Storage routing
    index.js 
- store // Deposit vuex
    index.js
- utils // Storage tool method
    async.js // axios Method
    fnVarificate.js // Form validation method
    util.js // Other tools and methods

[Common Components]

Instead of referring to third-party component libraries, such as bootstrap or element components, they developed their own common components needed in the project

Encapsulated article component, header component, return component, button component, card component, full screen component, input box component, loading component, mask component, search box component, multi-line input box component, Title component, breadcrumb component, button group component, anti-color button component, password box component, input box component and linkage selection component.

BaseAdd.vue // "add to"assembly
BaseArticle.vue  // Article component
BaseAvatar.vue // Head component
BaseBack.vue // Return component
BaseButton.vue // Button component
BaseCard.vue // Card module
BaseFullScreen.vue // Full screen module
BaseInput.vue  // Input box component
BaseLoading.vue  // loading assembly
BaseMask.vue // mask assembly
BaseSearchBox.vue  // Search box component
BaseTextArea.vue // Multi-line Input Box Component
BaseTitle.vue  // Title Component
BreadCrumb.vue // Bread crumb assembly
ButtonBox.vue  // Button Group Component
ButtonInverted.vue // Anti-color Button Component
InputPassword.vue  // Password Box Component
InputWithTest.vue // Input Box Component Containing Detection
LinkageSelector.vue // Linkage Selection Component

[Functional components]

Set up the directory according to the function, as follows

Alert
Category Management
Comment Management
Home page (Home)
Like Management
Post
Size
The Header User Management

 

Overall thinking

[Full screen layout]

Full-screen layout with height setting is implemented mainly through calc

<div
  id="root"
  :class="$style.wrap"
  :style="{height:wrapHeight+'px'}"
>
  ...
  <TheHeader :class="$style.header"/>
  <main :class="$style.main">
    <transition :name="transitionName">
      <router-view :class="$style.router" />
    </transition>
  </main>
</div>
.header {
  height: 40px;
}
.main {
  position: relative;
  height: calc(100% - 40px);
  overflow: auto;
}

[Hierarchical Management]

Hierarchical z-index of the project, using only 0-3

Full-screen pop-up box optimization level is the highest, set to 3; sidebar set to 2; page elements default to 0, if necessary, to set to 1

Global Ejection Layer

Set up the global pop-up layer and load in the entry file App.vue, all components can be shared

// App.vue
<template>
  <div
    id="root"
    :class="$style.wrap"
    :style="{height:wrapHeight+'px'}"
  >
    <AlertWithLoading v-show="doShowLoading" />
    <AlertWithText
      v-show="alertText !== ''"
      :text="alertText"
      :onClick="() => {$store.commit(HIDE_ALERTTEXT)}"
    />
    <TheHeader :class="$style.header"/>
    <main :class="$style.main">
      <transition :name="transitionName">
        <router-view :class="$style.router" />
      </transition>
    </main>
  </div>
</template>

[Routing Management]

vue-router uses static routing table to manage routing. Although it is not flexible as react-router-dom, it is easy to find and see at a glance.

Set up routing to load components on demand and set scrolling behavior

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)
export default function createRouter() {
  return new Router({
    mode: 'history',
    routes: [
      {
        path: '/',
        component: () => import(/* webpackChunkName:'home' */ '@/components/Home/Home'),
        name: 'home',
        meta: { index: 0 }
      },
      {
        path: '/posts',
        component: () => import(/* webpackChunkName:'post' */ '@/components/Post/PostList'),
        name: 'postlist'
      },
      {
        path: '/posts/search',
        component: () => import(/* webpackChunkName:'post' */ '@/components/Post/SearchPost'),
        name: 'searchpost'
      },
      {
        path: '/posts/:postid',
        component: () => import(/* webpackChunkName:'post' */ '@/components/Post/Post'),
        name: 'post',
        children: [
          {
            path: 'comments',
            name: 'commentlist',
            component: () => import(/* webpackChunkName:'comment' */ '@/components/Comment/CommentList'),
            children: [
              {
                path: 'add',
                name: 'addcomment',
                component: () => import(/* webpackChunkName:'comment' */ '@/components/Comment/AddComment')
              },
              {
                path: ':commentid/update',
                name: 'updatecomment',
                component: () => import(/* webpackChunkName:'comment' */ '@/components/Comment/UpdateComment')
              },
              {
                path: ':commentid/delete',
                name: 'deletecomment',
                component: () => import(/* webpackChunkName:'comment' */ '@/components/Comment/DeleteComment')
              }
            ]
          }
        ]
      },
      {
        path: '/categories',
        component: () => import(/* webpackChunkName:'category' */ '@/components/Category/CategoryList'),
        name: 'categorylist'
      },
      {
        path: '/categories/:number',
        component: () => import(/* webpackChunkName:'category' */ '@/components/Category/Category'),
        name: 'category'
      },
      {
        path: '/topics/:number',
        component: () => import(/* webpackChunkName:'category' */ '@/components/Category/CategoryTopic'),
        name: 'topic'
      },
      // register
      {
        path: '/signup',
        component: () => import(/* webpackChunkName:'user' */ '@/components/User/AuthSignup'),
        name: 'signup'
      },
      // Log in by cell phone number
      {
        path: '/signin_by_phonenumber',
        component: () => import(/* webpackChunkName:'user' */ '@/components/User/AuthSigninByPhoneNumber'),
        name: 'signin_by_phonenumber'
      },
      // Log in by username
      {
        path: '/signin_by_username',
        component: () => import(/* webpackChunkName:'user' */ '@/components/User/AuthSigninByUsername'),
        name: 'signin_by_username'
      },
      // User page
      {
        path: '/users/:userid',
        component: () => import(/* webpackChunkName:'user' */ '@/components/User/UserDesk'),
        name: 'user'
      }
    ],
    scrollBehavior(to, from, savedPosition) {
      if (savedPosition) {
        return savedPosition
      }
      return { x: 0, y: 0 }
    }
  })
}

[State Management]

The state management of each component is named module.js and stored in the current component directory

import Vue from 'vue'
import Vuex from 'vuex'
import auth from '@/components/User/module'
import alert from '@/components/Alert/module'
import post from '@/components/Post/module'
import category from '@/components/Category/module'
import like from '@/components/Like/module'
import size from '@/components/Size/module'
import comment from '@/components/Comment/module'

Vue.use(Vuex)
export default function createStore() {
  return new Vuex.Store({
    modules: {
      auth,
      alert,
      post,
      category,
      like,
      size,
      comment
    }
  })
}

The state of each component includes state, getters, actions, and mutations fields, with Category component as an example

import { BASE_CATEGORY_URL } from '@/constants/API'
import { getNumberWithoutPostPositiveZero, getCategoryNumbers } from '@/utils/util'

export const LOAD_CATEGORIES = 'LOAD_CATEGORIES'
export const LOAD_CATEGORIES_ASYNC = 'LOAD_CATEGORIES_ASYNC'
const category = {
  state: {
    docs: []
  },
  getters: {
    categoryCount: state => state.docs.length,
    getCategoriesByNumber: state => state.docs.reduce((obj, t) => {
      obj[t.number] = t
      return obj
    }, {}),
    getCategoryByNumber: state => number => state.docs.find(doc => doc.number === number),
    getPosterityCategories: (state, getters) => number => {
      const reg = new RegExp(`^${getNumberWithoutPostPositiveZero(number)}`)
      return state.docs.filter(doc => {
        doc.titleDatas = getCategoryNumbers(doc.number).map(t => getters.getCategoriesByNumber[t].name)
        return String(doc.number).match(reg) && (doc.posts.length)
      })
    },
    getChildrenCategoryies: state => number => {
      const reference = String(getNumberWithoutPostPositiveZero(number))
      const len = reference.length
      const regExp = new RegExp(`^${reference}(0[1-9]|[1-9][0-9])(0){${8 - len}}`)
      return state.docs.filter(doc => String(doc.number).match(regExp))
    },
    getCategoryRootDatas: state => state.docs.filter(doc => Number(String(doc.number).slice(2)) === 0),
    getRecommendedCategories: state => state.docs.filter(t => t.recommend).sort((a, b) => a.index - b.index)
  },
  actions: {
    /* Get all category information */
    [LOAD_CATEGORIES_ASYNC]({ commit }) {
      return new Promise((resolve, reject) => {
        this._vm.$axios({
          commit,
          url: BASE_CATEGORY_URL,
          doHideAlert: true,
          success(result) {
            // Save class
            commit(LOAD_CATEGORIES, result.docs)
            // Notify the front-end that the operation was successful
            resolve(result.docs)
          },
          fail(err) {
            // Forward notification operation failed
            reject(err)
          }
        })
      })
    }
  },
  mutations: {
    /* Save Category Information */
    [LOAD_CATEGORIES](state, payload) {
      state.docs = payload
    }
  }
}
export default category

[Data transfer]

There are generally three ways to transfer data between components, one is to use props in vue and custom events, the other is to use params attributes of routing, and the other is to use vuex.

1. props and custom events

// BaseInput
<template>
  <input
    :class="$style.input"
    :value="value"
    autocomplete="off"
    autocapitalize="off"
    @input="$emit('input', $event.target.value)"
  >
</template>
<script>
export default {
  props: {
    value: { type: String, default: '' }
  }
}
</script>

// InputPassword
<input
  :class="$style.input"
  :placeholder="placeholder"
  :value="value"
  autocomplete="off"
  autocapitalize="off"
  type="password"
  @input="$emit('input',$event.target.value)"
>

2. params attributes of routing

// Post.vue
 <BaseBack @click.native="$router.push($route.params.parentPath || '/')">Return</BaseBack>

//AuthSign.vue
<template>
    <router-link
        :active-class="$style.active"
        :to="{ name: 'signin', params: { parentPath } }"
    >Deng&nbsp;record</router-link>
</template>
<script>
export default {
  computed: {
    parentPath() {
      const temp = this.$route.params.parentPath
      if (temp) {
        return temp
      }
      return ''
    }
  }
}
</script>

3. Use vuex

// Category.vue
<template>
  <article v-if="category" :class="$style.box">
    <BaseBack @click.native="$router.push('/categories')">Category list</BaseBack>
    <BaseTitle>{{ category.name }}Knowledge system</BaseTitle>
    ...
  </article>
</template>
<script>
export default {
  computed: {
    category() {
      return this.$store.getters.getCategoryByNumber(Number(this.paramsNumber))
    }
    ...
  }
}
</script>

 

Project optimization

[Offline Caching]

Implementing offline caching through service worker

const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin')

plugins: [
  new SWPrecacheWebpackPlugin({
    dontCacheBustUrlsMatching: /\.\w{8}\./,
    filename: 'service-worker.js',
    logger(message) {
      if (message.indexOf('Total precache size is') === 0) {
        return;
      }
      if (message.indexOf('Skipping static resource') === 0) {
        return;
      }
      console.log(message);
    },
    navigateFallback: 'https://www.xiaohuochai.cc',
    minify: true,
    navigateFallbackWhitelist: [/^(?!\/__).*/],
    dontCacheBustUrlsMatching: /./,
    staticFileGlobsIgnorePatterns: [/\.map$/, /\.json$/],
    runtimeCaching: [{
        urlPattern: '/',
        handler: 'networkFirst'
      },
      {
        urlPattern: /\/(posts|categories|users|likes|comments)/,
        handler: 'networkFirst'
      }
    ]
  })
]

[Add to Desktop]

Under andriod, the manifest.json file is added to the desktop, while IOS needs to set meta tags

<meta name="theme-color" content="#fff"/>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Front-end station">
<link rel="apple-touch-icon" href="/logo/logo_256.png">
<link rel="shortcut icon" href="/logo/favicon.ico">
<link rel="manifest" href="/manifest.json" />

// manifest.json
{
  "name": "Small Match Front End Station",
  "short_name": "Front-end station",
  "start_url": "/",
  "display": "standalone",
  "description": "",
  "theme_color": "#fff",
  "background_color": "#d8d8d8",
  "icons": [{
      "src": "./logo/logo_32.png",
      "sizes": "32x32",
      "type": "image/png"
    },
    {
      "src": "./logo/logo_48.png",
      "sizes": "48x48",
      "type": "image/png"
    },
    {
      "src": "./logo/logo_96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "./logo/logo_144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "./logo/logo_192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "./logo/logo_256.png",
      "sizes": "256x256",
      "type": "image/png"
    }
  ]
}

[Subpage refresh]

When the child page is refreshed, there may be a situation that the data passed from the parent can not be obtained. The author's treatment is to jump to the parent page.

mounted() {
  if (!this.comment && this.operate === 'update') {
    this.$router.push(`/posts/${this.postId}/comments`)
  } else {
    this.setTextAreaValue()
  }
}

[promise]

Adding Promise to actions to facilitate state change processing

[LOAD_COMMENTS_ASYNC]({ commit }, payload) {
  return new Promise((resolve, reject) => {
    this._vm.$axios({
      commit,
      data: payload,
      url: BASE_COMMENT_URL,
      doHideAlert: true,
      success(result) {
        // Save class
        commit(LOAD_COMMENTS, result.docs)
        // Notify the front-end that the operation was successful
        resolve(result.docs)
      },
      fail(err) {
        // Forward notification operation failed
        reject(err)
      }
    })
  })
}

[Component Sharing]

Because the elements used for editing and building components are the same, but the content is empty when building new components, and you need to add content when editing components, then you can reuse components.

// AddComment.vue
<CommentForm operate="add" />

//UpdateComment.vue
<CommentForm operate="update" />

[Cleaning up the environment]

If you use addEventListener to bind event handlers, clean up the environment in time when components are destroyed

mounted() {
  window.addEventListener('devicemotion', throttle(this.testShake))
}
beforeDestroy() {
  window.removeEventListener('devicemotion', throttle(this.testShake))
}

[Application and data separation]

Configuration data is used to separate data from application. Configuration data is mainly API call address, which is stored in the constants directory as a constant.

// API.js
let API_HOSTNAME
if (process.env.NODE_ENV === 'production') {
  API_HOSTNAME = 'https://api.xiaohuochai.cc'
} else {
  API_HOSTNAME = '/api'
}
export const SIGNUP_URL = `${API_HOSTNAME}/auth/signup`
export const SIGNIN_BYUSERNAME_URL = `${API_HOSTNAME}/auth/signin_by_username`
export const SIGNIN_BYPHONENUMBER_URL = `${API_HOSTNAME}/auth/signin_by_phonenumber`
export const VERIFICATE_URL = `${API_HOSTNAME}/auth/verificate`

export const BASE_USER_URL = `${API_HOSTNAME}/users`
export const BASE_POST_URL = `${API_HOSTNAME}/posts`
export const BASE_TOPIC_URL = `${API_HOSTNAME}/topics`
export const BASE_CATEGORY_URL = `${API_HOSTNAME}/categories`
export const BASE_LIKE_URL = `${API_HOSTNAME}/likes`
export const BASE_COMMENT_URL = `${API_HOSTNAME}/comments`

export const ADMIN_URL = 'https://admin.xiaohuochai.cc'

[Functional throttling]

Use function throttling for functions with higher trigger frequency

/**
 * Function throttling
 * @param {fn} function test(){}
 * @return {fn} function test(){}
 */
export const throttle = (fn, wait = 100) => function func(...args) {
  if (fn.timer) return
  fn.timer = setTimeout(() => {
    fn.apply(this, args)
    fn.timer = null
  }, wait)
}

[DNS pre-parsing]

DNS pre-parsing is implemented by setting meta tags

<link rel="dns-prefetch" href="//api.xiaohuochai.cc" />
<link rel="dns-prefetch" href="//static.xiaohuochai.site" />
<link rel="dns-prefetch" href="//demo.xiaohuochai.site" />
<link rel="dns-prefetch" href="//pic.xiaohuochai.site" />

[Image Lazy Loading and webp]

Using vue-lazyload plug-in to realize lazy loading of pictures and conversion of pictures into webp format under andriod system

Vue.use(VueLazyload, {
  loading: require('./assets/imgs/loading.gif'),
  listenEvents: ['scroll'],
  filter: {
    webp(listener, options) {
      if (!options.supportWebp) return
      const isCDN = /xiaohuochai.site/
      if (isCDN.test(listener.src)) {
        listener.src += '?imageView2/2/format/webp'
      }
    }
  }
})

 

Function realization

[Shake-and-shake effect]

Shake-and-shake effect is achieved mainly by monitoring devicemotion events

  mounted() {
    window.addEventListener('devicemotion', throttle(this.testShake))
  },
  beforeDestroy() {
    window.removeEventListener('devicemotion', throttle(this.testShake))
  },
  methods: {
    testShake(e) {
      const { x, y, z } = e.accelerationIncludingGravity
      const { lastX, lastY, lastZ } = this
      const nowRange = Math.abs(lastX - x) + Math.abs(lastY - y) + Math.abs(lastZ - z)
      if (nowRange > 80) {
        window.location.href = ADMIN_URL
      }
      this.lastX = x
      this.lastY = y
      this.lastZ = z
    }
  }

[gyroscopic effect]

The effect of gyroscope is mainly achieved by monitoring deviceorientation events.

  mounted() {
    // Monitoring gyroscope
    window.addEventListener('deviceorientation', throttle(this.changeBeta))
  },
  beforeDestroy() {
    // Cancel monitoring
    window.removeEventListener('deviceorientation', throttle(this.changeBeta))
  },
  methods: {
    changeBeta(e) {
      if (this.beta !== Math.round(e.beta)) {
        this.beta = Math.round(e.beta)
      }
    }
  }

[Slow-moving Ejection Layer]

There are two implementations of the transition pop-up layer, including transition and animation. The project is implemented by animation.

<UserMenuList v-if="doShowMenuList" :onExit="() => {doShowMenuList = false}"/>
@keyframes move {
  100% { transform: translateY(0); }
}
@keyframes opacity {
  100% { opacity: 1; }
}
.mask {
  opacity: 0;
  animation: opacity linear both .2s;
}
.list {
  transform: translateY(-100%);
  animation: move forwards .2s;
}

[icon management]

All icons are stored in the common/SVG directory in SVG format.

// SVGAdd.vue
<template>
  <svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
    <path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/>
    <path d="M0 0h24v24H0z" fill="none"/>
  </svg>
</template>

[axios function encapsulation]

Encapsulating axios function into async.js file under utils directory, integrating loading component and alert component into the whole data acquisition process of axios function

import { SHOW_LOADING, HIDE_LOADING, SHOW_ALERTTEXT, HIDE_ALERTTEXT } from '@/components/Alert/module'
import { SIGNOUT } from '@/components/User/module'
import axios from 'axios'

const async = {
  install(Vue) {
    Vue.prototype.$axios = ({ commit, url, method, data, headers, success, fail, doHideAlert }) => {
      // display loading
      commit(SHOW_LOADING)
      let axiosObj = url
      if (method) {
        axiosObj = { method, url, data, headers }
      }
      axios(axiosObj)
        .then(res => {
          const { message, result } = res.data
          // Close loading
          commit(HIDE_LOADING)
          // Display Success Tips
          !doHideAlert && commit(SHOW_ALERTTEXT, message)
          // 1 Close prompt automatically after seconds
          setTimeout(() => { commit(HIDE_ALERTTEXT) }, 1000)
          // Successful callback function
          success && success(result)
        })
        .catch(err => {
          // Close loading
          commit(HIDE_LOADING)
          if (err.response) {
            const { data } = err.response
            // Customization error
            if (data.code === 1) {
              commit(SHOW_ALERTTEXT, data.message)
              // System error
            } else if (data.code === 2) {
              commit(SHOW_ALERTTEXT, data.message)
              fail && fail(err)
              // Authentication error
            } else if (data.code === 3) {
              commit(SHOW_ALERTTEXT, data.message)
              commit(SIGNOUT)
              window.location.href = '/signin_by_username'
            } else {
              // Display error prompts
              commit(SHOW_ALERTTEXT, 'Server failure')
              // Callback function after failure
              fail && fail(err)
            }
          } else {
            // Display error prompts
            commit(SHOW_ALERTTEXT, 'Server failure')
            // Callback function after failure
            fail && fail(err)
          }
        })
    }
  }
}

export default async

[Directory Jump]

Using the scrollIntoView() method, when you click on a directory, the article jumps to the relevant section without changing the URL.

<ul :class="$style.list">
  <li
    v-for="(item, index) in titles"
    :key="item"
    :class="$style.item"
    @click="onChangeAnchor(`anchor${index+1}`)"
  >
    {{ index + 1 }},{{ item }}
  </li>
</ul>
methods: {
  onChangeAnchor(id) {
    document.getElementById(id).scrollIntoView({ behavior: 'smooth' })
  }
}

 

Compatible processing

Anchor point

When using anchor to jump inside a page, the URL changes, the page refreshes, and other browsers have no problem. However, the PWA desktop icon under ISO will jump to the safari browser

Using scrollIntoView() instead of anchor #, the page only skips and does not refresh. andriod supports setting smooth scroll behavior for scrollIntoView:'smooth', but IOS does not support it

[Page enlargement]

In IOS, input will zoom in when it gets the focus, and meta-setting user-scalable=no can cancel the zoom-in effect.

<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, shrink-to-fit=no">

[rounded corners]

Under IOS, when the input field only displays the bottom border, the bottom corner effect will appear. Setting border-radius:0 will do.

border-radius:0

[outline]

In android browser, when the input domain is in focus, the default outline effect is a circle of light yellow outline.

It can be removed by setting outline:none

outline: none

[Click Background]

On the mobile side, when clicking on clickable elements, a light blue background appears under android and a grey background appears under IOS.

By setting the - webkt-tap-hightlight-color attribute, you can cancel the background effect when clicking

* {
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}

[Local non-rolling]

Under IOS, there may be bug s that local scrolling is not smooth or even local non-scrolling.

It can be solved by setting the overflow-scrolling attribute to touch on this element.

div {
  -webkit-overflow-scrolling: touch;
}

Anchor point

When using anchor to jump inside a page, the URL changes, the page refreshes, and other browsers have no problem. However, the PWA desktop icon under ISO will jump to the safari browser

Using scrollIntoView() instead of anchor #, the page only skips and does not refresh. andriod supports setting smooth scroll behavior for scrollIntoView:'smooth', but IOS does not support it

Posted by NDK1971 on Sat, 11 May 2019 15:59:46 -0700