如何使用marked.js使Markdown在网页上良好的展示(vue + element ui)
栏目: JavaScript · 发布时间: 6年前
内容简介:先上成果图网页布局分为三部分,分别是为了md能在网页上良好的展示,应具备以下功能:
先上成果图
网页的布局
网页布局分为三部分,分别是
- 头部header,固定定位
- 侧边栏aside,固定定位
- 内容contain,静态定位, margin-top值为header的高度,margin-left的值为aside的宽度,是router-view的出口。分为两部分:
- 主内容,显示md转换后的html页面,margin-right值为md目录的宽度值
- 提取markdown的h1和h2目录,用于标题导航,固定定位
功能
为了md能在网页上良好的展示,应具备以下功能:
- 点击左侧的菜单,可以获取到相应的md内容(字符串格式),将md内容转成html,为一级、二级标题加锚点id
- 为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的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); } } } 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。