项目地址 https://github.com/liangxh0523/md_web_displaycss
网页布局分为三部分,分别是html
为了md能在网页上良好的展现,应具有如下功能:git
我使用了marked.js将md转成htmlgithub
// 先安装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();
watch: {
'$route.path': {
immediate:true,
handler: function(newValue) {
this.mode = newValue.slice(1);
let index = 0;
// 在rendererMD.heading里有本身的做用域,用this访问不到
let mode = this.mode
// 这里的处理只是为了同时展现多个demo,三种方法须要不一样的处理方法
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}>`;
}
};
document.getElementsByTagName("html")[0].style['scroll-behavior'] = this.mode === 'css_demo' ? 'smooth' : '';
this.compiledMarkdown = marked(str);
}
}
}
复制代码
在html中,用v-html绑定此计算属性便可web
<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;
},
复制代码
<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>
复制代码
这里有三种方法实现平滑滚动chrome
当点击右侧的滚动条时,处理点击事件,根据点击元素的offsetTop和scrollTop值的差,肯定须要滚动的距离npm
handleHighlight(item) {
this.highlightIndex = item;
if (this.mode === 'raw_js_demo') {
this.rawJsScroll(item);
} else if (this.mode === 'scroll_into_view') {
let jump = document.querySelectorAll('.jump');
jump[item].scrollIntoView({
block: 'start',
behavior: "smooth"
});
}
},
rawJsScroll(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;
window.scrollTo(0, distance);
setTimeout(smoothDown, 10);
} else {
window.scrollTo(0, total);
}
}
function smoothUp() {
if (distance > total) {
distance -= step;
window.scrollTo(0, distance);
setTimeout(smoothUp, 10);
} else {
window.scrollTo(0, total);
}
}
}
复制代码
若是是css的方式,滚动条的容器处的scroll-behavior属性设置为smooth值数组
document.getElementsByTagName("html")[0].style['scroll-behavior'] = this.mode === 'css_demo' ? 'smooth' : '';
复制代码
因为这里有header,因此须要锚点定位时进行偏移处理,因此对于须要定位到的元素h一、h2前面插入一个元素,这个元素的标签内绑定锚点值,它的padding-top和margin-top设置为相反数,值为头部的高度,这样就完成了定位元素的偏移处理。浏览器
rendererMD.heading = function(text, level) {
if (level < 3) {
return <div class="jump offset" id="${index++}"></div><h${level}>${text}</h${level}>`;
} else {
return `<h${level}>${text}</h${level}>`;
}
};
div .offset {
position: relative;
padding-top: 40px;
margin-top: -40px;
visibility: hidden;
}
复制代码
若是使用scrollIntoView()的方法,就不须要加锚点值进行定位了,在目录项的点击事件中,使用当前选中元素,设置scrollIntoView()方法的block和behavior值,直接平滑滚动到指定的位置bash
rendererMD.heading = function(text, level) {
if (level < 3) {
return <div class="jump offset" id="${index++}"></div><h${level}>${text}</h${level}>`;
} else {
return `<h${level}>${text}</h${level}>`;
}
};
handleHighlight(item) {
let jump = document.querySelectorAll('.jump');
jump[item].scrollIntoView({
block: 'start',
behavior: "smooth"
});
}
复制代码
在阅读md内容时,随着滚动条的变化,目录的高亮项也随着变化,这里有注意:
mounted() {
this.$nextTick(function() {
window.addEventListener('scroll', this.onScroll);
});
},
beforeDestroy() {
window.removeEventListener('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;
// 处理三种平滑滚动处理方式时的偏移量不一样
let height = this.mode === 'raw_js_demo' ? 75 : (this.mode === 'css_demo' ? 10 : 0);
if (top > _itemTop - height) {
currentId = i;
} else {
break;
}
}
if (currentId) {
// 这里的currentOId是字符串,必须转换成数字,不然高亮项的全等没法匹配
this.highlightIndex = parseInt(currentId);
}
}
}
复制代码
以上就是如何让md在网页上良好展现的所有功能,谷歌了屡次,感谢分享经验的艾瑞巴dei,开源万岁~!
待改进的点有:
// #这不是标题
格式的内容以为有用的话请点赞~你的赞就是我更新最大的动力~!