大家好,我是前端小张同学,最近在忙着公司的业务开发,都没有怎么更新自己的学习记录了,借着今天这个机会,就给大家分享一下,我是如何把上一次的弹幕设计,在业余时间写成了一个简单的
mini库,对比上次思路设计,这次是进阶版。
弹幕功能: 开启、关闭、重置、暂停、发布弹幕,icon图标、链接跳转、自定义用户自己发布的弹幕样式、修改颜色,批量删除等。
待添加功能 :点赞,自定义多类型弹幕,其他的还在等考虑,有好的建议可以在评论区留言。
1 : 前言
1.1 : 为什么会做这件事 ?
先跟大家说说为什么会做这件事,其实能开始动手写弹幕库这件事情是来自于我上班时的一个灵感,因为我们公司有一个需求,就是给基金走势图
上增加弹幕,但正好这个东西就是我负责开发的,所以我在公司完成了一版,但我发现不是很好,没有设计的那么完美,代码没有层次性等等,所以我决定再写一版,于是在业余时间开始设计弹幕。
1.2 想法
说说我的想法吧,对于弹幕写成一个库这件事情其实我是很避讳的,不敢挑战,为什么呢,因为弹幕这个功能太常见了,必定是会有集成好了的比我好上千倍的库,甚至别人的功能比我多得多 , 但是为什么我还要去写,去做这件事情呢? 我认为你去做一件事情没有对和错,对你自己来水只有成功和失败,过程中的收获全都是你自己学到的。
1.2 :学习收获
- 需求设计
- mini库的中文文档开发
- 如何实现中英文切换以及暗色主题切换
- 如何部署github静态网站(分支部署)
- vuePress 搭建文档网站学习
- 如何用vueCli打包一个组件package
- vueCli优化包体积。
- npm发包,更新包,版本查看等等。
2:我是怎么做的?
2.1思维导图
2.1.1 说说的弹幕设计吧,这里就不用文字描述了,我给大家提供一个思维导图,里面有我的设计层次。
2.2 开发弹幕
其实弹幕 主要是用面向对象的设计理念来进行开发的,其目的就是为了让后面开发更方便,每一个功能只需要调用一个方法,方法里去实现具体的功能。
import { BarrageType } from "../constant";import { Barrage } from "./barrage";// 弹幕管理 实体类export class BarrageManager {constructor(barrageVue) {this.BarrageVueInstance = barrageVue;this.pauseFlag = false;this.barrageList = []; // 弹幕数据this.cacheSourceBarrageList = []; // 缓存弹幕原始数据this.bacthDeleteIds = []; // 所有需要批量删除的对象idthis.barrageTimer = null; // 定时器this.maxDeleteCounter = 0; // 最大删除次数this.lastDeleteCounter = 0; // 最后删除次数this.currentRow = 0; //当前正在生成弹幕的行this.createTotal = 0; // 总共取了多少条this.total = 0; // 原始数据总数this.jumpLinkFlag = barrageVue.jumpLinkFlag // 是否需要点击弹幕跳转链接this.maxRows = barrageVue.rows / 2; // 最大的弹道数量this.fullScreen = barrageVue.fullScreen; // 是否全屏弹幕this.delay = barrageVue.delay; // 延迟时间 --> 你希望弹幕需要多少秒滚动一屏,弹幕文组滑过容器的时间this.createTime = barrageVue.createTime;this.isBatchDestory = barrageVue.isBatchDestory // 是否批量删除弹幕this.batchDestoryNum = barrageVue.batchDestoryNum; //批量删除的数量this.level = 1; // 弹幕等级this.top = 0; // ,每一条弹幕相对于自己父盒子的距离this.offsetWidth = 0; // 弹幕容器宽度this.defaultColor = '#fff';this.everyRowsLengths = new Array(this.maxRows).fill(0)}getBarrageList = () => this.barrageListgetBarrageTimer = () => this.barrageTimergetPauseFlag = () => this.pauseFlagsaveOffsetWidth = (value) => { this.offsetWidth = value }saveBarrageItemClientWidth = (clientWidth, currentRow) => {this.everyRowsLengths[currentRow] = this.everyRowsLengths[currentRow] + clientWidth}setDefaultColor = (value) => { this.defaultColor !== value && (this.defaultColor = value) }computedTopValue = (rowIndex = 0) => `${(rowIndex * this.BarrageVueInstance.rowHeight)}px`addBarrage(value) {this.barrageList.push(value) // 将创建完成的弹幕对象添加到队列中}addDeleteIdInQueue(id) {this.bacthDeleteIds.push(id)this.bacthDeleteHandler()}createBarrage(barrage, userCreateBarrage = false) {if (userCreateBarrage) {this.currentRow = this.everyRowsLengths.findIndex(cur => cur === Math.min(...this.everyRowsLengths))this.top = this.computedTopValue(this.currentRow)this.everyRowsLengths[this.currentRow] = this.everyRowsLengths[this.currentRow] + 150this.currentRow = this.currentRow + 1}const options = {url: barrage?.url || '',color: barrage?.color || this.defaultColor,content: barrage?.content || '',id: barrage?.id || Date.now(),top: barrage?.top || this.top,level: barrage?.level || 1,imgLink: barrage.imgLink || '',delay: barrage?.delay || `${this.delay}s`,type: barrage?.type || BarrageType.MYBARRAGE,offsetWidth: barrage?.offsetWidth || this.offsetWidth,animationPlayState: barrage?.animationPlayState || 'running',}return new Barrage(options)}bacthDeleteHandler() {// 如果 最大删除数量 为 0 并且 你的 批量删除数组id长度 等于了 余数 那就证明只有余数这么多弹幕待删除if (this.maxDeleteCounter === 0 && this.lastDeleteCounter === this.bacthDeleteIds.length) {this.batchDelete(0, this.lastDeleteCounter)this.lastDeleteCounter = 0this.bacthDeleteIds = []} else if (this.maxDeleteCounter > 0) {if (this.bacthDeleteIds.length !== this.batchDestoryNum) returnthis.batchDelete(0, this.batchDestoryNum)this.bacthDeleteIds.splice(0, this.batchDestoryNum)this.maxDeleteCounter = this.maxDeleteCounter - 1}}batchDelete(start, count) {if (this.barrageList.length === 0) returnthis.barrageList.splice(start, count)}deleteBarrage(id) {const index = this.barrageList.findIndex(item => item.id === id)if (index === -1) returnthis.barrageList.splice(index, 1)}deleteAllBarrage() {this.barrageList = []}timedCreationBarrage() {this.barrageTimer = setInterval(() => {if (!this.cacheSourceBarrageList[this.createTotal]) return this.clearTimer(this.barrageTimer) // 如果创建的数量以及大于了总数则清除定时器s;this.generateBarrage()}, this.createTime * 1000);}generateBarrage() {if (this.currentRow >= this.maxRows) {this.currentRow = 0; // 回到初始行}const barrageItem = this.cacheSourceBarrageList[this.createTotal] // 取出每一个弹幕对象// 是全屏弹幕吗 ? 是的话 随机两层 level 1 和 level 2this.level = this.fullScreen ? Math.floor(Math.random() * 2) + 1 : 1;this.top = this.computedTopValue(this.currentRow); // 计算准确的topconst assignBarrage = Object.assign(barrageItem, { level: this.level, top: this.top, delay: `${this.delay}s`, offsetWidth: this.offsetWidth })const barrageConstructor = this.createBarrage(assignBarrage)this.addBarrage(barrageConstructor)this.createTotal = this.createTotal + 1this.currentRow = this.currentRow + 1}close() {this.clearTimer(this.barrageTimer)this.resetData()}// 开启弹幕play(barrages) {// 如果没有暂停过 则 走初始化流程if (!this.pauseFlag) return this._init(barrages || this.cacheSourceBarrageList)// 否则 接着上一次的继续this.timedCreationBarrage()}// 暂停弹幕pause() {this.clearTimer(this.barrageTimer)// 是否暂停过this.pauseFlag = true}reset(barrages) {this.close()this._init(barrages || this.cacheSourceBarrageList)}clearTimer(timerId) {clearInterval(timerId)this.barrageTimer = null}// 重置数据resetData() {this.total = 0this.createTotal = 0this.barrageList = []this.maxDeleteCounter = 0this.lastDeleteCounter = 0this.pauseFlag = falsethis.everyRowsLengths = new Array(this.maxRows).fill(0)}// 初始化弹幕弹幕数据_init(barrageList) {// 如果你不是一个数组 或者 你是 数组但长度为 0 则 returnif (!Array.isArray(barrageList) || (Array.isArray(barrageList) && barrageList.length === 0)) return;this.cacheSourceBarrageList = barrageList; // 缓存传入的弹幕数据this.total = barrageList.length;if (this.isBatchDestory) { // 如果是批量删除的话才计算 次数this.maxDeleteCounter = Math.floor(barrageList.length / this.batchDestoryNum); // 计算最大可删除的数量this.lastDeleteCounter = barrageList.length % this.batchDestoryNum; // 计算最后一次需要删除的数量}this.timedCreationBarrage()}}import { BarrageType } from "../constant"; import { Barrage } from "./barrage"; // 弹幕管理 实体类 export class BarrageManager { constructor(barrageVue) { this.BarrageVueInstance = barrageVue; this.pauseFlag = false; this.barrageList = []; // 弹幕数据 this.cacheSourceBarrageList = []; // 缓存弹幕原始数据 this.bacthDeleteIds = []; // 所有需要批量删除的对象id this.barrageTimer = null; // 定时器 this.maxDeleteCounter = 0; // 最大删除次数 this.lastDeleteCounter = 0; // 最后删除次数 this.currentRow = 0; //当前正在生成弹幕的行 this.createTotal = 0; // 总共取了多少条 this.total = 0; // 原始数据总数 this.jumpLinkFlag = barrageVue.jumpLinkFlag // 是否需要点击弹幕跳转链接 this.maxRows = barrageVue.rows / 2; // 最大的弹道数量 this.fullScreen = barrageVue.fullScreen; // 是否全屏弹幕 this.delay = barrageVue.delay; // 延迟时间 --> 你希望弹幕需要多少秒滚动一屏,弹幕文组滑过容器的时间 this.createTime = barrageVue.createTime; this.isBatchDestory = barrageVue.isBatchDestory // 是否批量删除弹幕 this.batchDestoryNum = barrageVue.batchDestoryNum; //批量删除的数量 this.level = 1; // 弹幕等级 this.top = 0; // ,每一条弹幕相对于自己父盒子的距离 this.offsetWidth = 0; // 弹幕容器宽度 this.defaultColor = '#fff'; this.everyRowsLengths = new Array(this.maxRows).fill(0) } getBarrageList = () => this.barrageList getBarrageTimer = () => this.barrageTimer getPauseFlag = () => this.pauseFlag saveOffsetWidth = (value) => { this.offsetWidth = value } saveBarrageItemClientWidth = (clientWidth, currentRow) => { this.everyRowsLengths[currentRow] = this.everyRowsLengths[currentRow] + clientWidth } setDefaultColor = (value) => { this.defaultColor !== value && (this.defaultColor = value) } computedTopValue = (rowIndex = 0) => `${(rowIndex * this.BarrageVueInstance.rowHeight)}px` addBarrage(value) { this.barrageList.push(value) // 将创建完成的弹幕对象添加到队列中 } addDeleteIdInQueue(id) { this.bacthDeleteIds.push(id) this.bacthDeleteHandler() } createBarrage(barrage, userCreateBarrage = false) { if (userCreateBarrage) { this.currentRow = this.everyRowsLengths.findIndex(cur => cur === Math.min(...this.everyRowsLengths)) this.top = this.computedTopValue(this.currentRow) this.everyRowsLengths[this.currentRow] = this.everyRowsLengths[this.currentRow] + 150 this.currentRow = this.currentRow + 1 } const options = { url: barrage?.url || '', color: barrage?.color || this.defaultColor, content: barrage?.content || '', id: barrage?.id || Date.now(), top: barrage?.top || this.top, level: barrage?.level || 1, imgLink: barrage.imgLink || '', delay: barrage?.delay || `${this.delay}s`, type: barrage?.type || BarrageType.MYBARRAGE, offsetWidth: barrage?.offsetWidth || this.offsetWidth, animationPlayState: barrage?.animationPlayState || 'running', } return new Barrage(options) } bacthDeleteHandler() { // 如果 最大删除数量 为 0 并且 你的 批量删除数组id长度 等于了 余数 那就证明只有余数这么多弹幕待删除 if (this.maxDeleteCounter === 0 && this.lastDeleteCounter === this.bacthDeleteIds.length) { this.batchDelete(0, this.lastDeleteCounter) this.lastDeleteCounter = 0 this.bacthDeleteIds = [] } else if (this.maxDeleteCounter > 0) { if (this.bacthDeleteIds.length !== this.batchDestoryNum) return this.batchDelete(0, this.batchDestoryNum) this.bacthDeleteIds.splice(0, this.batchDestoryNum) this.maxDeleteCounter = this.maxDeleteCounter - 1 } } batchDelete(start, count) { if (this.barrageList.length === 0) return this.barrageList.splice(start, count) } deleteBarrage(id) { const index = this.barrageList.findIndex(item => item.id === id) if (index === -1) return this.barrageList.splice(index, 1) } deleteAllBarrage() { this.barrageList = [] } timedCreationBarrage() { this.barrageTimer = setInterval(() => { if (!this.cacheSourceBarrageList[this.createTotal]) return this.clearTimer(this.barrageTimer) // 如果创建的数量以及大于了总数则清除定时器s; this.generateBarrage() }, this.createTime * 1000); } generateBarrage() { if (this.currentRow >= this.maxRows) { this.currentRow = 0; // 回到初始行 } const barrageItem = this.cacheSourceBarrageList[this.createTotal] // 取出每一个弹幕对象 // 是全屏弹幕吗 ? 是的话 随机两层 level 1 和 level 2 this.level = this.fullScreen ? Math.floor(Math.random() * 2) + 1 : 1; this.top = this.computedTopValue(this.currentRow); // 计算准确的top const assignBarrage = Object.assign(barrageItem, { level: this.level, top: this.top, delay: `${this.delay}s`, offsetWidth: this.offsetWidth }) const barrageConstructor = this.createBarrage(assignBarrage) this.addBarrage(barrageConstructor) this.createTotal = this.createTotal + 1 this.currentRow = this.currentRow + 1 } close() { this.clearTimer(this.barrageTimer) this.resetData() } // 开启弹幕 play(barrages) { // 如果没有暂停过 则 走初始化流程 if (!this.pauseFlag) return this._init(barrages || this.cacheSourceBarrageList) // 否则 接着上一次的继续 this.timedCreationBarrage() } // 暂停弹幕 pause() { this.clearTimer(this.barrageTimer) // 是否暂停过 this.pauseFlag = true } reset(barrages) { this.close() this._init(barrages || this.cacheSourceBarrageList) } clearTimer(timerId) { clearInterval(timerId) this.barrageTimer = null } // 重置数据 resetData() { this.total = 0 this.createTotal = 0 this.barrageList = [] this.maxDeleteCounter = 0 this.lastDeleteCounter = 0 this.pauseFlag = false this.everyRowsLengths = new Array(this.maxRows).fill(0) } // 初始化弹幕弹幕数据 _init(barrageList) { // 如果你不是一个数组 或者 你是 数组但长度为 0 则 return if (!Array.isArray(barrageList) || (Array.isArray(barrageList) && barrageList.length === 0)) return; this.cacheSourceBarrageList = barrageList; // 缓存传入的弹幕数据 this.total = barrageList.length; if (this.isBatchDestory) { // 如果是批量删除的话才计算 次数 this.maxDeleteCounter = Math.floor(barrageList.length / this.batchDestoryNum); // 计算最大可删除的数量 this.lastDeleteCounter = barrageList.length % this.batchDestoryNum; // 计算最后一次需要删除的数量 } this.timedCreationBarrage() } }import { BarrageType } from "../constant"; import { Barrage } from "./barrage"; // 弹幕管理 实体类 export class BarrageManager { constructor(barrageVue) { this.BarrageVueInstance = barrageVue; this.pauseFlag = false; this.barrageList = []; // 弹幕数据 this.cacheSourceBarrageList = []; // 缓存弹幕原始数据 this.bacthDeleteIds = []; // 所有需要批量删除的对象id this.barrageTimer = null; // 定时器 this.maxDeleteCounter = 0; // 最大删除次数 this.lastDeleteCounter = 0; // 最后删除次数 this.currentRow = 0; //当前正在生成弹幕的行 this.createTotal = 0; // 总共取了多少条 this.total = 0; // 原始数据总数 this.jumpLinkFlag = barrageVue.jumpLinkFlag // 是否需要点击弹幕跳转链接 this.maxRows = barrageVue.rows / 2; // 最大的弹道数量 this.fullScreen = barrageVue.fullScreen; // 是否全屏弹幕 this.delay = barrageVue.delay; // 延迟时间 --> 你希望弹幕需要多少秒滚动一屏,弹幕文组滑过容器的时间 this.createTime = barrageVue.createTime; this.isBatchDestory = barrageVue.isBatchDestory // 是否批量删除弹幕 this.batchDestoryNum = barrageVue.batchDestoryNum; //批量删除的数量 this.level = 1; // 弹幕等级 this.top = 0; // ,每一条弹幕相对于自己父盒子的距离 this.offsetWidth = 0; // 弹幕容器宽度 this.defaultColor = '#fff'; this.everyRowsLengths = new Array(this.maxRows).fill(0) } getBarrageList = () => this.barrageList getBarrageTimer = () => this.barrageTimer getPauseFlag = () => this.pauseFlag saveOffsetWidth = (value) => { this.offsetWidth = value } saveBarrageItemClientWidth = (clientWidth, currentRow) => { this.everyRowsLengths[currentRow] = this.everyRowsLengths[currentRow] + clientWidth } setDefaultColor = (value) => { this.defaultColor !== value && (this.defaultColor = value) } computedTopValue = (rowIndex = 0) => `${(rowIndex * this.BarrageVueInstance.rowHeight)}px` addBarrage(value) { this.barrageList.push(value) // 将创建完成的弹幕对象添加到队列中 } addDeleteIdInQueue(id) { this.bacthDeleteIds.push(id) this.bacthDeleteHandler() } createBarrage(barrage, userCreateBarrage = false) { if (userCreateBarrage) { this.currentRow = this.everyRowsLengths.findIndex(cur => cur === Math.min(...this.everyRowsLengths)) this.top = this.computedTopValue(this.currentRow) this.everyRowsLengths[this.currentRow] = this.everyRowsLengths[this.currentRow] + 150 this.currentRow = this.currentRow + 1 } const options = { url: barrage?.url || '', color: barrage?.color || this.defaultColor, content: barrage?.content || '', id: barrage?.id || Date.now(), top: barrage?.top || this.top, level: barrage?.level || 1, imgLink: barrage.imgLink || '', delay: barrage?.delay || `${this.delay}s`, type: barrage?.type || BarrageType.MYBARRAGE, offsetWidth: barrage?.offsetWidth || this.offsetWidth, animationPlayState: barrage?.animationPlayState || 'running', } return new Barrage(options) } bacthDeleteHandler() { // 如果 最大删除数量 为 0 并且 你的 批量删除数组id长度 等于了 余数 那就证明只有余数这么多弹幕待删除 if (this.maxDeleteCounter === 0 && this.lastDeleteCounter === this.bacthDeleteIds.length) { this.batchDelete(0, this.lastDeleteCounter) this.lastDeleteCounter = 0 this.bacthDeleteIds = [] } else if (this.maxDeleteCounter > 0) { if (this.bacthDeleteIds.length !== this.batchDestoryNum) return this.batchDelete(0, this.batchDestoryNum) this.bacthDeleteIds.splice(0, this.batchDestoryNum) this.maxDeleteCounter = this.maxDeleteCounter - 1 } } batchDelete(start, count) { if (this.barrageList.length === 0) return this.barrageList.splice(start, count) } deleteBarrage(id) { const index = this.barrageList.findIndex(item => item.id === id) if (index === -1) return this.barrageList.splice(index, 1) } deleteAllBarrage() { this.barrageList = [] } timedCreationBarrage() { this.barrageTimer = setInterval(() => { if (!this.cacheSourceBarrageList[this.createTotal]) return this.clearTimer(this.barrageTimer) // 如果创建的数量以及大于了总数则清除定时器s; this.generateBarrage() }, this.createTime * 1000); } generateBarrage() { if (this.currentRow >= this.maxRows) { this.currentRow = 0; // 回到初始行 } const barrageItem = this.cacheSourceBarrageList[this.createTotal] // 取出每一个弹幕对象 // 是全屏弹幕吗 ? 是的话 随机两层 level 1 和 level 2 this.level = this.fullScreen ? Math.floor(Math.random() * 2) + 1 : 1; this.top = this.computedTopValue(this.currentRow); // 计算准确的top const assignBarrage = Object.assign(barrageItem, { level: this.level, top: this.top, delay: `${this.delay}s`, offsetWidth: this.offsetWidth }) const barrageConstructor = this.createBarrage(assignBarrage) this.addBarrage(barrageConstructor) this.createTotal = this.createTotal + 1 this.currentRow = this.currentRow + 1 } close() { this.clearTimer(this.barrageTimer) this.resetData() } // 开启弹幕 play(barrages) { // 如果没有暂停过 则 走初始化流程 if (!this.pauseFlag) return this._init(barrages || this.cacheSourceBarrageList) // 否则 接着上一次的继续 this.timedCreationBarrage() } // 暂停弹幕 pause() { this.clearTimer(this.barrageTimer) // 是否暂停过 this.pauseFlag = true } reset(barrages) { this.close() this._init(barrages || this.cacheSourceBarrageList) } clearTimer(timerId) { clearInterval(timerId) this.barrageTimer = null } // 重置数据 resetData() { this.total = 0 this.createTotal = 0 this.barrageList = [] this.maxDeleteCounter = 0 this.lastDeleteCounter = 0 this.pauseFlag = false this.everyRowsLengths = new Array(this.maxRows).fill(0) } // 初始化弹幕弹幕数据 _init(barrageList) { // 如果你不是一个数组 或者 你是 数组但长度为 0 则 return if (!Array.isArray(barrageList) || (Array.isArray(barrageList) && barrageList.length === 0)) return; this.cacheSourceBarrageList = barrageList; // 缓存传入的弹幕数据 this.total = barrageList.length; if (this.isBatchDestory) { // 如果是批量删除的话才计算 次数 this.maxDeleteCounter = Math.floor(barrageList.length / this.batchDestoryNum); // 计算最大可删除的数量 this.lastDeleteCounter = barrageList.length % this.batchDestoryNum; // 计算最后一次需要删除的数量 } this.timedCreationBarrage() } }
这里面就是对弹幕的一些操作,包括了弹幕的开启
,重置
,关闭
,暂停
,等等,如果要看完整版的代码,大家可以去我的 github拉取代码,逐行分析。
2.3 中文文档开发
相信一个好的库,是少不了一个好的文档的,虽然我的库很一般,但是我还是希望能做到最好,给大家代码更轻松地体验。
中文文档,我首页是自己开发的,想自己去定制一款第一份属于自己的文档,正如大家开头所见那个,其实它也是一个Vue项目,当然如果你想深入了解,你可以点击这里,支持主题,中英文切换。
这里可以跟大家插一嘴,像这种暗色主题是如何实现的?
2.4 :暗色主题切换实现方式。
技术选型 : scss + vue2 实现
思路 : 将 html身上加一个属性,然后根据属性选择器进行应用不同的样式(混入样式)。然后主题切换时 去更改 html的属性,这样就实现了暗色主题切换
, 如果你是less 原理也是一样的,只不过代码上略有差异
核心代码
$themes : (light : ( // 定义 百色主题 的一些颜色变量bgColor : $theme-linear-gradient,color : $black,btnBgColor : #f1f1f1,borderColor : #bcbcbc,themeSvgBgColor : $white,themeSvgColor : #767676,githubLeftColor : $white,docBgColor : $white,customColor : $theme-linear-gradient),dark : ( // 定义暗色主题的一些变量bgColor : $black,color : $white,btnBgColor : #2f2f2f,borderColor : #474747,themeSvgBgColor : $black,themeSvgColor : $white,githubLeftColor : $black,docBgColor : $black,customColor : $white),);@function getVar($key){ // 函数获取刚刚定义中的变量属性的值$themeMap : map-get($themes , $curTheme); // 遍历 themes对象 , $curTheme 值为 当前主题 默认为 暗色 dark@return map-get($themeMap , $key); //返回 遍历的对象根据 传入的key来取出 变量的值};$curTheme :dark;@mixin theme-color() { // 混入一个 改变主题色的工具@each $key , $value in $themes{ // 遍历主题的键 和 值$curTheme : $key !global; // 将curTheme作为全局变量 将key(dark or light ) 赋值给 curTheme 这样就可以根据主题定义的对象取出属性html[data-theme=#{$key}] & { // 然后根据当前主题 根据属性选择器 选择@content; // 将传入的 样式 作为 当前主题的 样式进行使用}}}// 使用 方式@include theme-color { // 这里所有的内容将作为内容给到@contentbackground-color: getVar("btnBgColor");border: 1px solid getVar("borderColor");}$themes : ( light : ( // 定义 百色主题 的一些颜色变量 bgColor : $theme-linear-gradient, color : $black, btnBgColor : #f1f1f1, borderColor : #bcbcbc, themeSvgBgColor : $white, themeSvgColor : #767676, githubLeftColor : $white, docBgColor : $white, customColor : $theme-linear-gradient ), dark : ( // 定义暗色主题的一些变量 bgColor : $black, color : $white, btnBgColor : #2f2f2f, borderColor : #474747, themeSvgBgColor : $black, themeSvgColor : $white, githubLeftColor : $black, docBgColor : $black, customColor : $white ), ); @function getVar($key){ // 函数获取刚刚定义中的变量属性的值 $themeMap : map-get($themes , $curTheme); // 遍历 themes对象 , $curTheme 值为 当前主题 默认为 暗色 dark @return map-get($themeMap , $key); //返回 遍历的对象根据 传入的key来取出 变量的值 }; $curTheme :dark; @mixin theme-color() { // 混入一个 改变主题色的工具 @each $key , $value in $themes{ // 遍历主题的键 和 值 $curTheme : $key !global; // 将curTheme作为全局变量 将key(dark or light ) 赋值给 curTheme 这样就可以根据主题定义的对象取出属性 html[data-theme=#{$key}] & { // 然后根据当前主题 根据属性选择器 选择 @content; // 将传入的 样式 作为 当前主题的 样式进行使用 } } } // 使用 方式 @include theme-color { // 这里所有的内容将作为内容给到@content background-color: getVar("btnBgColor"); border: 1px solid getVar("borderColor"); }$themes : ( light : ( // 定义 百色主题 的一些颜色变量 bgColor : $theme-linear-gradient, color : $black, btnBgColor : #f1f1f1, borderColor : #bcbcbc, themeSvgBgColor : $white, themeSvgColor : #767676, githubLeftColor : $white, docBgColor : $white, customColor : $theme-linear-gradient ), dark : ( // 定义暗色主题的一些变量 bgColor : $black, color : $white, btnBgColor : #2f2f2f, borderColor : #474747, themeSvgBgColor : $black, themeSvgColor : $white, githubLeftColor : $black, docBgColor : $black, customColor : $white ), ); @function getVar($key){ // 函数获取刚刚定义中的变量属性的值 $themeMap : map-get($themes , $curTheme); // 遍历 themes对象 , $curTheme 值为 当前主题 默认为 暗色 dark @return map-get($themeMap , $key); //返回 遍历的对象根据 传入的key来取出 变量的值 }; $curTheme :dark; @mixin theme-color() { // 混入一个 改变主题色的工具 @each $key , $value in $themes{ // 遍历主题的键 和 值 $curTheme : $key !global; // 将curTheme作为全局变量 将key(dark or light ) 赋值给 curTheme 这样就可以根据主题定义的对象取出属性 html[data-theme=#{$key}] & { // 然后根据当前主题 根据属性选择器 选择 @content; // 将传入的 样式 作为 当前主题的 样式进行使用 } } } // 使用 方式 @include theme-color { // 这里所有的内容将作为内容给到@content background-color: getVar("btnBgColor"); border: 1px solid getVar("borderColor"); }
utils.js
export function getAttribute (el , attributeName ) {return document.querySelector(el).getAttribute(attributeName)}export function setAttribute (el , attributeName , value) {document.querySelector(el).setAttribute(attributeName , value)}export function getAttribute (el , attributeName ) { return document.querySelector(el).getAttribute(attributeName) } export function setAttribute (el , attributeName , value) { document.querySelector(el).setAttribute(attributeName , value) }export function getAttribute (el , attributeName ) { return document.querySelector(el).getAttribute(attributeName) } export function setAttribute (el , attributeName , value) { document.querySelector(el).setAttribute(attributeName , value) }
app.vue
mounted() {setAttribute("html", attributeType.DATA_THEME, themeType.LIGHT);},methods : {const currentTheme = getAttribute("html", attributeType.DATA_THEME);if (currentTheme === themeType.DARK) {setAttribute("html", attributeType.DATA_THEME, themeType.LIGHT);} else {setAttribute("html", attributeType.DATA_THEME, themeType.DARK);}this.$emit("input", !this.value);}}mounted() { setAttribute("html", attributeType.DATA_THEME, themeType.LIGHT); }, methods : { const currentTheme = getAttribute("html", attributeType.DATA_THEME); if (currentTheme === themeType.DARK) { setAttribute("html", attributeType.DATA_THEME, themeType.LIGHT); } else { setAttribute("html", attributeType.DATA_THEME, themeType.DARK); } this.$emit("input", !this.value); } }mounted() { setAttribute("html", attributeType.DATA_THEME, themeType.LIGHT); }, methods : { const currentTheme = getAttribute("html", attributeType.DATA_THEME); if (currentTheme === themeType.DARK) { setAttribute("html", attributeType.DATA_THEME, themeType.LIGHT); } else { setAttribute("html", attributeType.DATA_THEME, themeType.DARK); } this.$emit("input", !this.value); } }
2.5 : github部署中文文档
这个我会在后面继续更新一篇文章,单独来讲,如何用分支部署github项目,如果你想学习,请随时关注我的动态。
2.6 :讲讲VuePress
说起这个,你不得不说,写代码也要靠缘分,其实在开发这个弹幕库之前,我是不知道 vuePress
这个框架的,有一天晚上我 刷抖音
看到有一个老师,讲vitePress
, 我就在想,既然有VitePress那应该有vuePress,然后第二天,我就开始学习了起来。照着官网搭建了一个项目。
它能够把你的Mackdown
转换成一个网页,平时你在Mack down 的笔记,你用vuepress 它也可以帮你实现文档自由,真的太好用了,家人们,强烈建议学一下!!!。
2.6.1 学习VuePress的疑难杂症
让我最头疼的一点就是 vuepress 项目的部署,如果你是在github上部署你的vuePress 项目你不能自定义项目名称,否则项目样式就会丢失,你必须设置根路径,并且项目还需要 用户名.github.io
这种规则,没有服务器的日子也太难过了,有没有大佬知道怎么解决,可以在评论区留言。
接下来就是打包我们自己写的组件了。
2.7 : 如何用vueCli打包一个组件
- 项目根目录下创建 package 文件夹
- 提供一个出口index.js和一个package.json
- 同级就是你的组件包
- 修改vue.config.js
index.js
这里我是参考Vant
组件库组件注册方式进行实现的
import miniVueBarrage from './barrage/index.vue'; // 导入你的组件入口const version = '1.0.0'; // 设定版本const components = [ // 将你的组件放入一个数组miniVueBarrage]const install = (Vue) => { // 进行批量注册逐渐components.forEach(component => {Vue.component(component.name, component);});}/* istanbul ignore if */if (typeof window !== 'undefined' && window.Vue) { // 判断window 身上是否有vue实例 有的话 调用install进行组件注册install(window.Vue);}export {miniVueBarrage// 导出组件};export default { // 导出一个 install 函数和版本install, version}import miniVueBarrage from './barrage/index.vue'; // 导入你的组件入口 const version = '1.0.0'; // 设定版本 const components = [ // 将你的组件放入一个数组 miniVueBarrage ] const install = (Vue) => { // 进行批量注册逐渐 components.forEach(component => { Vue.component(component.name, component); }); } /* istanbul ignore if */ if (typeof window !== 'undefined' && window.Vue) { // 判断window 身上是否有vue实例 有的话 调用install进行组件注册 install(window.Vue); } export { miniVueBarrage// 导出组件 }; export default { // 导出一个 install 函数和版本 install, version }import miniVueBarrage from './barrage/index.vue'; // 导入你的组件入口 const version = '1.0.0'; // 设定版本 const components = [ // 将你的组件放入一个数组 miniVueBarrage ] const install = (Vue) => { // 进行批量注册逐渐 components.forEach(component => { Vue.component(component.name, component); }); } /* istanbul ignore if */ if (typeof window !== 'undefined' && window.Vue) { // 判断window 身上是否有vue实例 有的话 调用install进行组件注册 install(window.Vue); } export { miniVueBarrage// 导出组件 }; export default { // 导出一个 install 函数和版本 install, version }
然后 你需要修改你的 vue.config.js
const { defineConfig } = require('@vue/cli-service')const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");const TerserPlugin = require("terser-webpack-plugin");const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");const CopyWebpackPlugin = require('copy-webpack-plugin');const path = require('path')const isProduction = process.env.NODE_ENV === 'production'const resolve = (dir) => path.resolve(__dirname, dir)module.exports = defineConfig({publicPath: './', //设置根路径outputDir: path.resolve(__dirname, './dist/lib'), // 设置打包出口transpileDependencies: true, //指定需要被编译的依赖模块productionSourceMap: false, // 生产环境是否需要 sourceMapchainWebpack(config) {isProduction && config.plugins.delete('html') // 打包完成后 我不需要 html文件生成 删除HTMLPlugin},css: {extract: {filename: isProduction ? 'mini-vue-barrage.css' : '[name].css', // 采用css 分离插件chunkFilename: '[name].css', // 每一个csschunk css的文件名称},loaderOptions: {postcss: {postcssOptions: {plugins: [require('postcss-preset-env')({// css 编译转换 增加 --webkit等browsers: ["defaults","not ie < 11", // 版本不小于 ie 11的"last 2 versions", //并且他的前两个版本"> 1%",// 将市场份额大于1%的浏览器"iOS 7",// ios大于 7"last 3 iOS versions" // ios的前三版本]})]}}}},configureWebpack: {entry: isProduction ? './package/index.js' : './src/main.js', // 设置入口, 如果是生产 则打包我的package 也就是我写的组件output: {filename: isProduction ? 'index.js' : '[name].js', // 设置打包出口library: {name: 'miniVueBarrage', // 指定库的名称type: 'commonjs' // 指定输出类型},},optimization: {minimize: isProduction, // 是否采用 js 压缩minimizer: [ // 采用js压缩的插件isProduction ? new TerserPlugin({terserOptions: {nameCache: true,compress: {drop_console: true,drop_debugger: true,pure_funcs: ["console.log"] // 移除console},output: {beautify: true, // 压缩注释comments: false,}}}) : '']},plugins: [ // 使用css压缩new CssMinimizerPlugin(),isProduction && new CopyWebpackPlugin({ // 使用copyPlugin 机型文件复制patterns: [ //{from: './package/package.json',to: resolve('./dist/package.json'),},{from: './README.md',to: resolve('./dist/README.md'),}]})]}})const { defineConfig } = require('@vue/cli-service') const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const CopyWebpackPlugin = require('copy-webpack-plugin'); const path = require('path') const isProduction = process.env.NODE_ENV === 'production' const resolve = (dir) => path.resolve(__dirname, dir) module.exports = defineConfig({ publicPath: './', //设置根路径 outputDir: path.resolve(__dirname, './dist/lib'), // 设置打包出口 transpileDependencies: true, //指定需要被编译的依赖模块 productionSourceMap: false, // 生产环境是否需要 sourceMap chainWebpack(config) { isProduction && config.plugins.delete('html') // 打包完成后 我不需要 html文件生成 删除HTMLPlugin }, css: { extract: { filename: isProduction ? 'mini-vue-barrage.css' : '[name].css', // 采用css 分离插件 chunkFilename: '[name].css', // 每一个csschunk css的文件名称 }, loaderOptions: { postcss: { postcssOptions: { plugins: [ require('postcss-preset-env')({// css 编译转换 增加 --webkit等 browsers: [ "defaults", "not ie < 11", // 版本不小于 ie 11的 "last 2 versions", //并且他的前两个版本 "> 1%",// 将市场份额大于1%的浏览器 "iOS 7",// ios大于 7 "last 3 iOS versions" // ios的前三版本 ] }) ] } } } }, configureWebpack: { entry: isProduction ? './package/index.js' : './src/main.js', // 设置入口, 如果是生产 则打包我的package 也就是我写的组件 output: { filename: isProduction ? 'index.js' : '[name].js', // 设置打包出口 library: { name: 'miniVueBarrage', // 指定库的名称 type: 'commonjs' // 指定输出类型 }, }, optimization: { minimize: isProduction, // 是否采用 js 压缩 minimizer: [ // 采用js压缩的插件 isProduction ? new TerserPlugin({ terserOptions: { nameCache: true, compress: { drop_console: true, drop_debugger: true, pure_funcs: ["console.log"] // 移除console }, output: { beautify: true, // 压缩注释 comments: false, } } } ) : '' ] }, plugins: [ // 使用css压缩 new CssMinimizerPlugin(), isProduction && new CopyWebpackPlugin({ // 使用copyPlugin 机型文件复制 patterns: [ // { from: './package/package.json', to: resolve('./dist/package.json'), }, { from: './README.md', to: resolve('./dist/README.md'), } ] }) ] } })const { defineConfig } = require('@vue/cli-service') const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const CopyWebpackPlugin = require('copy-webpack-plugin'); const path = require('path') const isProduction = process.env.NODE_ENV === 'production' const resolve = (dir) => path.resolve(__dirname, dir) module.exports = defineConfig({ publicPath: './', //设置根路径 outputDir: path.resolve(__dirname, './dist/lib'), // 设置打包出口 transpileDependencies: true, //指定需要被编译的依赖模块 productionSourceMap: false, // 生产环境是否需要 sourceMap chainWebpack(config) { isProduction && config.plugins.delete('html') // 打包完成后 我不需要 html文件生成 删除HTMLPlugin }, css: { extract: { filename: isProduction ? 'mini-vue-barrage.css' : '[name].css', // 采用css 分离插件 chunkFilename: '[name].css', // 每一个csschunk css的文件名称 }, loaderOptions: { postcss: { postcssOptions: { plugins: [ require('postcss-preset-env')({// css 编译转换 增加 --webkit等 browsers: [ "defaults", "not ie < 11", // 版本不小于 ie 11的 "last 2 versions", //并且他的前两个版本 "> 1%",// 将市场份额大于1%的浏览器 "iOS 7",// ios大于 7 "last 3 iOS versions" // ios的前三版本 ] }) ] } } } }, configureWebpack: { entry: isProduction ? './package/index.js' : './src/main.js', // 设置入口, 如果是生产 则打包我的package 也就是我写的组件 output: { filename: isProduction ? 'index.js' : '[name].js', // 设置打包出口 library: { name: 'miniVueBarrage', // 指定库的名称 type: 'commonjs' // 指定输出类型 }, }, optimization: { minimize: isProduction, // 是否采用 js 压缩 minimizer: [ // 采用js压缩的插件 isProduction ? new TerserPlugin({ terserOptions: { nameCache: true, compress: { drop_console: true, drop_debugger: true, pure_funcs: ["console.log"] // 移除console }, output: { beautify: true, // 压缩注释 comments: false, } } } ) : '' ] }, plugins: [ // 使用css压缩 new CssMinimizerPlugin(), isProduction && new CopyWebpackPlugin({ // 使用copyPlugin 机型文件复制 patterns: [ // { from: './package/package.json', to: resolve('./dist/package.json'), }, { from: './README.md', to: resolve('./dist/README.md'), } ] }) ] } })
有了以下配置你就可以对你的组件进行打包了,但我还是建议大家使用webpack进行搭建,我这里是因为以及用了cli所以方便,如果包比较多我建议你使用webpack,自己搭建一套,更有价值。
2.8 : 压缩体积
然后 关于压缩体积 , 我们这里做的就是 css压缩和js压缩以及移除一些注释,尽可能地减少包的体积,从代码层面因为只有一个组件也没有很大的优化空间,所以主要所做的还是压缩以及转换工作,将不必要的文件可以移除。
2.9 : npm发包,更新包,版本查看
好,到这里就是最后一步了,我们打包完成后 会生成下面的几个文件 ,分别是 lib
, package.json
, README.md
lib 就不说了 ,打包完的js和css都在lib下,给大家重点讲一下package.json
2.9.1 package.json
{"name": "minivuebarrage", // 你的包名"version": "0.2.9", // 包的版本 0 --> 你的主版本 2 --> 你的版本, 9 --> 你的每一次更新的小版本"private": false, // 是否是私有的"description": "minivuebarrage 是一个轻量级的弹幕组件", // 你的包描述"main": "./lib/index.js", // 你的包的入口文件是哪个"scripts": { // 你的脚本命令,相信大家都很熟悉"test": "echo \"Error: no test specified\" && exit 1"},"author": { // 包的作者"name": "xiaozhang","url": "https://github.com/xiaozhangclassmater" // 作者的github},"repository": {"url": "https://github.com/xiaozhangclassmater/miniVueBarrage", // 作者的仓库链接"type": "git" //类型为 git},"engines": { // node版本限制"node": ">= 10.0.0"},"keywords": [ // 你的包 关键字,别人在npm上可以通过哪些关键字来搜到你的包"Barrage assembly","minivuebarrage"],"license": "MIT",//开源协议"browserslist": [ // 浏览器的版本"> 1%","last 2 versions","not dead"]}{ "name": "minivuebarrage", // 你的包名 "version": "0.2.9", // 包的版本 0 --> 你的主版本 2 --> 你的版本, 9 --> 你的每一次更新的小版本 "private": false, // 是否是私有的 "description": "minivuebarrage 是一个轻量级的弹幕组件", // 你的包描述 "main": "./lib/index.js", // 你的包的入口文件是哪个 "scripts": { // 你的脚本命令,相信大家都很熟悉 "test": "echo \"Error: no test specified\" && exit 1" }, "author": { // 包的作者 "name": "xiaozhang", "url": "https://github.com/xiaozhangclassmater" // 作者的github }, "repository": { "url": "https://github.com/xiaozhangclassmater/miniVueBarrage", // 作者的仓库链接 "type": "git" //类型为 git }, "engines": { // node版本限制 "node": ">= 10.0.0" }, "keywords": [ // 你的包 关键字,别人在npm上可以通过哪些关键字来搜到你的包 "Barrage assembly", "minivuebarrage" ], "license": "MIT",//开源协议 "browserslist": [ // 浏览器的版本 "> 1%", "last 2 versions", "not dead" ] }{ "name": "minivuebarrage", // 你的包名 "version": "0.2.9", // 包的版本 0 --> 你的主版本 2 --> 你的版本, 9 --> 你的每一次更新的小版本 "private": false, // 是否是私有的 "description": "minivuebarrage 是一个轻量级的弹幕组件", // 你的包描述 "main": "./lib/index.js", // 你的包的入口文件是哪个 "scripts": { // 你的脚本命令,相信大家都很熟悉 "test": "echo \"Error: no test specified\" && exit 1" }, "author": { // 包的作者 "name": "xiaozhang", "url": "https://github.com/xiaozhangclassmater" // 作者的github }, "repository": { "url": "https://github.com/xiaozhangclassmater/miniVueBarrage", // 作者的仓库链接 "type": "git" //类型为 git }, "engines": { // node版本限制 "node": ">= 10.0.0" }, "keywords": [ // 你的包 关键字,别人在npm上可以通过哪些关键字来搜到你的包 "Barrage assembly", "minivuebarrage" ], "license": "MIT",//开源协议 "browserslist": [ // 浏览器的版本 "> 1%", "last 2 versions", "not dead" ] }
在这里重点说一下 main
这个属性。
我们平时导入一个包都是直接引入包名,并没有向下再写路径,有没有想过这个问题,为什么我们能在antd这个包中导入一些东西。
import antd from 'antd'import antd from 'antd'import antd from 'antd'
其实很简单,就是antd这个包下面 有一个package.json
文件,main 指定了我们的入口,当我们打包的时候,它就会根据main提供的路径进行查找。所以这就是原因。
2.9.2 发布包
- 切换到你打包文件的目录,比如
dist
- 执行 npm login 进行登录
会生成
1:
npm notice Log in on https://registry.npmjs.org/
代表你的 npm源,相信大家在国内的开发者很多都是 淘宝的镜像源,在这里我们需要切换成 npm原镜像。
2:
Username
: 提示你输入你的npm 用户名,没有的可以去npm上注册 ,我的是 xiaozhangclassxxx
3:
Password
: 提示你输入 npm密码
4:
Email: (this IS public)
: 提示你输入你的邮箱,它会在邮箱里给你发一个验证码,然后你填到终端就可以了
npm notice Please check your email for a one-time password (OTP) Enter one-time password: (验证码)
登录成功后 它会提示 Logged in as xiaozhangclassmate on https://registry.npmjs.org/.
然后你可以通过 npm publish 就可以把当前目录下所有的文件发布到 npm仓库上。
更新包
查看当前包版本 输入你的更新类型 , 比如 update docs(更新文档)
npm version <update_type>// 发布之前 你需要把你的package.json --> version 进行一次小版本升级 例如 你之前是 0.0.1 那你这一次应该是 0.0.2npm publish -m '对此版本的描述'npm version <update_type> // 发布之前 你需要把你的package.json --> version 进行一次小版本升级 例如 你之前是 0.0.1 那你这一次应该是 0.0.2 npm publish -m '对此版本的描述'npm version <update_type> // 发布之前 你需要把你的package.json --> version 进行一次小版本升级 例如 你之前是 0.0.1 那你这一次应该是 0.0.2 npm publish -m '对此版本的描述'
弃用包
看官方文档,说得很详细 去官方文档
ok,到这里基本上以及结束了,这就是我的开发过程,下面给大家展示一波才艺。
3:幕后花絮
太多报错了,哈哈哈,这只是一点点,还有很多报错,没记录下来,所以 过程是艰辛的,结果是美好的不经历风雨,怎么能见彩虹
, 我们一起加油。
结束
最后,如果大家想去看源码怎么实现的可以去 我的github ,目前还在迭代开发中,有好的想法可以来提PR
我是前端小张同学,期待你的关注,跟我一起行动起来,前端没死。