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

先上成果圖

github github.com/li328250157…css


網頁的佈局

網頁佈局分爲三部分,分別是html

頭部header,固定定位git

側邊欄aside,固定定位,margin-top的值是header的高度github

內容contain,靜態定位, margin-top值爲header的高度,margin-left的值爲aside的寬度,是router-view的出口。分爲兩部分:npm

主內容,顯示md轉換後的html頁面,margin-right值爲md目錄的寬度值數組

提取markdown的h1和h2目錄,用於標題導航,固定定位markdown

功能

爲了md能在網頁上良好的展現,應具有如下功能:app

點擊左側的菜單,能夠獲取到相應的md內容(字符串格式),將md內容轉成html,爲一級、二級標題加錨點idide

爲html增長md的格式,引入一個css便可,參考網址佈局

提取md中的一級二級標題,在右側顯示文章目錄

點擊右側文章目錄,左側內容可定位到相應的位置,還要作平滑滾動處理,加強用戶體驗

左側內容滾動時,右側目錄的激活項隨之動態變化


md轉成html

我使用了marked.js將md轉成html,並在這裏爲h1和h2加上了id值,做爲錨點

//  先安裝marked.js到本地

npm install marked --save

//  在組件內引入marked

import marked from 'marked';

//  marked的基本設置

marked.setOptions({

    renderer: rendererMD,

    gfm: true,

    tables: true,

    breaks: false,

    pedantic: false,

    sanitize: false,

    smartLists: true,

    smartypants: false

});


//  實例化

let rendererMD = new marked.Renderer();

// 在計算屬性中,處理md的h一、h2,加上id值,並使用marked轉成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綁定此計算屬性便可

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


提取標題

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';

        })

        // 以致少一個#開始,緊接非換行符外任意個字符進行惰性匹配,而後是一個換行符

        .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級標題,將一級和二級過濾出來

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

    let index = 0;

    //  在此處加index值,這裏和標籤裏綁定的id是對應的

    nav = nav.map(_ => {

        _.index = index++;

        return _;

    });

    let retNavs = [];

    let toAppendNavList;

    navLevel.forEach(level => {

        // 遍歷一級和二級標題,將同一級的元素組成一個新數組

        toAppendNavList = this.find(nav, {

            level: level

        });

        if (retNavs.length === 0) {

            // 處理一級標題

            retNavs = retNavs.concat(toAppendNavList);

        } else {

            // 處理二級標題,把二級標題加到相應的父節點的children中

            toAppendNavList.forEach(_ => {

                _ = Object.assign(_);

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

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

            });

        }

    });

    //  此處的retNavs就是處理後的樹

    return retNavs;

},

//  處理屬於同一級的標題,組成數組

find(arr, condition) {

    return arr.filter(_ => {

        for (let key in condition) {

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

                return false;

            }

        }

        return true;

    });

},

//  獲取此節點的父節點

getParentIndex(nav, endIndex) {

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

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

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

            return nav[i].index;

        }

    }

},

//  找到同一個父節點的全部子節點

appendToParentNav(nav, parentIndex, newNav) {

    //  找到每個二級標題的傅標題的index值

    let index = this.findIndex(nav, {

        index: parentIndex

    });

    if (index === -1) {

        // 這裏處理的是三級及如下標題

        // 若是在一級標題裏沒找到父節點,就去每個一級標題裏的children裏找

        let subNav;

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

            // 處理沒有父節點的狀況

            subNav = nav[i];

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

        }

    } else {

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

    }

},

//  找符合條件的數組中的成員

findIndex(arr, condition) {

    let ret = -1;

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

        for (var key in condition) {

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

                return false;

            }

        }

        ret = index;

    });

    return ret;

},

md目錄的展現和錨點定位

<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標籤裏已經設置了href值,進行了錨點定位,在點擊目錄綁定的事件裏作了平滑處理

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;

    // 平滑滾動,時長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;

        }

    }

}

主內容滾動,目錄高亮

在閱讀md內容時,隨着滾動條的變化,目錄的高亮項也隨着變化

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是字符串,必須轉換成數字,不然高亮項的全等沒法匹配

            this.highlightIndex = parseInt(currentId);

        }

    }

}

Summary

以上就是如何讓md在網頁上良好展現的所有功能,谷歌了屢次,感謝分享經驗的艾瑞巴dei,開源萬歲~!

待改進的點有:

如何避免非標題的#的正則匹配,好比// #這不是標題 格式的內容

md中h1和h2標籤的處理,md中是容許html的,如今不能知足匹配h一、h2的標籤

相關文章
相關標籤/搜索