文章说明
本文主要为了书写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 = f
let newValue
if (field.selectionStart || field.selectionStart === 0) {
const startPos = field.selectionStart
const endPos = field.selectionEnd
const restoreTop = field.scrollTop
newValue = 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.length
field.selectionEnd = startPos + value.length
}, 0)
} else {
newValue = field.value + value
field.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编辑展示