仿金山打字效果
背景
需要校验用户输入的内容和我们要求的内容保持一致,比如:签合同的时候需要用户根据我们的内容进行手抄
效果图
实现代码
<template>
<div class="app">
<h1>
功能1:根据输入的内容去匹配正文,输入的内容显示在"输入内容"中,如果匹配正确的话则使用绿色标识,错误的的话使用红色标识
</h1>
<h3>《荷塘月色》</h3>
<p>{{ original_content }}</p>
<hr />
输入内容:
<p v-html="content"></p>
<hr />
<el-input
:autosize="{ minRows: 2 }"
type="textarea"
:rows="2"
placeholder="请输入内容"
v-model="input_content"
@input="handleInputContent"
>
</el-input>
<h1>功能2:在原文中标识</h1>
<h3>《荷塘月色》</h3>
<p v-html="content_2" ref="p_ref"></p>
<hr />
<el-input
:autosize="{ minRows: 2 }"
type="textarea"
:rows="2"
placeholder="请输入内容"
v-model="input_content_2"
@input="handleInputContent_2"
>
</el-input>
<hr />
<h1>功能3:在输入中标识</h1>
<h3>《荷塘月色》</h3>
<p>{{ content_3 }}</p>
<hr />
输入的内容:
<div
class="text-box"
contenteditable="true"
@input="handleInputContent_3"
ref="test_ref"
></div>
</div>
</template>
<script>
export default {
data() {
return {
original_content:
'路上只我一个人,背着手踱着。这一片天地好像是我的;我也像超出了平常旳自己,到了另一世界里。我爱热闹,也爱冷静;爱群居,也爱独处。像今晚上,一个人在这苍茫旳月下,什么都可以想,什么都可以不想,便觉是个自由的人。白天里一定要做的事,一定要说的话,现在都可不理。这是独处的妙处,我且受用这无边的荷香月色好了。',
content: '',
input_content: '',
original_content_2:
'路上只我一个人,背着手踱着。这一片天地好像是我的;我也像超出了平常旳自己,到了另一世界里。我爱热闹,也爱冷静;爱群居,也爱独处。像今晚上,一个人在这苍茫旳月下,什么都可以想,什么都可以不想,便觉是个自由的人。白天里一定要做的事,一定要说的话,现在都可不理。这是独处的妙处,我且受用这无边的荷香月色好了。',
content_2:
'路上只我一个人,背着手踱着。这一片天地好像是我的;我也像超出了平常旳自己,到了另一世界里。我爱热闹,也爱冷静;爱群居,也爱独处。像今晚上,一个人在这苍茫旳月下,什么都可以想,什么都可以不想,便觉是个自由的人。白天里一定要做的事,一定要说的话,现在都可不理。这是独处的妙处,我且受用这无边的荷香月色好了。',
input_content_2: '',
input_content_3: '',
content_3:
'路上只我一个人,背着手踱着。这一片天地好像是我的;我也像超出了平常旳自己,到了另一世界里。我爱热闹,也爱冷静;爱群居,也爱独处。像今晚上,一个人在这苍茫旳月下,什么都可以想,什么都可以不想,便觉是个自由的人。白天里一定要做的事,一定要说的话,现在都可不理。这是独处的妙处,我且受用这无边的荷香月色好了。',
}
},
directives: {
convert: {
update(el, binding) {
console.log(el, binding)
// el.innerHTML = '<span class="error">错误了</span>'
},
},
},
methods: {
handleInputContent(value) {
// 解决输入特殊字符后出现问题,要手动转义
value = this.escapingSpecialCharacters(value)
const reg = new RegExp('^' + value + '$')
const match_result = reg.test(this.original_content)
const str_length = value.length
if (match_result) {
this.content =
"<span class='right'>" +
this.original_content.substring(0, str_length) +
'</span>'
this.original_content.slice(str_length - 1)
} else {
// 记录此次需要匹配的原始内容
let original_content_search_str = this.original_content.substring(
0,
str_length
)
let match_index = 0
for (let i = 1; i <= value.length; i++) {
const search_str = value.slice(0, i)
if (original_content_search_str.includes(search_str)) {
match_index = i
} else {
match_index = i - 1
break
}
}
this.content =
"<span class='right'>" +
value.substring(0, match_index) +
"</span><span class='error'>" +
value.substring(match_index) +
'</span>'
}
},
handleInputContent_2(value) {
// 解决输入特殊字符后出现问题,要手动转义
value = this.escapingSpecialCharacters(value)
const reg = new RegExp('^' + value + '$')
const match_result = reg.test(this.original_content_2)
const str_length = value.length
if (match_result) {
this.content_2 =
"<span class='right'>" +
this.original_content_2.substring(0, str_length) +
'</span>' +
this.original_content_2.substring(str_length)
} else {
const right_el = this.$refs.p_ref.querySelector('.right')
if (right_el) {
const right_length = right_el.textContent.length
this.content_2 =
"<span class='right'>" +
this.original_content_2.substring(0, right_length) +
'</span>' +
"<span class='error'>" +
this.original_content_2.substring(right_length, str_length) +
'</span>' +
this.original_content_2.substring(str_length)
} else {
this.content_2 =
"<span class='error'>" +
this.original_content_2.substring(
0,
this.original_content_2.length
) +
'</span>'
}
}
},
handleInputContent_3(e) {
const input_content = e.target.textContent
const reg_input_content = this.escapingSpecialCharacters(input_content)
const reg = new RegExp('^' + reg_input_content + '$')
const is_validate = reg.test(this.content_3)
if (is_validate) {
e.target.innerHTML = "<span class='right'>" + input_content + '</span>'
} else {
let right_index = -1
let compare_str = ''
for (let i = 0; i < input_content.length; i++) {
const str = input_content[i]
compare_str += str
const new_compare_str = this.escapingSpecialCharacters(compare_str)
const str_reg = new RegExp('^' + new_compare_str)
if (str_reg.test(this.content_3)) {
right_index = i
} else {
break
}
}
if (right_index != -1) {
e.target.innerHTML =
"<span class='right'>" +
input_content.substring(0, right_index + 1) +
'</span>' +
"<span class='error'>" +
input_content.substring(right_index + 1) +
'</span>'
} else {
e.target.innerHTML =
"<span class='error'>" + input_content + '</span>'
}
}
const selection = window.getSelection()
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0)
// 插入新输入的文本节点
range.deleteContents() // 清空选区内所有节点
range.collapse(true)
// 这里可能是因为 由于覆盖了 contenteditable属性元素中内容时新输入的内容会在最前面显示
moveCursorToEnd(this.$refs.test_ref)
}
function moveCursorToEnd(el) {
el.focus() // 设置焦点
if (
typeof window.getSelection != 'undefined' &&
typeof document.createRange != 'undefined'
) {
const range = document.createRange()
range.selectNodeContents(el)
range.collapse(false) // 将范围折叠到其结尾处
const sel = window.getSelection()
sel.removeAllRanges() // 清除所有选择范围
sel.addRange(range) // 添加新的选择范围以将光标移至文本末尾
}
}
},
escapingSpecialCharacters(value) {
return value
.replaceAll('.', '\\.')
.replaceAll('^', '\\^')
.replaceAll('$', '\\$')
.replaceAll('*', '\\\\*')
.replaceAll('+', '\\+')
.replaceAll('?', '\\\\?')
.replaceAll('\\', '\\\\')
.replaceAll('|', '\\|')
.replaceAll('[', '\\[')
.replaceAll(']', '\\]')
.replaceAll('{', '\\{')
.replaceAll('}', '\\}')
.replaceAll(';', '\\\\;')
.replaceAll('(', '\\(')
.replaceAll(')', '\\)')
},
},
}
</script>
<style lang="less" scoped>
// /deep/.error {
// color: #f00 !important;
// }
/deep/.right {
color: #2e8b57;
}
/deep/.error {
color: #f00;
}
.text-box {
height: 30px;
border: 1px solid orange;
}
</style>
具有"contenteditable"属性元素修改元素的内容,解决光标不在末尾的情况
<div class="edit_div" contenteditable="true"></div>
<script>
var edit_div_el = document.querySelector(".edit_div");
edit_div_el.addEventListener("input", (e) => {
e.preventDefault();
const input_content = e.target.textContent;
// 新增额外的内容
e.target.innerHTML = input_content + "ok;";
const selection = document.getSelection();
console.log(selection);
if (selection.rangeCount) {
const range = document.createRange();
range.selectNodeContents(edit_div_el);
range.collapse(false);
const sel = window.getSelection();
sel.removeAllRanges(); // 清除所有选择范围
sel.addRange(range);
}
});
</script>