前言
文章总结了项目开发中遇到的问题以及解决方案。
CDN是怎么在项目中发挥作用以及怎么使用呢?
CDN(内容分发网络)指请求资源的方式,即通过script头去请求对应的脚本资源的一种方式,项目里配置之后不需要通过npm
包管理工具去下载配置的包。
目的:将引用的外部js、css
文件剥离开来,不编译到vendor.js
中,而是用资源的形式引用,这样浏览器可以使用多个线程异步将vendor.js
、外部的js等加载下来,达到加速首页展示效果。
1. 在vue.config.js进行配置
本人对vue
、vuex
、vue-router
、axios
、element-ui
、echarts
进行了cdn引用。(请求element-ui
、echarts
的cdn较慢)
//生产环境标记
const IS_PRODUCTION = process.env.NODE_ENV === 'production'
//配置引用cdn的js、css地址
const cdn = {
css: [
'https://unpkg.com/element-ui@2.13.2/lib/theme-chalk/index.css'
],
js: [
'https://cdn.bootcdn.net/ajax/libs/vue/2.6.10/vue.min.js',
'https://cdn.bootcdn.net/ajax/libs/vue-router/3.0.2/vue-router.min.js',
'https://cdn.bootcdn.net/ajax/libs/vuex/3.1.0/vuex.min.js',
'https://cdn.bootcdn.net/ajax/libs/axios/0.18.1/axios.min.js',
'https://unpkg.com/element-ui@2.13.2/lib/index.js',
'https://cdn.bootcdn.net/ajax/libs/echarts/5.0.1/echarts.min.js'
]
}
//配置打包时使用CDN节点(加入externals外部扩展), 忽略打包的第三方库
//左面放package.json中的扩展的名称,右面放项目依赖的名称(项目初始化要用的名称)
const externals = {
// 属性名称 vue, 表示遇到 import xxx from 'vue' 这类引入 'vue'的,不去 node_modules 中找,而是去找全局变量 Vue(其他的为VueRouter、Vuex、axios、ELEMENT、echarts,注意全局变量是一个确定的值,不能修改为其他值,修改为其他大小写或者其他值会报错,若有异议可留言)
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
axios: 'axios',
'element-ui': 'ELEMENT',
'echarts': 'echarts'
}
chainWebpack(config) {
if (IS_PRODUCTION) {
config.plugin('html').tap(args => {
args[0].cdn = cdn
return args
})
//视为一个外部库,而不将它打包进来
config.externals(externals)
}
}
2.在public/index.html文件配置
使用 webpack
中自带的插件 html插件进行配置,在 index.html
中增加判断,是否使用 CDN, htmlWebpackPlugin.options
使用的是vue.config
中的config.plugin('html')
的插件属性。
<!-- 使用CDN的CSS文件 -->
<% for (var i in
htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %>
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" />
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" />
<% } %>
<!-- 使用CDN加速的JS文件,配置在vue.config.js下 -->
<% for (var i in
htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
3.易出错点
- Router is not defined 解决方案: 将Router 改为 ‘VueRouter’
- Uncaught TypeError: Illegal constructor 解决方案:修改externals 中‘'element-ui’的value
const externals = {
'element-ui': 'ELEMENT',
}
列表无限滚动需要考虑两点:
- 数据太多,要做虚拟列表
- 下拉到底,继续加载数据并拼接之前的数据
虚拟列表怎么实现呢?
只展示可视区域内的列表项目,动态计算可视区域内的列表项,删除非可视区域列表项。
(1)首先确定dom结构
- 第一层作为容器层(
infinite-list-container
),目的是监听列表滚动,记录滚动位置scrollTop。 - 第二层作为占位层(
infinite-list-phantom
),根据实际列表的长度占位,撑开空间,形成滚动条 - 第三层作为列表层(
infinite-list
),列表数据展示的可视化区域,需要用transform:translate3D(x,y,z)
,这里的y指的是列表偏移量。
(2)监听数据
监听容器的scroll事件,获取滚动位置scrollTop
- 可视区域高度:screenHeight
- 列表每项高度:itemSize
- 列表数据:listData
- 当前滚动位置:scrollTop
(3)确定需要的数据
- 滚动的位置:
this.$ref.list.scrollTop
- 确定列表项的高度:
itemSize = 100px
- 可视区域的列表项的总数:
visableCount = Math.ceil(screenHeight / itemSize)
- 确定每次加载列表数据的条数:
listData.length
- 确定列表的实际的长度:
listHeight = itemSize * listData.length
- 开始索引:
start = Math.floor(scrollTop / itemSize)
- 结束索引:
end = start + visableCount
- 偏移量:scrollTop - (scrollTop % itemSize)
(4)代码
<div class="infinite-list-container" ref="list" @scroll="scrollEvent">
<div class="scrollTopBtn" @click="scrollToTop" v-show="end > 20"> 回到顶部</div>
<div class="infinite-list-phantom" :style="{ height: listHeight + 'px' }"></div>
<div class="infinite-list" :style="{ transform: getTransform }">
<div class="infinite-list-item" v-for="item in visibleData" :key="item.id" @click="toDetail(item.id)" :style="{ height: itemSize + 'px',lineHeight: itemSize + 'px' }">
<div class="left-section">
{{ item.title[0] }}
</div>
<div class="right-section">
<div class="title">{{ item.title }}</div>
<div class="desc">{{ item.content }}</div>
</div>
</div>
</div>
</div>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import Faker from "faker";
interface Data {
title: string;
content: string;
id: number | string;
}
@Component
export default class VirtualList extends Vue {
public readonly itemSize: number = 100;
public listData: Data[] = [];
// 可视区域高度
public screenHeight: number =
document.documentElement.clientHeight || document.body.clientHeight;
// 可显示的列表项数
public visibleCount: number = Math.ceil(this.screenHeight / this.itemSize);
// 偏移量
public startOffset: number = 0;
// 起始索引
public start: number = 0;
// 结束索引
public end: number = this.start + this.visibleCount;
public $refs: {
list: any;
};
// 列表总高度
get listHeight() {
return this.listData.length * this.itemSize;
}
// 偏移量对应的style
get getTransform() {
return `translate3d(0,${this.startOffset}px,0)`;
}
// 获取真实显示列表数据
get visibleData() {
return this.listData.slice(
this.start,
Math.min(this.end, this.listData.length)
);
}
// 获取数据
getTenListData() {
if (this.listData.length >= 200) {
return [];
}
return new Array(10).fill({}).map((item) => ({
id: Faker.random.uuid(),
title: Faker.name.title(),
content: Faker.random.words(),
}));
}
//初始化
created() {
this.listData = this.getTenListData();
}
//滚动顶部
scrollToTop() {
this.$refs.list.scrollTo({
top: 0,
left: 0,
behavior: "smooth",
});
}
//监听滚动事件
public scrollEvent(e: any) {
// 当前滚动位置
const scrollTop = this.$refs.list.scrollTop;
// 此时的开始索引
this.start = Math.floor(scrollTop / this.itemSize);
// 此时的结束索引
this.end = this.start + this.visibleCount;
//拼接数据
if (this.end > this.listData.length) {
this.listData = this.listData.concat(this.getTenListData());
}
// 此时的偏移量
this.startOffset = scrollTop - (scrollTop % this.itemSize);
}
}
</script>
(5)图示