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