转载: 使用vue全家桶制作博客网站 HTML5 移动网站制作的好教程

news/2024/5/15 16:04:02/文章来源:https://blog.csdn.net/weixin_34249678/article/details/93370720

使用vue全家桶制作博客网站

前面的话

  笔者在做一个完整的博客上线项目,包括前台、后台、后端接口和服务器配置。本文将详细介绍使用vue全家桶制作的博客网站

 

概述

  该项目是基于vue全家桶(vue、vue-router、vuex、vue SSR)开发的一套博客前台页面,主要功能包括首页显示、认证系统、文章管理、评论管理和点赞管理

【访问地址】

  域名:https://xiaohuochai.cc

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

  或者可以直接扫描二维码访问

【项目介绍】

  该项目的内容以笔者自学前端的过程中写的600多篇博客为基础,对于同样学习前端的同学可能会有所帮助。许多博客都有直接可以操作的DEMO,对知识的理解可能会更直观

  采用移动优先的响应式布局,移动端、桌面端均可适配;字体大小使用em单位,桌面端的文字相应变大;移动端可使用滑屏操作,桌面端通过光标设置、自定义滚动条、回车确定等,提升交互体验

  全站采用服务器端渲染SSR的方式,有利于SEO,减少了首屏渲染时间;使用service worker和manifest实现了PWA方案的离线缓存和添加到桌面的功能

  根据HTML标签内容模型,使用语义化标签,尽量减少标签层级,尽量减少无语义的div标签

  CSS大量使用类选择器,尽量减少选择器层级,在vue组件中使用CSS module和postCSS,使用styleLint规范CSS代码,按照布局类属性、盒模型属性、文本类属性、修饰类属性的顺序编写代码,并使用order插件进行校验

  使用esLint规范JS代码,代码风格参照airbnb规范,所有命名采用驼峰写法,公共组件以Base为前缀,事件函数以on为前缀,异步函数以async为后缀,布尔值基本以do或is为前缀

  没有引用第三方组件库,如bootstrap或element组件,而是自己开发了项目中所需的公共组件。在common目录下,封装了头像、全屏、loading、遮罩、搜索框、联动选择等组件,方便开发

  使用配置数据,实现了数据和应用分离,以常量的形式存储在constants目录下

  使用了阿里云的短信模块,实现了短信验证功能

  该项目有两个隐藏彩蛋,一个是摇一摇功能,可以直接摇到后台页面,另一个是陀螺仪功能,上下晃动手机时,头像会进行旋转

  项目进行了代码优化,最终优化评分如下所示

 

功能演示

  主要功能包括首页显示、认证系统、文章管理、评论管理和点赞管理

【首页显示】

  首页包括可拖拽轮播图、专题推荐、文章推荐和类别推荐

【认证系统】

   认证系统包括用户注册、用户登录、短信验证

  1、用户处于未登录态时,可以阅读文章,但不能点赞和评论,否则会弹出登录框

  2、用户注册

  3、用户登录

【文章管理】

  文章管理包括浏览推荐文章、按类别筛选、文章搜索、按目录查看

  1、浏览推荐文章

  2、文章筛选

  3、文章搜索

  4、按目录查看

【点赞管理】

【评论管理】

  评论管理包括查看评论、添加评论、修改评论和删除评论

 

目录结构

  src目录下,包括assets(静态资源)、common(公共组件)、components(功能组件)、constants(常量配置)、router(路由)、store(vuex)和utils(工具方法)这7个目录

复制代码
- assets // 存放静态资源,主要是图片-imgscss.png // CSS文章背景图...
- common // 存放公共组件-SVG // 存放VUE图标组件SVGAdd.vue // "添加到"按钮SVGBack.vue // "返回"按钮...BaseArticle.vue // 文章组件BaseAvatar.vue // 头像组件...
- components // 存放功能组件-Post // 文章组件      module.js //文章状态管理    Post.vue // 文章显示组件PostContent.vue // 文章目录组件PostList.vue // 文章列表组件SearchPost.vue // 搜索文章组件...
- constants // 存放常量配置API.js // 存放API调用地址
- router // 存放路由index.js 
- store // 存放vuexindex.js
- utils // 存放工具方法async.js // axios方法fnVarificate.js // 表单验证方法util.js // 其他工具方法
复制代码

【公共组件】

  没有引用第三方组件库,如bootstrap或element组件,而是自己开发了项目中所需的公共组件

  封装了文章组件、头像组件、返回组件、按钮组件、卡片组件、全屏组件、输入框组件、loading组件、遮罩组件、搜索框组件、多行输入框组件、标题组件、面包屑组件、按钮组组件、反色按钮组件、密码框组件、包含检测的输入框组件和联动选择组件

复制代码
BaseAdd.vue // "添加到"组件
BaseArticle.vue  // 文章组件
BaseAvatar.vue // 头像组件
BaseBack.vue // 返回组件
BaseButton.vue // 按钮组件
BaseCard.vue // 卡片组件
BaseFullScreen.vue // 全屏组件
BaseInput.vue  // 输入框组件
BaseLoading.vue  // loading组件
BaseMask.vue // 遮罩组件
BaseSearchBox.vue  // 搜索框组件
BaseTextArea.vue // 多行输入框组件
BaseTitle.vue  // 标题组件
BreadCrumb.vue // 面包屑组件
ButtonBox.vue  // 按钮组组件
ButtonInverted.vue // 反色按钮组件
InputPassword.vue  // 密码框组件
InputWithTest.vue // 包含检测的输入框组件
LinkageSelector.vue // 联动选择组件
复制代码

【功能组件】

  按照功能来设置目录,如下所示

复制代码
弹出框(Alert)
类别管理(Category)
评论管理(Comment)
主页(Home)
点赞管理(Like)
文章管理(Post)
页面尺寸(Size)
公共头部(TheHeader) 用户管理(User)
复制代码

 

整体思路

【全屏布局】

  使用设置高度的全屏布局方式,主要通过calc来实现

复制代码
<divid="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;
}
复制代码

【层级管理】

  项目的层级z-index,只使用0-3

  全屏的弹出框优化级最高,设置为3;侧边栏设置为2;页面元素默认为0,如有需要,要设置为1

【全局弹出层】

  在入口文件App.vue中设置全局的弹出层和loading,所有组件都可以共用

复制代码
// App.vue
<template><divid="root":class="$style.wrap":style="{height:wrapHeight+'px'}"><AlertWithLoading v-show="doShowLoading" /><AlertWithTextv-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>
复制代码

【路由管理】

  vue-router使用静态路由表的形式对路由进行管理,虽然没有react-router-dom灵活,但方便寻找,一目了然

  按路由设置按需加载组件,并设置滚动行为

复制代码
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'},// 注册{path: '/signup',component: () => import(/* webpackChunkName:'user' */ '@/components/User/AuthSignup'),name: 'signup'},// 按手机号登录{path: '/signin_by_phonenumber',component: () => import(/* webpackChunkName:'user' */ '@/components/User/AuthSigninByPhoneNumber'),name: 'signin_by_phonenumber'},// 按用户名登录{path: '/signin_by_username',component: () => import(/* webpackChunkName:'user' */ '@/components/User/AuthSigninByUsername'),name: 'signin_by_username'},// 用户页面{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 }}})
}
复制代码

【状态管理】

  每个组件的状态管理命名为module.js,保存在当前组件目录下

复制代码
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}})
}
复制代码

  每个组件的状态包括state、getters、actions和mutations字段,以Category组件为例

复制代码
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] = treturn 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.lengthconst 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: {/* 获取全部类别信息 */[LOAD_CATEGORIES_ASYNC]({ commit }) {return new Promise((resolve, reject) => {this._vm.$axios({commit,url: BASE_CATEGORY_URL,doHideAlert: true,success(result) {// 保存类别commit(LOAD_CATEGORIES, result.docs)// 向前端通知操作成功resolve(result.docs)},fail(err) {// 向前端通知操作失败reject(err)}})})}},mutations: {/* 保存类别信息 */[LOAD_CATEGORIES](state, payload) {state.docs = payload}}
}
export default category
复制代码

【数据传递】

  组件间的数据传递方式一般有三种,一种是使用vue中的props和自定义事件,另一种是使用路由的params属性,还有一种是通过vuex

  1、props和自定义事件

复制代码
// 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属性

复制代码
// Post.vue<BaseBack @click.native="$router.push($route.params.parentPath || '/')">返回</BaseBack>//AuthSign.vue
<template><router-link:active-class="$style.active":to="{ name: 'signin', params: { parentPath } }">登&nbsp;录</router-link>
</template>
<script>
export default {computed: {parentPath() {const temp = this.$route.params.parentPathif (temp) {return temp}return ''}}
}
</script>
复制代码

  3、使用vuex

复制代码
// Category.vue
<template><article v-if="category" :class="$style.box"><BaseBack @click.native="$router.push('/categories')">类别列表</BaseBack><BaseTitle>{{ category.name }}知识体系</BaseTitle>...</article>
</template>
<script>
export default {computed: {category() {return this.$store.getters.getCategoryByNumber(Number(this.paramsNumber))}...}
}
</script>
复制代码

 

项目优化

【离线缓存】

  通过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'}]})
]
复制代码

【添加到桌面】

  andriod下,通过设置manifest.json文件添加到桌面,而IOS则需要设置meta标签

复制代码
<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="前端小站">
<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": "小火柴的前端小站","short_name": "前端小站","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"}]
}
复制代码

【子页面刷新】

  子页面刷新时,可能会出现得不到从父级传递过来的数据的情况,笔者的处理是跳转到父级页面

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

【promise】

  为actions添加Promise,方便状态改变后的处理

复制代码
[LOAD_COMMENTS_ASYNC]({ commit }, payload) {return new Promise((resolve, reject) => {this._vm.$axios({commit,data: payload,url: BASE_COMMENT_URL,doHideAlert: true,success(result) {// 保存类别commit(LOAD_COMMENTS, result.docs)// 向前端通知操作成功resolve(result.docs)},fail(err) {// 向前端通知操作失败reject(err)}})})
}
复制代码

【组件共用】

  由于编辑和新建组件用到的元素是一样的,只不过,新建组件时内容为空,编辑组件时需要添加内容,这时就可以复用组件

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

【清理环境】

  如果使用addEventListener绑定了事件处理函数,在组件销毁的时候,要及时清理环境

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

【应用和数据分离】

  使用配置数据,实现数据和应用分离,配置数据主要是API调用地址,以常量的形式存储在constants目录下

复制代码
// 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'
复制代码

【函数节流】

  为触发频率较高的函数使用函数节流

复制代码
/*** 函数节流* @param {fn} function test(){}* @return {fn} function test(){}*/
export const throttle = (fn, wait = 100) => function func(...args) {if (fn.timer) returnfn.timer = setTimeout(() => {fn.apply(this, args)fn.timer = null}, wait)
}
复制代码

【DNS预解析】

  DNS预解析通过设置meta标签实现

<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" />

【图片懒加载和webp】

  通过vue-lazyload插件实现图片懒加载和andriod系统下图片转换成webp格式

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

 

功能实现

【摇一摇效果】

  摇一摇效果主要通过监测devicemotion事件实现

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

【陀螺仪效果】

  陀螺仪效果主要通过监测deviceorientation事件实现

复制代码
  mounted() {// 监测陀螺仪window.addEventListener('deviceorientation', throttle(this.changeBeta))},beforeDestroy() {// 取消监测window.removeEventListener('deviceorientation', throttle(this.changeBeta))},methods: {changeBeta(e) {if (this.beta !== Math.round(e.beta)) {this.beta = Math.round(e.beta)}}}
复制代码

【缓动弹出层】

  过渡弹出层有两种实现方式,包括transition和animation,该项目使用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;
}
复制代码

【图标管理】

  所有的图标都使用SVG格式,存储在common/SVG目录下

复制代码
// 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函数封装】

  封装axios函数到utils目录下的async.js文件中,将loading组件、alert组件整合到axios函数的整个数据获取过程中

复制代码
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 }) => {// 显示loadingcommit(SHOW_LOADING)let axiosObj = urlif (method) {axiosObj = { method, url, data, headers }}axios(axiosObj).then(res => {const { message, result } = res.data// 关闭loadingcommit(HIDE_LOADING)// 显示成功提示!doHideAlert && commit(SHOW_ALERTTEXT, message)// 1秒后自动关闭提示setTimeout(() => { commit(HIDE_ALERTTEXT) }, 1000)// 成功后的回调函数success && success(result)}).catch(err => {// 关闭loadingcommit(HIDE_LOADING)if (err.response) {const { data } = err.response// 自定义错误if (data.code === 1) {commit(SHOW_ALERTTEXT, data.message)// 系统错误} else if (data.code === 2) {commit(SHOW_ALERTTEXT, data.message)fail && fail(err)// 认证错误} else if (data.code === 3) {commit(SHOW_ALERTTEXT, data.message)commit(SIGNOUT)window.location.href = '/signin_by_username'} else {// 显示错误提示commit(SHOW_ALERTTEXT, '服务器故障')// 失败后的回调函数fail && fail(err)}} else {// 显示错误提示commit(SHOW_ALERTTEXT, '服务器故障')// 失败后的回调函数fail && fail(err)}})}}
}export default async
复制代码

【目录跳转】

  使用scrollIntoView()方法,点击目录时,文章跳转到相关部分,且不改变URL

复制代码
<ul :class="$style.list"><liv-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' })}
}

 

兼容处理

【锚点】

  使用锚点进行页面内跳转时,URL发生改变,页面刷新,其他浏览器没有问题。但是,ISO下的PWA桌面图标会跳转到safari浏览器中

  使用scrollIntoView()方法来替代锚点#,页面内只跳转不刷新。andriod下支持给scrollIntoView设置平滑滚动behavior: 'smooth',但IOS不支持

【页面放大】

  IOS下,input获取焦点时会放大,meta设置user-scalable=no,可取消放大效果

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

【圆角】

  IOS下,input域只显示底边框时,会出现底边圆角效果,设置border-radius:0即可

border-radius:0

【轮廓outline】

  android浏览器下,input域处于焦点状态时,默认会有一圈淡黄色的轮廓outline效果

  通过设置outline:none可将其去除

outline: none

【点击背景】

  在移动端,点击可点击元素时,android下会出现淡蓝色背景,IOS下会出现灰色背景

  可以通过-webkt-tap-hightlight-color属性的设置,取消点击时出现的背景效果

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

【局部不滚动】

  IOS下,可能会出现局部滚动不流畅,甚至局部不滚动的bug

  通过在该元素上设置overflow-scrolling属性为touch即可解决

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

【锚点】

  使用锚点进行页面内跳转时,URL发生改变,页面刷新,其他浏览器没有问题。但是,ISO下的PWA桌面图标会跳转到safari浏览器中

  使用scrollIntoView()方法来替代锚点#,页面内只跳转不刷新。andriod下支持给scrollIntoView设置平滑滚动behavior: 'smooth',但IOS不支持

 

https://www.cnblogs.com/xiaohuochai/p/9228543.html

转载于:https://www.cnblogs.com/porter/p/9232553.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_723795.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CDN网站加速原理

2019独角兽企业重金招聘Python工程师标准>>> CDN加速将网站的内容缓存在网络边缘&#xff08;离用户接入网络最近的地方&#xff09;&#xff0c;然后在用户访问网站内容的时候&#xff0c;通过调度系统将用户的请求路由或者引导到离用户接入网络最近或者访问效果的…

Linux 服务器建站新手教程(宝塔建站全流程)-不需要敲一行命令

Linux 服务器配置、运行、不用敲命令WordPress 建站攻略 本文提供全图文流程&#xff0c;中文翻译。 Chinar 坚持将简单的生活方式&#xff0c;带给世人&#xff01;&#xff08;拥有更好的阅读体验 —— 高分辨率用户请根据需求调整网页缩放比例&#xff09; Chinar —— 心分…

高大上网站-CSS3总结1-图片2D处理以及BUG修复

高大上网站-CSS3总结1-图片2D处理以及BUG修复 一&#xff0c;前言&#xff1a; 现在的前端UI相对JS来说&#xff0c;重视并不够。 但是CSS3提供的新特性&#xff0c;将现在的网站赤裸裸的划分为两类&#xff1a;一类还在写着老旧样式&#xff0c;或者通过bootstrap来蹭点CSS3动…

Apache实现一台服务器上运行多个网站

总共有三种方法&#xff1a;通过不同的IP地址 通过不同的域名 通过不同的端口号 (1).通过不同的IP地址实现 例如一台CentOS7有两个IP&#xff1a;192.168.5.101和192.168.5.103 [rootyouxi1 conf]# cd /etc/httpd/conf.d/ [rootyouxi1 conf.d]# vim test.conf  //新建一个配置…

大型网站技术架构(五)网站高可用架构

2019独角兽企业重金招聘Python工程师标准>>> 网站的可用性&#xff08;Avaliability&#xff09;描述网站可有效访问的特性。 1、网站可用性的度量与考核 网站不可用时间&#xff08;故障时间&#xff09;故障修复时间点-故障发现&#xff08;报告&#xff09;时间点…

网站HTTP升级HTTPS完全配置手册

本文由葡萄城技术团队于51CTO原创并首发转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。今天&#xff0c;所有使用Google Chrome稳定版的用户迎来了v68正式版首个版本的发布&#xff0c;详细版本号…

SEO实战密码:60天网站流量提高20倍(第2版)

SEO实战密码:60天网站流量提高20倍(第2版)(Zac告诉你他所知道的关于SEO的一切) 编辑推荐 畅销书升级版&#xff01;  《SEO实战密码——60天网站流量提高20倍&#xff08;第2版&#xff09;》第1版荣获2011年度电子工业出版社最畅销图书奖。   Zac告诉你他所知道的关于SEO的…

您要访问的网站包含欺诈信息

版权声明&#xff1a;本文为 testcs_dn(微wx笑) 原创文章&#xff0c;非商用自由转载-保持署名-注明出处&#xff0c;谢谢。 https://blog.csdn.net/testcs_dn/article/details/80863960 不管是什么网站&#xff0c;安全你自己都有责任&#xff0c;没有绝对的安全。

Scrapy使用随机User-Agent爬取网站

小哈.jpg在爬虫爬取过程中&#xff0c;我们常常会使用各种各样的伪装来降低被目标网站反爬的概率&#xff0c;其中随机更换User-Agent就是一种手段。 在scrapy中&#xff0c;其实已经内置了User-Agent中间件&#xff0c; class UserAgentMiddleware(object):"""…

【JAVA系列】使用JavaScript实现网站访问次数统计代码

公众号&#xff1a;SAP Technical本文作者&#xff1a;matinal原文出处&#xff1a;http://www.cnblogs.com/SAPmatinal/ 原文链接&#xff1a;【JAVA系列】使用JavaScript实现网站访问次数统计代码前言部分 大家可以关注我的公众号&#xff0c;公众号里的排版更好&#xff0c;…

夺命雷公狗—玩转SEO---33---DEDE网站安全

很多人都说dede不安全好多黑阔会黑掉她&#xff0c;但是说句实话&#xff0c;小弟10几年前就开始玩黑阔了&#xff0c;就连黑阔站长也是10年前就开始做了而且还是做过三个黑阔站点会员人数巅峰时期3万&#xff0c;同时在线超5000&#xff0c;时间流逝&#xff0c;我就不想多说以…

夺命雷公狗—玩转SEO---34---DEDE浅入百度搜索资源平台抓取和网站地图提交

百度搜索资源平台的地址是&#xff1a;https://ziyuan.baidu.com &#xff0c;注册号会员账户后即可进入平台~~&#xff01; 登录后&#xff0c;我们需要对网站进行验证&#xff0c;验证过后&#xff0c;我们来到抓取诊断&#xff1a; 抓取成功了&#xff0c;说明我们的网站可…

夺命雷公狗—玩转SEO---35---DEDE快排之LOGO和友情链接的玩法

LOGO是每个网站都必须要有的&#xff0c;所以我们需要用到一个技术&#xff0c;那么就是关键词与图片分离操作&#xff0c;从而提升排名~~&#xff01; 我们首先需要做的就是找到logo 的标签&#xff0c;然后给 logo 的 a 标签外加上一个 div 。 原本我们的是这样的&am…

夺命雷公狗—玩转SEO---36---只需3步实现判断关键词难易度

我们在做排名的前期必须要先进行判断我们的词难度是否大~ ~&#xff01; 第一我们打开百度指数 index.baidu.com&#xff1a; 对于一个企业站或者一个地区或者全国词来说200-300左右都是好平均的&#xff08;难易度正常&#xff09;200以下的偏简单的&#xff0c;我们的茂名旅…

夺命雷公狗—玩转SEO---37---查询网站收录且进行提升

我们经常需要查询我们的网站收录的情况&#xff0c;查询方法其实也很简单&#xff0c;通过 site: 即可进行查询&#xff0c;如下所示&#xff1a; 这些事目前已经被百度收录的情况&#xff0c;然后我们使用 尖叫青蛙 来进行查询&#xff0c;如下所示&#xff1a; 我们在左…

夺命雷公狗—玩转SEO---38---百度快照投诉从而更新快照

我们网页排名的是一个快照&#xff0c;使用我们网站上如果发生修改之后&#xff0c;我们需要及时通知搜索引擎爬虫过来索引新内容的快速操作~~&#xff01; 正常来说蜘蛛他会在1周爬行一次&#xff0c;所以我们需要及时通知搜索引擎~ 操作方法其实也很简单&#xff0c;有两种方…

夺命雷公狗—玩转SEO---39---nofollow玩法的奥秘

nofollow其实是放在A标签里面的一个属性~~&#xff01; <a rel“nofollow”></a> nofollow 其实有一个较大的优势是在于防止权重的流失~~&#xff01; 列子&#xff1a; 比如我们自家的网站地址是www.usbdy.com &#xff0c;我们的的A标签中的地址是www.baidu.co…

夺命雷公狗—玩转SEO---40---提升网站出图率的技巧

出图&#xff0c;他分为两种&#xff0c;首页和内容页出图~~&#xff01; 首页我们最常见的就是LOGO图片了&#xff0c;我们的LOGO如果可以&#xff0c;尽量按照百度的要求上的规则来玩&#xff1a; 我们可以进入百度站长平台进行查询一下规则来玩&#xff0c;PC站点的LOGO比率…

夺命雷公狗—玩转SEO---41---H1标签的玩法

网站中除了 tiitle 标签后就到H标签了&#xff0c;这是一个比较重要的标签&#xff0c;因为蜘蛛会按照等级进行划分网站内容的重要性的&#xff0c;如先是 titile &#xff0c;然后就到H标签了。 H标签分为H1-H6&#xff0c;但是我们最多用到H3即可~~&#xff01; H标签…

夺命雷公狗—玩转SEO---42---快速交换友情链接

我们常见的交换友情链接的方式有别人网站上直接联系对方的QQ找站长1对1的进行交换&#xff0c;然后还有就是去找交换友情链接相关的QQ群&#xff0c;其实我们也可以快速通过一些平台进行换区&#xff0c;如下&#xff1a; 推荐1.链天下&#xff0c;http://www.huanlj.com&#…