前端 - Client 篇
一. flag
- 开启打包分析:webpack-bundle-analyzer
- 开启 gzip
- 开启库 cdn
- 图片 cdn
- webpack 按需加载
- SSR
二、交互
- 向上滚导航显示,向下滚导航消失
- 页面滚动一定距离后,向上按钮出现
- 屏幕缩小出一个小按钮弹出目录
三. 解决的问题
1. vue-cli3 安装错误
注意,需设置 npm 源,改为淘宝源后错误减少,安装成功。
npm audit fix,npm@6之后 https://docs.npmjs.com/cli/audit
2. build 之后空白页
打开生成的 index.html 后,你会发现是空白,检查代码后,你会发现代码是“/app.js"这样的,部署到 http 服务器就好了,比如 nginx 。
在本地 docker run 一个 nginx 容器,非常方便。
3. vue.config.js
对于 vue.config.js 的相关配置,可以在 “node_modules/@vue/cli-service/lib/option.js” 中查看其结构以及默认配置。配合文档 Vue CLI 配置参考 阅读,效果更加。
其中 chainWebpack 的配置,在 vue inspect 这个命令输出的文件中,看看代码和注释就明白了。
4. 初步优化
首先配置:babal+router+vuex+eslint,均在 Chrome 无痕模式下。
5. 文件压缩
build 之后的文件是压缩的,不需要多加配置。
6. 安装 webpack-bundle-analyzer
① 其实在当前文件夹目录下输入命令,构建后会自动生成一个 report.html
。不需要安装此插件。
npx vue-cli-service build --report
相关资料:vue-cli-service-build,看文档的时候,看到这个 --report 参数就感觉是这个,果然 😁
具体逻辑代码:
② 上述方式需要自己打开文件,可通过一些步骤实现自动化。既然已经使用了 vue-cli3,具体包也有,或许改改哪里的配置就好?尝试不安装此插件,就地取材试试。
Github issue:vue-cli-service build --report #998
"scripts": {
"report": "vue-cli-service build --report"
},
③ 直接安装插件:重启 Server 后会启动一个小服务器(端口),自动弹出分析页面。安装方式:
会影响 docker-compose up - -build 构建,需注释掉,建议更加细分环境。
vue-cli3.0配置 webpack-bundle-analyzer 插件
https://github.com/webpack-contrib/webpack-bundle-analyzer/blob/master/README.md
npm intall webpack-bundle-analyzer –D
// vue.config.js 内添加
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
configureWebpack = {
return { // 返回一个将会被合并的对象
plugins: [
new BundleAnalyzerPlugin()
]
}
}
// 或者这样添加:
chainWebpack: config => {
config
.plugin('webpack-bundle-analyzer')
.use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
}
npm run serve
localhost:8888
7. 批量导入和备份 CDN
- JS CDN
- CSS CDN
发现某地方可以存数据,同时考虑到 CDN 失效的问题,并结合其他方案,设计了这样的结构。
externals
名字不能自定义,其中 key
值是包名,value
值是其导出的方法名称。
小技巧:在浏览器
Console
中,可测试。
// vue.config.js
configureWebpack: config => { // 此时 configureWebpakc 为函数
config.externals = {
'vue': 'Vue', // import Vue from 'vue'
'vue-router': 'VueRouter', // import VueRouter from 'vue-router'
'vuex': 'Vuex' // import Vuex from 'vuex'
}
}
此外如果遇到 "Vue is not defined" 等等. 请将 <script>
放在 index.html
的<head></head>
内部,而不是 body
后面。这是血的教训 😭 。
配置
在 vue.config.js
内配置数据:
module.exports = {
pages: { // 对象的 key 是入口名字,比如这里的 index, subpage
index: { // 此处是 HtmlWebpackPlugin 的参数,由此设想 CDN 存放位置
entry: 'src/main.js',
...
cdn: {
css: [],
js: [
{
name: externals['vue'], //将 externals 数据提出了
url: 'https://cdn.bootcss.com/vue/2.6.10/vue.runtime.min.js',
backUpUrl: '/js/vue@2.6.10.runtime.min.js'
}, ....
模板
public
内 index.html
的模板:
<head>
...
<!--引入 CDN,如果 CDN 挂掉就用本地文件-->
<% if (process.env.NODE_ENV === 'production') { %>
<% for(let js of htmlWebpackPlugin.options.cdn.js) { %>
<link href="<%=js.url%>" rel="preload" as="script">
<script src="<%=js.url%>"></script>
<script>
if (typeof window['<%=js.name%>'] == 'undefined') {
document.write("<script src='<%=js.backUpUrl%>'><\/script>")
}
</script>
<% } %>
<% } %>
</head>
构建生成效果
build
构建之后的效果:
网络加载情况
无CDN:
有CDN:
8. 开启 gzip
- 最小压缩值的最优解
compression-webpack-plugin
const CompressionPlugin = require('compression-webpack-plugin')
...
configureWebpack: config => { // object | Funtion,此时 configureWebpack 为函数
config.externals = externals // 直接修改
return { // 返回一个将会被合并的对象
new CompressionPlugin({ // gzip 的设置
threshold: 8192 // 只有大于此大小的文件才压缩,字节为单位
})
}
}
nginx
gzip on;
gzip_comp_level 4;
gzip_disable "MSIE [1-6]\.";
gzip_min_length 8192;
gzip_types application/javascript test/html text/css application/json image/svg+xml application/xml application/xhtml+xml text/plain application/x-javascript text/javascript;
gzip_static on;
网络加载情况
其他
本想测试最小压缩值最优为多少,咳咳, 不太好测试,测试数据不足。暂时先设置 8KB 了(Github文档上设置的是8KB)。
Window
下创建固定大小文件:
fsutil file createnew C:\test.js 10240
一段 bat 脚本,不过都是 0,压缩后基本一样大:
// 当前目录下建立 1KB - 10KB 的文件
for /l %%i in (1024,1024,10240) DO fsutil file createnew %cd%\test%%i.js %%i
9. 开启 Brotli
- 需要知道在 gz 和 br 文件中,谁先返回。应该是 br。
与 Gzip 相同,都是用于压缩,添加方式相同。
const BrotliPlugin = require('brotli-webpack-plugin')
...
configureWebpack: config => {
return {
new BrotliPlugin({
threshold: 8192
})
}
}
nginx.conf:
brotli on;
brotli_comp_level 5;
brotli_min_length 8192;
brotli_types application/javascript test/html text/css application/json image/svg+xml application/xml application/xhtml+xml text/plain application/x-javascript text/javascript;
brotli_static on;
brotli_window 512K;
10. Vuetify - UI 框架
vue-cli-plugin-vuetify
默认选项即可,会自动生成 src/plugins/vuetify.js
和修改对应文件;
记得在 plugins 内加上 new VuetifyLoaderPlugin()
//vuetify.js
import Vue from 'vue'
import Vuetify from 'vuetify/lib'
import 'vuetify/src/stylus/app.styl'
import 'material-design-icons-iconfont'
Vue.use(Vuetify)
//index.html
<link rel="stylesheet" href="https://cdn.bootcss.com/vuetify/1.1.15/vuetify.min.css">
② 安装 material-design-icons-iconfont
开发依赖,Material Design
图标;
https://vuetifyjs.com/en/framework/a-la-carte#vuetify-loader
//vuetify.js
import Vue from 'vue'
import Vuetify from 'vuetify/lib'
import 'vuetify/src/stylus/app.styl'
// 图标这里还有些问题
Vue.use(Vuetify, {
iconfont: 'md' // 'md' || 'mdi' || 'fa' || 'fa4'
})
滚动页面-组件显示与隐藏
vuetify 官方文档上的检测滚动事件示例并不成功,只写 v-scroll
就好。
<v-btn v-show="show" v-scroll="onScroll">按钮</v-btn>
小坑
文档应该是描述失当: https://vuetifyjs.com/en/framework/a-la-carte#vuetify-loader>
正确做法: https://github.com/vuetifyjs/vuetify-loader/issues/20
建议
这个框架很不错的,值得学习。多看文档,每个例子代码都理解下。有什么不会的去 Github issues 里面搜索。
11. Markdown
选择的是 marked.js 库,配合 highlight.js 进行代码高亮。
详细请查看项目:markdown-toc,相关代码。
- 生成对应目录
- 美化显示效果
- 文章锚点在哪里,目录哪个标签高亮
TOC
考虑的情况:标题并不是按顺序写的,也就是说,顺序是乱的:开局一个二级标题,接下来就是五级标题,三级标题这样。
规则:
* [index].level==当前level:栈顶如果是</li>,则弹出栈;输入<li>+text;推</li>入栈;index++。
* [index].level>当前level:如果栈顶是</ul>,则输入<li><ul>,推</li>入栈,推</ul>入栈;如果栈顶不是</ul>,则输入<ul>,推</ul>入栈;level++。
* [index].level<当前level:弹出 [(当前level-匹配level) * 2+1] 个值出栈;level=[index].level。
* index 达到最大,栈内数据全弹出。
美化
在 zhangjikai/markdown-css
的基础上,结合 github-markdown-css
进行配置。
- 修改与整理:
.markdown-body
, - 目录样式:自行添加了
.markdown-toc
。 - 自适应:px, em, rem:lib-flexible和px2rem-loader
联动-对应目录条高亮
- 对应目标条标题高亮:根据 md 中有锚点的标题与生成的 toc 目录数量相同,所以只需要一个监听,按照顺序递增或者递减即可。
- Vue 无法读取 HTMLCollection 列表的 length
mounted 不会承诺所有的子组件也都一起被挂载。地址
- 获得元素距离浏览器顶部距离(不是页面滚动距离)
w// 其中的 top 属性,超过则为负
getBoundingClientRect()
- style.color 后,hover 消失:!important
- 刷新页面后,index 变为原始值,需要根据 URL 初始化 index。
- 目录过长,对应目录不在可视区内,滚动
- 页面向下滚动,当导航消失不见时,目录紧贴页面上端
有关 CSS
在页面右侧设置了目录,有以下需求:
- 高度根据屏幕可视区域自适应
- 内部可滚动:右侧滚动条 auto
- 吸顶效果:导航不可见时,自动向上移动一定距离
- 右侧滚动条出现时,紧贴屏幕右侧
目录吸顶效果
采用的是 flex
布局,12 栅格。
<v-layou>
<v-flex xs11 offset-xs1>
<v-layout>
<v-flex md8> md </v-flex>
<v-flex md4> toc </v-flex>
</v-layout>
</v-flex>
</v-layout>
以下两钟方法看自己取舍,我根据目前的需求,采用了 fixed
的方式。
① position: fixed
将 postion
设置为 fixed
,则其高度为 window.innerHeight
,宽度为 window.innerWeight
。
position: fixed;
height: 70%;
overflow-y: auto;
width: calc(30.5555%);
toc
目录区域的宽度计算:
(100 % *(11 / 12))*(4 / 12) // 30.555···%
吸顶效果:
// v-Scroll:监听滚动条滚动
let nowTop = document.documentElement.scrollTop + document.body.scrollTop;
this.oMarkdown.chain.tocDOM.style.marginTop = nowTop > 64 ? "-64px" : "0"
② postion: sticky
它的吸顶效果是天生的,无需监听滚动条,会自动吸顶。需要注意兼容性。
position: sticky;
top: 3em;
高度是容器的高度,无法根据屏幕来自适应,需要计算。
// 初始化设置高度,且 v-resize:监听页面缩放
height = window.innerHeight * 0.7 + "px"
宽度是容器的宽度,计算比较方便,但吸顶后会同 position: fixed
,需要重新设置宽度,这好像也得监听,要不怎么知道吸顶了...
width: calc(100%);
// 吸顶后需要重新设置宽度
width: calc(30.5555%);
12. 懒加载
v_layzy
13. 刷新空白
history-mode.html#后端配置例子/nginx,加上 try_files
即可
location / {
root /var/www/website/client/dist; # html 文件夹位置
try_files $uri $uri/ /index.html; # 解决路由 history 模式下刷新 404
}
14. 交互
-
向上滚导航显示,向下滚导航消失
-
页面滚动一定距离后,向上按钮出现
-
屏幕缩小出一个小按钮弹出目录
有时间把网站统计加进去。
滚动页面-组件显示与隐藏
vuetify 官方文档上的检测滚动事件示例并不成功。只写 v-scroll
就好。
<v-btn v-show="show" v-scroll="onScroll">按钮</v-btn>
onScroll() {
let top = document.documentElement.scrollTop + document.body.scrollTop
this.show = top > 150 ? true : false
}
跳转 && 滚动
- 根据
URL
地址跳转,如果有#
,则滚动至对应锚点位置(文章页) - 浏览文章内容时:
- 对应目标标题高亮
- 若目录标题过多:
- 目录区域会自动滚动
- 高亮标题置于中间(首尾除外)
- 目录区域可手动滚动
- 点击文章目录标题后:
- 页面滚动至对应文章内容
- 目录标题颜色切换
- 修改
window.location.hash
- 均平滑滚动
- 无标题情况
- 滚动兼容?
小坑:如果不设置延时,根本不会跳转。具体原因暂时不明。以及 scrollIntoView
被目录区域滚动条滚动所影响,换成了 window.scrollTo
mounted() {
if(this.$route.hash) {
setTimeout(()=> {
let anchor = decodeURI(this.$route.hash).slice(1)
document.getElementById(anchor).scrollIntoView()
}, 1000)
}
}
同时如果给 scrollIntoView
设置缓慢动画,但滚动距离太远,它就不会完全到达。
似乎是 window.location.hash 与 scrollIntoView 之间的冲突。或许 scrollIntoView 是 window.location.hash 的补充?加了动画?
并不改变 URL
中途 hash 改变会停止
anchor.addEventListener('click', function (e) {
e.preventDefault();
document.querySelector(this.getAttribute('href')).scrollIntoView({
behavior: 'smooth'
})
setTimeout(()=> {
window.location.hash = this.getAttribute('href')
})
})
通过属性来传递值。index
如果 addEventListener,()=>传递当前的this,如果是匿名函数里面就是它自己的this,可以获取属性,所以很蛮烦。两个不互通。 只能通过匿名函数获得属性再在里面调用函数传递。
this.tocDOM.querySelectorAll('a[href^="#"]') // NodeList
this.tocDOM.getElementsByTagName('a') // HTMLCollection
offsetTop // 它距离父元素顶部的距离
getBoundingClientRect().top // 距离相对于浏览器顶部的距离
15. npx
从 npm
的 v5.2
版本增加。
目标:调用项目内部安装的模块
16. 部署 url
本地其服务没问题,但是 dist 之后请求的 localhost:3000 就会出现问题,需要配置 nginx,修改 axios。
- PC 端访问没问题,移动端有问题(请求不到数据,请求的是 localhost:3000)
17. Chrome://inspect调试
- 科学上网
- USB 调试
18. 跨域
process.env.NODE_ENV
- .env.production
- .env.development
axios, nginx
在本地,直接访问 localhost:3000 获取 api,在服务端,接口请求 /api/,直接在路由加 /api/就是了
本地服务没问题,但是 dist 之后请求的 localhost:3000 就会出现问题,需要配置 nginx,修改 axios。
pc跨域成功,移动端不可以,感觉是请求地址发生的问题,访问的是 /post/,而不是设置的 /api/post
移动端失效 PC 正常访问
其实这个问题并不是跨域导致的,而是请求路径。
在解决跨域问题时,用真机测试发现的问题。通过此问题对
Nginx
和网络熟练了些~
① 场景:通过 ipconfig
找到 IPv4
地址,手机访问 192.168.1.123
打开页面
无线局域网适配器 WLAN:192.168.1.123
问题:手机获取不到 api
的数据,请求 api
时 axios
的 baseURL
是 localhost:3000/api
关键:手机访问的 localhost
并不是电脑端的 localhost
!手机有自己的 localhost
!
解决:将 localhost:3000/api
改为 192.168.1.123:3000/api
② 场景: Nginx
配置反向代理,将 evgo2017.com/api
转发至本机 3000
端口
问题:localhost
与 127.0.0.1
均无效,内网 ip
有效
关键:用的是 Docker
啊亲
解决:写作容器名称或采用内网 ip
location /api {
proxy_pass http://evgo-server:3000;
}
如果有配置 HTTPS
:
① 复制一份 proxy
过去;
② proxy_pass
请求协议为 http
,不需要改成 https
;
③ 建议 axios
的 baseURL
请求协议改为 https
。
此外,下面两个地址是不一样的,注意 /
!
proxy_pass http://localhost:3000;
proxy_pass http://localhost:3000/;
19. require
fs.readFile 能请求到文件,但是 require 就不可以
20. axios
配置
异步
21. 向上按钮可拖动 - 指令
功能:
-
点击向上滚动
-
可拖拽
- PC 端 drag
- 移动端 touch
- 不可超出可视范围
-
屏幕尺寸变小:
- 按钮位置不在可视宽度内,修改其宽度
- 按钮位置不在可视高度内,修改其高度
-
浏览器效果测试
-
整理元素宽高距离等
-
盒子模型
推动效果:el.style.left = e.clientX + 'px', 鼠标推着走
① 推动太慢:原生JS快速拖动元素失效问题
mousemove 绑定在 body 或 document 上,会很流畅,而 div 自己身上会跟不上
② 移动卡顿:v-btn class 的问题
自己使用 div 模拟了此按钮
由知乎的下一个回答按钮想到的
https://juejin.im/post/5afbe5ad51882542ba07f21f
https://cn.vuejs.org/v2/guide/custom-directive.html#ad
代码复用和抽象的主要形式是组件
对普通DOM进行底层操作->自定义指令
推动效果:el.style.left = e.clientX + 'px', 鼠标推着走
推动太慢:
mousemove 绑定在 body 或 document 上,会很流畅
而 div 自己身上会跟不上
https://segmentfault.com/a/1190000015313127
触摸事件:
touchstart, move,cancel
拖动时会滚动屏幕:
chrome 默认 passive: true,忽略 event.preventDefault(),提高性能
css:style="touch-action: none;"
document.addEventListener('touchmove', callback, {passive: false})
但是禁止滚动后,移动图标会很慢
我以为是著名的300ms问题,结果经过测试
无论禁止不禁止滚动,每次移动的间隔时间基本都差不多
那么是设置left的问题?
结果显示时间戳差值不过1
touchmove 是在不停的计算的,没一点问题
甚至差值很大的时候也很快设置好了
但是为什么显示这么慢?去掉passive: false就很快?
我突然想到,e.preventDefault();
禁止了
那么原先的是否有动画?
慢速的移动并没有什么问题,突然一大截的距离,
没有缓冲的感觉,直接就过去,这不就会表现出“卡顿”?
此外原先是有 click 事件的(回到顶部)
在手机上绑定了 touch 事件,300ms,触发
https://blog.csdn.net/u014346301/article/details/53536714
可能是 chrome 的问题!用手机的打开这个页面没问题!
用可以正常移动的代码测试,变得很卡了!
在APP.vue里面测试div2,正常,但加到组件TheGoTop就慢了,注销掉组件GoTop内部,正常了,最后问题在于 v-btn!
似乎是 v-btn__content class 的问题!加上去就卡了!
mouseup 等会触发 click
为了拖动换成了 drag
切记在元素上加上 "draggable='true'"
移动位置,缩小屏幕,不见
vue 获取 dom
ref='btn'
this.$refs.btn.style.left = "0px"
this.$refs.btn.style.top = "16px"
inherit
字面量声明的当时是无法获取到对象的。
获取滚动条宽度
window.innerWidth - body.clientWidth 是滚动条宽度,无论是否有y轴滚动条
但是 window.innerHeight - body.clientHeight 却不是,收到x轴滚动条影响
22. 各元素宽高
document.body.scroll\client\offset
window.inner\outer
screen.width\availWidth
document.scrollingElement.clientWidth
滚动条:window.innerWidth - document.scrollingElement.clientWidth
23. lint Error ELIFYCYCLE
出现这个报错,但是不合规范的代码依旧被美化了,说明它是在工作的。
https://github.com/vuejs/vue-cli/issues/3224
经过查询,发现也许是我的代码有问题,出现了 ESLint 不可修复的问题。
改掉之前所有的报错后,在 plugins 内加上 new VuetifyLoaderPlugin(),之前以为是不需要加的,今天再看了看文档,昂,我错了。
之后所有错误都没啦,ELIFYCYCLE 也没有了。