Markdown、Latex编辑小工具
- 文章说明
- 主要代码
- 效果展示
- 源码下载
文章说明
本文主要为了书写Latex的书写风格,以及了解自己实现一个markdown类型的编辑器的过程;目前实现了当前的效果;书写文章进行记录,方便后续查阅
目前还未添加好markdown的代码高亮效果,等待后续自己实现一个高亮组件,然后添加到该demo中
文本相对复杂的几个小点为:
1、marked库的使用
2、katex库的使用
3、textarea元素的光标处插入内容的实现(本效果参考了文章:利用selection对象在textarea光标处插入指定文本)
4、滚动同步效果(目前实现的效果感觉不是很好,会有一些抖动的效果,感觉有点头晕,没有CSDN自带的编辑功能的这个同步效果的实现精细,不过我目前也没整明白它这里的实现原理是什么样的,如果有好的想法的同学欢迎提出在评论区中,如对demo有帮助,冰冰一号会进行采纳,同时添加到项目贡献者中)
Latex相关公式的查找(参考文章为:[markdown语法]公式篇–整理总结了常用的公式语法全)
实际关于markdown相关组件的使用,可以直接使用封装好的库,如v-md-editor,这种封装好的库使用相对简单,而且功能更加完善,参考链接:v-md-editor
主要代码
文件结构
markdown组件
<template><div class="container"><textarea class="left" v-model="data.text" @scroll="scrollLeftSync"></textarea><div class="right" v-html="renderedMarkdown" @scroll="scrollRightSync"></div></div>
</template><script setup>
import {marked} from 'marked';
import {computed, onMounted, reactive} from "vue";const data = reactive({text: "",
});const renderedMarkdown = computed(() => {return marked.parse(data.text);
});let left;
let right;onMounted(() => {left = document.getElementsByClassName("left")[0];right = document.getElementsByClassName("right")[0];left.focus();
});let leftScroll = true;
let rightScroll = true;function scrollLeftSync() {if (!leftScroll) {return;}const percent = (left.scrollTop / (left.scrollHeight - left.clientHeight)).toFixed(2);rightScroll = false;right.scrollTo({top: (right.scrollHeight - right.clientHeight) * percent,});rightScroll = true;
}function scrollRightSync() {if (!rightScroll) {return;}const percent = (right.scrollTop / (right.scrollHeight - right.clientHeight)).toFixed(2);leftScroll = false;left.scrollTo({top: (left.scrollHeight - left.clientHeight) * percent,});leftScroll = true;
}
</script><style lang="scss" scoped>
.container {width: 100%;height: 100%;.left {width: 50%;height: 100%;resize: none;border: none;outline: none;background: transparent;font-size: 1.4rem;padding: 1rem;display: block;overflow: auto;float: left;&::-webkit-scrollbar {width: 0.5rem;background-color: transparent;border-radius: 0.5rem;cursor: pointer;}&::-webkit-scrollbar-thumb {background-color: #bbbbbb;border-radius: 0.5rem;cursor: pointer;}}.right {width: 50%;height: 100%;background-color: #ffffff;font-size: 1.4rem;padding: 1rem;overflow: auto;float: left;&::-webkit-scrollbar {width: 0.5rem;height: 0.5rem;background-color: transparent;border-radius: 0.5rem;}&::-webkit-scrollbar-thumb {background-color: #bbbbbb;border-radius: 0.5rem;}}
}
</style>
Latex组件
<template><div class="container"><div class="tool"><ul><li @click="appendLine">换行</li><li @click="superscript">上标</li><li @click="subscript">下标</li><li @click="vector">向量</li><li @click="average">平均值</li><li @click="fraction">分式</li><li @click="dots">省略号</li><li @click="sqrt">根式</li><li @click="mul">乘</li><li @click="div">除</li><li @click="ge">大于等于</li><li @click="le">小于等于</li><li @click="ne">不等于</li><li @click="dx">导数</li><li @click="infinity">无穷</li><li @click="leftarrow">左箭头</li><li @click="rightarrow">右箭头</li><li @click="In">属于</li><li @click="notIn">不属于</li><li @click="overbrace">上大括号</li><li @click="underbrace">下大括号</li><li @click="sin">sin</li><li @click="cos">cos</li><li @click="tan">tan</li><li @click="log">log</li><li @click="lg">lg</li><li @click="ln">ln</li><li @click="equationSet">方程组</li><li @click="calculationProcess">计算过程</li></ul></div><div class="content"><textarea class="left" v-model="data.text" @scroll="scrollLeftSync"></textarea><div class="right" v-html="data.parseText" @scroll="scrollRightSync"></div></div></div>
</template><script setup>
import {onMounted, reactive, watch} from "vue";
import katex from 'katex'const data = reactive({text: "",parseText: "",
});const renderOption = {delimiters: [{left: '$$', right: '$$', display: true},{left: '$', right: '$', display: false},{left: '\\(', right: '\\)', display: false},{left: '\\[', right: '\\]', display: true}],throwOnError: false
}watch(() => data.text, () => {data.parseText = katex.renderToString(data.text, renderOption);
});let left;
let right;onMounted(() => {left = document.getElementsByClassName("left")[0];right = document.getElementsByClassName("right")[0];left.focus();
});let leftScroll = true;
let rightScroll = true;function scrollLeftSync() {if (!leftScroll) {return;}const percent = (left.scrollTop / (left.scrollHeight - left.clientHeight)).toFixed(2);rightScroll = false;right.scrollTo({top: (right.scrollHeight - right.clientHeight) * percent,});rightScroll = true;
}function scrollRightSync() {if (!rightScroll) {return;}const percent = (right.scrollTop / (right.scrollHeight - right.clientHeight)).toFixed(2);leftScroll = false;left.scrollTo({top: (left.scrollHeight - left.clientHeight) * percent,});leftScroll = true;
}function insertAtCursor(f, value, callback) {let field = flet newValueif (field.selectionStart || field.selectionStart === 0) {const startPos = field.selectionStartconst endPos = field.selectionEndconst restoreTop = field.scrollTopnewValue = field.value.substring(0, startPos) + value + field.value.substring(endPos, field.value.length)if (restoreTop > 0) {field.scrollTop = restoreTop}field.focus()setTimeout(() => {field.selectionStart = startPos + value.lengthfield.selectionEnd = startPos + value.length}, 0)} else {newValue = field.value + valuefield.focus()}callback(newValue)
}function addContent(text) {insertAtCursor(left, text, (newValue) => {data.text = newValue;});
}function appendLine() {addContent("\n\\\\\\\n");
}function superscript() {addContent(" x^y ");
}function subscript() {addContent(" x_y ");
}function vector() {addContent(" \\vec{a} ");
}function average() {addContent(" \\overline{a} ");
}function fraction() {addContent(" \\frac{1}{2} ");
}function dots() {addContent(" \\cdots ");
}function sqrt() {addContent(" \\sqrt[2]{x+y} ");
}function mul() {addContent(" \\times ");
}function div() {addContent(" \\div ");
}function ge() {addContent(" \\ge ");
}function le() {addContent(" \\le ");
}function ne() {addContent(" \\ne ");
}function dx() {addContent(" x{\\prime} ");
}function infinity() {addContent(" \\infty ");
}function leftarrow() {addContent(" \\leftarrow ");
}function rightarrow() {addContent(" \\rightarrow ");
}function In() {addContent(" \\in ");
}function notIn() {addContent(" \\notin ");
}function overbrace() {addContent(" \\overbrace{1+2+\\cdots+100} ");
}function underbrace() {addContent(" \\underbrace{1+2+\\cdots+100} ");
}function sin() {addContent(" \\sin 30^\\circ ");
}function cos() {addContent(" \\cos 30^\\circ ");
}function tan() {addContent(" \\tan 30^\\circ ");
}function log() {addContent(" \\log_2 8 ");
}function lg() {addContent(" \\lg 10 ");
}function ln() {addContent(" \\ln 2 ");
}function equationSet() {addContent("\nf(n)= \\begin{cases}\n" +"n/2, & \\text {if $n$ is even} \\\\\n" +"3n+1, & \\text{if $n$ is odd}\n" +"\\end{cases}\n");
}function calculationProcess() {addContent("\n\\begin{aligned}\n" +" f(x)\n" +" &=x^3+3x^2+3x+1\\\\\n" +" &=(x+1)^3\\\\\n" +"\\end{aligned}\n");
}
</script><style lang="scss" scoped>
.container {width: 100%;height: 100%;.tool {width: 100%;height: 6rem;border-bottom: 0.1rem solid #e0e1e2;background-color: #ffffff;ul {list-style: none;height: 3rem;line-height: 3rem;user-select: none;li {float: left;padding: 0 0.5rem;height: 3rem;text-align: center;color: #409eff;&:hover {cursor: pointer;color: #79bbff;}}}}.content {width: 100%;height: calc(100% - 6rem);.left {width: 50%;height: 100%;resize: none;border: none;outline: none;background: transparent;font-size: 1.4rem;padding: 1rem;display: block;float: left;&::-webkit-scrollbar {width: 0.5rem;background-color: transparent;border-radius: 0.5rem;}&::-webkit-scrollbar-thumb {background-color: #bbbbbb;border-radius: 0.5rem;}}.right {width: 50%;height: 100%;background-color: #ffffff;font-size: 1.4rem;padding: 1rem;overflow: auto;float: left;&::-webkit-scrollbar {width: 0.5rem;height: 0.5rem;background-color: transparent;border-radius: 0.5rem;}&::-webkit-scrollbar-thumb {background-color: #bbbbbb;border-radius: 0.5rem;}}}
}
</style>
效果展示
markdown编辑展示
Latex编辑展示
源码下载
冰冰markdown小工具