已经几乎好久没有更新文章了,因为工做和生活的缘由,仿佛失去了以前在大学时候的样子,变得慵懒起来。刚踏入社会仍是须要不停的鞭策本身,有不少东西要学,按期的写做对本身的提高是很大的。在写的过程你依旧在思考,你会想着把这东西变得更好展示到别人眼前。不会像写业务同样,完成了功能和需求不多从头去优化总结。恰好也是由于最近碰到的一个需求,看网上这方面资料不多,因而就有了这篇文章。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属性的属性值最前面要加#)markdown
②:在页面中须要的位置设置锚点`
<h1
id="miao"></h1>`
(注意:a标签中要写一个id属性,属性值要与①中的href的属性值同样,不加#)数据结构
经过正则匹配到文章中全部的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这类的标签呢? async
从控制台中中能够看出文章h标题都被抽离出来,接下来要作的就是将这些h标签转化为ul>li的形式。首先因该知道的一种数据结构<font color=orange size=3>--堆栈</font>。
简而言之就是先进后出的数据格式,好比说有一个篮子咱们依次往篮子里放鸡蛋,忽然有一天这个篮子底部快漏了,为了保护鸡蛋咱们要把鸡蛋从篮子里拿出来
,从篮子最外层依次向内取出鸡蛋,这就是典型的先进后出的例子。咱们h标签转化为ul>li其实也是同样的道理。编辑器
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的文章编辑器,敬请期待!