已經幾乎好久沒有更新文章了,因爲工做和生活的緣由,彷彿失去了以前在大學時候的樣子,變得慵懶起來。剛踏入社會仍是須要不停的鞭策本身,有不少東西要學,按期的寫做對本身的提高是很大的。在寫的過程你依舊在思考,你會想着把這東西變得更好展示到別人眼前。不會像寫業務同樣,完成了功能和需求不多從頭去優化總結。恰好也是由於最近碰到的一個需求,看網上這方面資料不多,因而就有了這篇文章。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的文章編輯器,敬請期待!