如何使用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);
        }
    }
}
复制代码

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

查看所有标签

猜你喜欢:

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

引爆点

引爆点

【加】马尔科姆•格拉德威尔(Malcolm Gladwell) / 钱清、覃爱冬 / 中信出版社 / 2014-4 / 36.00元

《引爆点》是《纽约客》怪才格拉德威尔的一部才华横溢之作。他以社会上突如其来的流行潮为切入点,从全新角度探索了控制科学和营销模式。他认为,思想、行为、信息及产品常会像传染病暴发一样迅速传播。正如一个病人就能引起全城流感;几位涂鸦爱好者能在地铁掀起犯罪浪潮;一位满意而归的顾客还能让新开张的餐馆座无虚席;发起小规模流行的团队能引发大规模流行风暴。这些现象均属“社会流行潮”,它达到临界水平并爆发的那一刻,......一起来看看 《引爆点》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

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

HSV CMYK互换工具