md在web良好展示的處理,三種實(shí)現(xiàn)定位時(shí)平緩滾動的方法(vue + Element ui + mark.js)

先上成果圖

github?https://github.com/li328250157/markdown

網(wǎng)頁的布局

網(wǎng)頁布局分為三部分,分別是

頭部header,固定定位

側(cè)邊欄aside,固定定位,margin-top的值是header的高度

內(nèi)容contain,靜態(tài)定位, margin-top值為header的高度,margin-left的值為aside的寬度,是router-view的出口。分為兩部分:

主內(nèi)容,顯示md轉(zhuǎn)換后的html頁面,margin-right值為md目錄的寬度值

提取markdown的h1和h2目錄,用于標(biāo)題導(dǎo)航,固定定位

功能


為了md能在網(wǎng)頁上良好的展示,應(yīng)具備以下功能:

點(diǎn)擊左側(cè)的菜單,可以獲取到相應(yīng)的md內(nèi)容(字符串格式),將md內(nèi)容轉(zhuǎn)成html,為一級、二級標(biāo)題加錨點(diǎn)id

為html增加md的格式,引入一個(gè)css即可,參考網(wǎng)址

提取md中的一級二級標(biāo)題,在右側(cè)顯示文章目錄

點(diǎn)擊右側(cè)文章目錄,左側(cè)內(nèi)容可定位到相應(yīng)的位置,還要做平滑滾動處理,增強(qiáng)用戶體驗(yàn)

左側(cè)內(nèi)容滾動時(shí),右側(cè)目錄的激活項(xiàng)隨之動態(tài)變化



md轉(zhuǎn)成html

我使用了marked.js將md轉(zhuǎn)成html,并在這里為h1和h2加上了id值,作為錨點(diǎn)

//? 先安裝marked.js到本地

npm install marked --save

//? 在組件內(nèi)引入marked

import marked from 'marked';

//? marked的基本設(shè)置

marked.setOptions({

? ? renderer: rendererMD,

? ? gfm: true,

? ? tables: true,

? ? breaks: false,

? ? pedantic: false,

? ? sanitize: false,

? ? smartLists: true,

? ? smartypants: false

});



//? 實(shí)例化

let rendererMD = new marked.Renderer();

// 在計(jì)算屬性中,處理md的h1、h2,加上id值,并使用marked轉(zhuǎn)成html

computed: {

? ? compiledMarkdown: function() {

? ? ? ? let index = 0;

? ? ? ? rendererMD.heading = function(text, level) {

? ? ? ? ? ? if (level < 3) {

? ? ? ? ? ? ? ? return `<h${level} id="${index++}" class="jump" >${text}</h${level}>`;

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? return `<h${level}>${text}</h${level}>`;

? ? ? ? ? ? }

? ? ? ? };

? ? ? ? return marked(this.content);

? ? }

}

在html中,用v-html綁定此計(jì)算屬性即可

<div class="markdown-body" ref="content" id="content" v-html="compiledMarkdown">



提取標(biāo)題

getTitle(content) {

? ? let nav = [];

? ? let navLevel = [1, 2];

? ? let tempArr = [];

? ? content

? ? ? ? .replace(/```/g, function(match) {

? ? ? ? ? ? return '\f';

? ? ? ? })

? ? ? ? .replace(/\f[^\f]*?\f/g, function(match) {

? ? ? ? ? ? return '';

? ? ? ? })

? ? ? ? .replace(/\r|\n+/g, function(match) {

? ? ? ? ? ? return '\n';

? ? ? ? })

? ? ? ? // 以至少一個(gè)#開始,緊接非換行符外任意個(gè)字符進(jìn)行惰性匹配,然后是一個(gè)換行符

? ? ? ? .replace(/(#+)[^#][^\n]*?(?:\n)/g, function(match, m1) {

? ? ? ? ? ? let title = match.replace('\n', '');

? ? ? ? ? ? let level = m1.length;

? ? ? ? ? ? tempArr.push({

? ? ? ? ? ? ? ? title: title.replace(/^#+/, '').replace(/\([^)]*?\)/, ''),

? ? ? ? ? ? ? ? level: level,

? ? ? ? ? ? ? ? children: []

? ? ? ? ? ? });

? ? ? ? });

? ? //? tempArr得到的是全部1-6級標(biāo)題,將一級和二級過濾出來

? ? nav = tempArr.filter(_ => _.level <= 2);

? ? let index = 0;

? ? //? 在此處加index值,這里和標(biāo)簽里綁定的id是對應(yīng)的

? ? nav = nav.map(_ => {

? ? ? ? _.index = index++;

? ? ? ? return _;

? ? });

? ? let retNavs = [];

? ? let toAppendNavList;

? ? navLevel.forEach(level => {

? ? ? ? // 遍歷一級和二級標(biāo)題,將同一級的元素組成一個(gè)新數(shù)組

? ? ? ? toAppendNavList = this.find(nav, {

? ? ? ? ? ? level: level

? ? ? ? });

? ? ? ? if (retNavs.length === 0) {

? ? ? ? ? ? // 處理一級標(biāo)題

? ? ? ? ? ? retNavs = retNavs.concat(toAppendNavList);

? ? ? ? } else {

? ? ? ? ? ? // 處理二級標(biāo)題,把二級標(biāo)題加到相應(yīng)的父節(jié)點(diǎn)的children中

? ? ? ? ? ? toAppendNavList.forEach(_ => {

? ? ? ? ? ? ? ? _ = Object.assign(_);

? ? ? ? ? ? ? ? let parentNavIndex = this.getParentIndex(nav, _.index);

? ? ? ? ? ? ? ? return this.appendToParentNav(retNavs, parentNavIndex, _);

? ? ? ? ? ? });

? ? ? ? }

? ? });

? ? //? 此處的retNavs就是處理后的樹

? ? return retNavs;

},

//? 處理屬于同一級的標(biāo)題,組成數(shù)組

find(arr, condition) {

? ? return arr.filter(_ => {

? ? ? ? for (let key in condition) {

? ? ? ? ? ? if (condition.hasOwnProperty(key) && condition[key] !== _[key]) {

? ? ? ? ? ? ? ? return false;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return true;

? ? });

},

//? 獲取此節(jié)點(diǎn)的父節(jié)點(diǎn)

getParentIndex(nav, endIndex) {

? ? //? 從當(dāng)前的index開始找 1.距離自己最近的(遞減體現(xiàn)) 2.level比本身小的(越小越高)

? ? for (var i = endIndex - 1; i >= 0; i--) {

? ? ? ? if (nav[endIndex].level > nav[i].level) {

? ? ? ? ? ? return nav[i].index;

? ? ? ? }

? ? }

},

//? 找到同一個(gè)父節(jié)點(diǎn)的所有子節(jié)點(diǎn)

appendToParentNav(nav, parentIndex, newNav) {

? ? //? 找到每一個(gè)二級標(biāo)題的傅標(biāo)題的index值

? ? let index = this.findIndex(nav, {

? ? ? ? index: parentIndex

? ? });

? ? if (index === -1) {

? ? ? ? // 這里處理的是三級及以下標(biāo)題

? ? ? ? // 如果在一級標(biāo)題里沒找到父節(jié)點(diǎn),就去每一個(gè)一級標(biāo)題里的children里找

? ? ? ? let subNav;

? ? ? ? for (var i = 0; i < nav.length; i++) {

? ? ? ? ? ? // 處理沒有父節(jié)點(diǎn)的情況

? ? ? ? ? ? subNav = nav[i];

? ? ? ? ? ? subNav.children.length && this.appendToParentNav(subNav.children, parentIndex, newNav);

? ? ? ? }

? ? } else {

? ? ? ? nav[index].children = nav[index].children.concat(newNav);

? ? }

},

//? 找符合條件的數(shù)組中的成員

findIndex(arr, condition) {

? ? let ret = -1;

? ? arr.forEach((item, index) => {

? ? ? ? for (var key in condition) {

? ? ? ? ? ? if (condition.hasOwnProperty(key) && condition[key] !== item[key]) { // 不進(jìn)行深比較

? ? ? ? ? ? ? ? return false;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? ret = index;

? ? });

? ? return ret;

},

md目錄的展示和錨點(diǎn)定位

<div id="menu">

? ? <ul class="nav-list">

? ? ? ? <li v-for="(nav, index) in contentMenu" :key="index">

? ? ? ? ? ? <a :href="'#' + nav.index" :class="{'active': highlightIndex === nav.index}" @click="handleHighlight(nav.index)" :key="nav.index">{{nav.title}}

? ? ? ? ? ? </a>

? ? ? ? ? ? <template v-if="nav.children.length > 0">

? ? ? ? ? ? ? ? <ul class="nav-list">

? ? ? ? ? ? ? ? ? ? <li v-for="(item, index) in nav.children" :key="index">

? ? ? ? ? ? ? ? ? ? ? ? <a :href="'#' + item.index" :class="{active: highlightIndex === item.index}" :key="item.index" @click="handleHighlight(item.index)">{{item.title}}

? ? ? ? ? ? ? ? ? ? ? ? </a>

? ? ? ? ? ? ? ? ? ? </li>

? ? ? ? ? ? ? ? </ul>

? ? ? ? ? ? </template>

? ? ? ? </li>

? ? </ul>

</div>

平滑滾動

在md的目錄中,a標(biāo)簽里已經(jīng)設(shè)置了href值,進(jìn)行了錨點(diǎn)定位,在點(diǎn)擊目錄綁定的事件里做了平滑處理

handleHighlight(item) {

? ? this.highlightIndex = item;

? ? let jump = document.querySelectorAll('.jump');

? ? //? 這里的60是header的高度值

? ? let total = jump[item].offsetTop - 60;

? ? let distance = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;

? ? // 平滑滾動,時(shí)長500ms,每10ms一跳,共50跳

? ? let step = total / 50;

? ? if (total > distance) {

? ? ? ? smoothDown();

? ? } else {

? ? ? ? let newTotal = distance - total;

? ? ? ? step = newTotal / 50;

? ? ? ? smoothUp();

? ? }

? ? function smoothDown() {

? ? ? ? if (distance < total) {

? ? ? ? ? ? distance += step;

? ? ? ? ? ? document.body.scrollTop = distance;

? ? ? ? ? ? document.documentElement.scrollTop = distance;

? ? ? ? ? ? setTimeout(smoothDown, 10);

? ? ? ? } else {

? ? ? ? ? ? document.body.scrollTop = total;

? ? ? ? ? ? document.documentElement.scrollTop = total;

? ? ? ? }

? ? }

? ? function smoothUp() {

? ? ? ? if (distance > total) {

? ? ? ? ? ? distance -= step;

? ? ? ? ? ? document.body.scrollTop = distance;

? ? ? ? ? ? document.documentElement.scrollTop = distance;

? ? ? ? ? ? setTimeout(smoothUp, 10);

? ? ? ? } else {

? ? ? ? ? ? document.body.scrollTop = total;

? ? ? ? ? ? document.documentElement.scrollTop = total;

? ? ? ? }

? ? }

}

主內(nèi)容滾動,目錄高亮

在閱讀md內(nèi)容時(shí),隨著滾動條的變化,目錄的高亮項(xiàng)也隨著變化

mounted() {

? ? this.$nextTick(function() {

? ? ? ? window.addEventListener('scroll', this.onScroll);

? ? });

},

methods: {

? ? onScroll() {

? ? ? ? let top = document.documentElement ? document.documentElement.scrollTop : document.body.scrollTop;

? ? ? ? let items = document.getElementById('content').getElementsByClassName('jump');

? ? ? ? let currentId = '';

? ? ? ? for (let i = 0; i < items.length; i++) {

? ? ? ? ? ? let _item = items[i];

? ? ? ? ? ? let _itemTop = _item.offsetTop;

? ? ? ? ? ? if (top > _itemTop - 75) {

? ? ? ? ? ? ? ? currentId = _item.id;

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? if (currentId) {

? ? ? ? ? ? //? 這里的currentOId是字符串,必須轉(zhuǎn)換成數(shù)字,否則高亮項(xiàng)的全等無法匹配

? ? ? ? ? ? this.highlightIndex = parseInt(currentId);

? ? ? ? }

? ? }

}

Summary

以上就是如何讓md在網(wǎng)頁上良好展示的全部功能,谷歌了多次,感謝分享經(jīng)驗(yàn)的艾瑞巴dei,開源萬歲~!

待改進(jìn)的點(diǎn)有:

如何避免非標(biāo)題的#的正則匹配,比如// #這不是標(biāo)題?格式的內(nèi)容

md中h1和h2標(biāo)簽的處理,md中是允許html的,現(xiàn)在不能滿足匹配h1、h2的標(biāo)簽

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容