已经几乎好久没有更新文章了,因为工做和生活的缘由,仿佛失去了以前在大学时候的样子,变得慵懒起来。刚踏入社会仍是须要不停的鞭策本身,有不少东西要学,按期的写做对本身的提高是很大的。在写的过程你依旧在思考,你会想着把这东西变得更好展示到别人眼前。不会像写业务同样,完成了功能和需求不多从头去优化总结。恰好也是由于最近碰到的一个需求,看网上这方面资料不多,因而就有了这篇文章。css
随着前端的兴起,愈来愈多的人加入了这个你们庭,框架和轮子玲琅满目、遍地开花。这样带来了不少好处:减小了开发成本和时间,可能一个功能别人已经造好了,你只须要npm install一下就可使用了,十分的快捷。可是带来的坏处也不可小嘘:随着项目的增大,轮子愈来愈多,多的难以掌控,项目的体积也由原来的10m > 20m > 100m...,这是一件很恐怖的事情。另外一方面你频繁的使用别人写好的轮子,不多本身思考和实现,久而久之,你的代码能力天然就降低了。因此我常常约束本身的一句话:能不用尽可能不用,开发中一个轮子的使用率超低,甚至只有一次,那坚定不用。前端
恰好一个需求就是:markdow文章编写和呈现。功能差很少相似掘金这种吧,今天给你们带来的是第一节,如何生成一个markdown 目录。 npm
makdown中的#,##,##组成的标题通过marked等工具转化渲染到网页中会成变成h标签,因此当拿到文章详情页后能够从中抽离出全部的目录标签即h1,h2,h3....数组
const toc: string[] = data.content.match(/<[hH][1-6]>.*?<\/[hH][1-6]>/g) // 经过正则的方式
复制代码
拿到这些标题以后就能够进行锚点的设置。在H5中关于锚点的作法不少,咱们会采用下面这种作法进行设计:浏览器
①:设置一个锚点连接 <a href="#miao">去找喵星人</a>
(注意:href属性的属性值最前面要加#)bash
②:在页面中须要的位置设置锚点<h1 id="miao"></h1>
(注意:a标签中要写一个id属性,属性值要与①中的href的属性值同样,不加#)markdown
经过正则匹配到文章中全部的h标签后,循环添加id属性并将div包裹数据结构
tocs.forEach((item: string, index: number) => {
let _toc = `<div name='toc-title' id='${index}'>${item} </div>`
data.content = data.content.replace(item, _toc)
})
复制代码
咱们看到的文章目录通常都是以ul > li > a 标签形式存在的,因此拿到了文章全部的h标签后如何转化为ul或li这类的标签呢? 框架
从控制台中中能够看出文章h标题都被抽离出来,接下来要作的就是将这些h标签转化为ul>li的形式。首先因该知道的一种数据结构--堆栈。 简而言之就是先进后出的数据格式,好比说有一个篮子咱们依次往篮子里放鸡蛋,忽然有一天这个篮子底部快漏了,为了保护鸡蛋咱们要把鸡蛋从篮子里拿出来 ,从篮子最外层依次向内取出鸡蛋,这就是典型的先进后出的例子。咱们h标签转化为ul>li其实也是同样的道理。async
export default function toToc(data: string[]) {
let levelStack: string[] = []
let result:string = ''
const addStartUL = () => { result += '<ul class="catalog-list">'; }
const addEndUL = () => { result += '</ul>\n'; }
const addLI = (index: number, itemText: string) => { result += '<li><a name="link" class="toc-link'+'-#'+ index + '" href="#' + index + '">' + itemText + "</a></li>\n"; }
data.forEach(function (item: any, index: number) {
let itemText: string = item.replace(/<[^>]+>/g, '') // 匹配h标签的文字
let itemLabel: string = item.match(/<\w+?>/)[0] // 匹配h?标签<h?>
let levelIndex: number = levelStack.indexOf(itemLabel) // 判断数组里有无<h?>
// 没有找到相应<h?>标签,则将新增ul、li
if (levelIndex === -1) {
levelStack.unshift(itemLabel)
addStartUL()
addLI(index, itemText)
}
// 找到了相应<h?>标签,而且在栈顶的位置则直接将li放在此ul下
else if (levelIndex === 0) {
addLI(index, itemText)
}
// 找到了相应<h?>标签,可是不在栈顶位置,须要将以前的全部<h?>出栈而且打上闭合标签,最后新增li
else {
while (levelIndex--) {
levelStack.shift()
addEndUL()
}
addLI(index, itemText)
}
})
// 若是栈中还有<h?>,所有出栈打上闭合标签
while (levelStack.length) {
levelStack.shift()
addEndUL()
}
return result
}
复制代码
至此全部的h标签都转换成了ui > li的形式而且增长了a连接锚点和以前文章中h标签id相互对应,文章就实现了目录和点击跳转。
到这里咱们的目标基本完成了一半,做为掘金的忠爱粉,固然是选择使用css进行优化一下,css贴起来总以为像是在拉家常,这里就不详细介绍了,大体是这样的
.catalog-list {
font-weight: 600;
padding-left: 10px;
position: relative;
font-size: 15px;
&:first-child::before {
content: "";
position: absolute;
top: 10px;
left: 12px;
bottom: 0;
width: 2px;
background-color: #ebedef;
opacity: .8;
}
}
& > li > a {
position: relative;
padding-left: 16px;
line-height: 20px;
@include catalogRound(0, 6px);
}
ul, li {
padding: 0;
margin: 0;
list-style: none;
}
ul > li > a {
font-size: 14px;
color: #333333;
padding-left: 36px;
font-weight: 500;
position: relative;
@include catalogRound(20px, 5px);
}
ul > ul > li > a {
line-height: 20px;
font-size: 14px;
color: #333333;
padding-left: 50px;
font-weight: normal;
@include catalogRound;
}
a {
color: #000;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 4px 0 4px 12px;
&:hover {
background-color: #ebedef;
}
}
}
复制代码
通过改装后的目录效果以下,貌似有几分相像了。至少不会太丑。后面主要介绍如何实现文章到目录的联动和目录到文章的联动。这是必不可少的一个功能
目录如何控制文章显示的位置,能够思考一下🤔:锚点是经过a标签来实现的可是大量的a标签的点击事件是没法捕获的,尤为咱们是经过转换出来的a标签,但观察发现锚点的hash值会在url上增长锚点位置,所以想到了一种解决方案能够经过监听url的变化来捕获点击的a标签是哪一个。因而咱们监听了route
@Watch('$route')
private routechange(val: any) {
const data = document.getElementsByClassName(`toc-link-${val.hash}`)[0] as Element
this.linkLists.forEach((list:Element) => {
data == list ? list.classList.add('active') : list.classList.remove('active')
})
}
复制代码
至此咱们点击目录即可跳转到文章响应的位置,这里有一个小提示。因为页面可能有nav导航定位,每每咱们跳转的文章会被导航栏遮住,所以须要改善一下,经过css属性设置margin-top = nav的高度,padding-top = -nav的高度。
目录到文章已经讲完了,滚动文章如何实现目录自动跳转呢?不妨也先大体理清楚思路:
具体实现步骤:
在mounted()生命周期中监听鼠标的滚动
window.addEventListener('scroll', this.handleScroll, true)
获取全部的文章标题和目录
this.$nextTick(async () => {
await this.getTitleHeight()
await this.getCataloglist()
})
// 获取每一个文章标题的距顶部的高度
private async getTitleHeight() {
let titlelist = Array.prototype.slice.call((this.$refs.article as Element).getElementsByClassName('toc-title'))
titlelist.forEach((item,index) => {
this.listHeight.push(item.offsetTop)
})
// 滚动的距离没法取到最后一个,所以在数组最后加上上一个两倍达到效果
this.listHeight.push(2 * (titlelist[titlelist.length-1].offsetTop))
}
// 获取目录的全部ul、a标签
private async getCataloglist() {
let catalogList = (this.$refs.catalog as Element).getElementsByClassName('catalog-list')
this.linkLists = document.getElementsByName('link')
this.target = Array.prototype.slice.call(catalogList)
}
复制代码
在handleScroll函数中监听文章滚动
private handleScroll() {
const scrollY = window.pageYOffset
this.fixed = scrollY > 230 ? true : false
for (let i = 0; i < this.listHeight.length-1; i++) {
let h1: number = this.listHeight[i]
let h2: number = this.listHeight[i + 1]
if (scrollY >= h1 && scrollY <= h2) {
const data: Element = document.getElementsByClassName(`toc-link-#${i}`)[0] as Element // 获取文章滚动到目录的目标元素
this.linkLists.forEach((list: Element) => {
let top: number = 0
top = i > 7 ? -28 * (i-7) : 0
this.target[0].style.marginTop = `${top}px`
data == list ? list.classList.add('active') : list.classList.remove('active') // 其余移除active
})
}
}
}
复制代码
代码讲解:
this.fixed = scrollY > 230 ? true : false
目录不跟随页面滚动,所以须要添加一个fixed属性,让他固定在文章的右边,230是目录前的一个盒子高度。当鼠标滚动到230px的时候目录就固定了带到了效果。
let top: number = 0
top = i > 7 ? -28 * (i-7) : 0
this.target[0].style.marginTop = `${top}px`
复制代码
虽然目录不跟随页面滚动,但目录过长可能就显示不出来,所以须要去动态设置目录的margin-top属性,
top = i > 7 ? -28 * (i-7) : 0 // 目录第7条的时候开始向上滚动
功能算是实现了,但仍是有不少能够优化的地方,也但愿指出给予意见。若是你不喜欢这样的目录,或者根本身的实际需求不同,那无非就是css的不一样罢了。功能实现每每决定了效果,能够根据本身需求去改写ul > li的css了。这只是从我实际项目中抽离出来的一部分。实际要比这难太多。不过这已经够用了,下期将带你们一块儿学习如何制做实现一个掘金Style的文章编辑器,敬请期待!