如何使用marked.js使Markdown在网页上良好的展示(vue + element ui)

栏目: JavaScript · 发布时间: 5年前

内容简介:先上成果图网页布局分为三部分,分别是为了md能在网页上良好的展示,应具备以下功能:

先上成果图

如何使用marked.js使Markdown在网页上良好的展示(vue + element ui)

网页的布局

网页布局分为三部分,分别是

  • 头部header,固定定位
  • 侧边栏aside,固定定位
  • 内容contain,静态定位, margin-top值为header的高度,margin-left的值为aside的宽度,是router-view的出口。分为两部分:
    • 主内容,显示md转换后的html页面,margin-right值为md目录的宽度值
    • 提取markdown的h1和h2目录,用于标题导航,固定定位

功能

为了md能在网页上良好的展示,应具备以下功能:

  1. 点击左侧的菜单,可以获取到相应的md内容(字符串格式),将md内容转成html,为一级、二级标题加锚点id
  2. 为html增加md的格式,引入一个css即可, 参考网址
  3. 提取md中的一级二级标题,在右侧显示文章目录
  4. 点击右侧文章目录,左侧内容可定位到相应的位置,还要做平滑滚动处理,增强用户体验
  5. 左侧内容滚动时,右侧目录的激活项随之动态变化

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的h1、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(/(#+)[^#][^\n]*?(?:\n)/g, function(match, m1, m2) {
            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
    });
    nav[index].children = nav[index].children.concat(newNav);
    //  如果要处理三级及以下标题,需要把每一个一级标题的children作为参数,调用appendToParentNav
},
//  找符合条件的数组中的成员
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;
            window.pageYOffset = distance;
            setTimeout(smoothDown, 10);
        } else {
            document.body.scrollTop = total;
            document.documentElement.scrollTop = total;
            window.pageYOffset = total;
        }
    }
    function smoothUp() {
        if (distance > total) {
            distance -= step;
            document.body.scrollTop = distance;
            document.documentElement.scrollTop = distance;
            window.pageYOffset = distance;
            setTimeout(smoothUp, 10);
        } else {
            document.body.scrollTop = total;
            document.documentElement.scrollTop = total;
            window.pageYOffset = 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);
        }
    }
}
复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

New Dark Age

New Dark Age

James Bridle / Verso Books / 2018-7-17 / GBP 16.99

As the world around us increases in technological complexity, our understanding of it diminishes. Underlying this trend is a single idea: the belief that our existence is understandable through comput......一起来看看 《New Dark Age》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具