效果:

思路: 讓一個div浮動在textarea上,樣式和位置保持完全一致,textarea負責輸入,div負責高亮顯示
代碼:
.vue
<template> <div class="highlight-contain"> <!-- 本組件是帶高亮的textarea,需要接受高亮關鍵詞數組來進行高亮 --> <div id="highlight-area" class="input-font el-textarea" > <div id="fake-textarea" class="el-textarea__inner" v-html="highlightHtml"></div> </div> <div id="input-area"> <el-input class="input-font" id="input-textarea" type="textarea" :placeholder="placeholder" :autosize="autosize" v-model="strValue" @input="getHighlightHtml" @mousemove.native="setHighlightArea('height',true)" ></el-input> </div> </div> </template> <style lang="postcss"> /* 這里是為了讓textarea中的文字隱藏,同時這只光標和placeholder顏色 */ #input-area .el-textarea .el-textarea__inner { color: #606266; /* 光標的顏色*/ text-shadow: 0px 0px 0px rgba(0, 0, 0, 0); /* 文本顏色 */ -webkit-text-fill-color: transparent; &::-webkit-input-placeholder { color: #dcdfe6; /* 改變placeholder文本顏色 */ text-shadow: none; -webkit-text-fill-color: initial; } } .highlight-contain { position: relative; & #highlight-area { /* 自定義樣式 */ position: absolute; left: 0px; top: 0px; pointer-events: none; & #fake-textarea { /* color: #ec140d; */ pointer-events: none; border: none; resize: none; background-color: rgba(0, 0, 0, 0); line-height: 1.5 !important; max-height: 590px; overflow-y: auto; /* 和html中的類一樣,都是為了設定和textarea一模一樣的樣式,防止文字對接不上(這里是復制的瀏覽器中textarea元素屬性) */ -webkit-appearance: textarea; -webkit-rtl-ordering: logical; -webkit-writing-mode: horizontal-tb !important; flex-direction: column; white-space: pre-wrap; word-wrap: break-word; text-rendering: auto; letter-spacing: normal; word-spacing: normal; text-transform: none; text-indent: 0px; text-shadow: none; text-align: start; margin: 0em; font: 400 14px Arial; } } } /* 高亮色 */ .highlight-11 { color: #fb4546; } .highlight-10 { color: #0fed40; } .highlight-9 { color: #feb71d; } .highlight-8 { color: #39afea; } .highlight-7 { color: #e512bf; } .highlight-6 { color: #0f29ed; } .highlight-5 { color: #f088c1; } .highlight-4 { color: #acbb09; } .highlight-3 { color: #7a152e; } .highlight-2 { color: #7ca51c; } .highlight-1 { color: #5e36aa; } .highlight-bracket { color: #000000; } </style> <script lang="ts" src="./highlightTextarea.ts"></script>
.ts
import { Vue, Component, Prop } from "vue-property-decorator"
import $ from 'jquery'
@Component({})
export default class HighlightTextarea extends Vue {
/* ---- 從父元素接受參數 ---- */
@Prop()
value: string
@Prop()
placeholder: string
@Prop()
autosize: { minRows: number, maxRows: number }
@Prop()
highlightKey: string[] //要高亮的詞
/* ---- 變量 ---- */
get strValue() {
return this.value ? this.value : ''
}
set strValue(val) {
this.$emit('input', val)
}
highlightHtml: string = '';
/* ---- 函數 ---- */
setHighlightArea(type: string, right?: boolean) {
if (type === 'height') {
if (right) {
let height = document.getElementById('input-textarea').style.height;
document.getElementById('fake-textarea').style.height = height;
} else {
window.setTimeout(() => {
let height = document.getElementById('input-textarea').style.height;
document.getElementById('fake-textarea').style.height = height;
}, 100);
}
} else if (type === 'scrollTop') {
if (right) {
let scroll = document.getElementById('input-textarea').scrollTop
document.getElementById('fake-textarea').scrollTop = scroll;
} else {
window.setTimeout(() => {
let scroll = document.getElementById('input-textarea').scrollTop
document.getElementById('fake-textarea').scrollTop = scroll;
}, 100);
}
}
}
getHighlightHtml(val) {
if (val.split('\n').length > this.autosize.maxRows) { //超過最大行textarea有滾動時,為解決div底部不能和textarea重合,故加一個<br/>,並延時設置scrolltop
this.highlightHtml = this.highlightStr(val, this.highlightKey) + '<br/>';
this.setHighlightArea('scrollTop', false);
} else {
this.highlightHtml = this.highlightStr(val, this.highlightKey)
}
// 高亮區和輸入區高度保持一致
this.setHighlightArea('height');
}
/**
* 高亮方法:
* 1.將oriStr中的高亮關鍵字使用“{{{關鍵字}}}”替換,這里防止關鍵詞數組中有包含關系,所有用空格區分oriStr
* 2. 然后再循環highlightKey用<span class="..."></span>替換文中的{{{和}}}
* @param oriStr 要高亮的字符串
* @param highlightKey 高亮關鍵詞
*/
highlightStr(oriStr: string, highlightKey: string[]): string {
if (!oriStr || !highlightKey || highlightKey.length === 0)
return oriStr;
let strConvert = (s: string, key: string): string => {
let rowArr = s.split('\n'); //按行進行處理
for (let i = 0; i < rowArr.length; i++) {
let strArr = rowArr[i].split(' ').filter(item => item !== '');
strArr = strArr.map(item => {
if (item === key) {
item = `{{{${item}}}}`
}
return item;
})
rowArr[i] = strArr.join(' ')
}
return rowArr.join('\n');
}
let rebuild = highlightKey.reduce(strConvert, oriStr);
let regExp;
let regStr;
for (let i = 0; i < highlightKey.length; i++) {
regStr = '\\{\\{\\{' + this.escapeString(highlightKey[i]);
regExp = new RegExp(regStr, 'g');
if (highlightKey[i] === '(' || highlightKey[i] === ')') { //小括號顏色
rebuild = rebuild.replace(regExp, `<span class="highlight-bracket">${highlightKey[i]}</span>`)
} else {
rebuild = rebuild.replace(regExp, `<span class="highlight-${i + 1}">${highlightKey[i]}`)
}
}
rebuild = rebuild.replace(/\}\}\}/g, '</span>');
return rebuild;
}
//處理字符串中可能對正則有影響的字符串
escapeString(value: string): string {
var str = value.replace(new RegExp('\\\\', 'g'), '\\\\');
var characterss = ['(', ')', '[', ']', '{', '}', '^', '$', '|', '?', '*', '+', '.'];
characterss.forEach(function (characters) {
var r = new RegExp('\\' + characters, 'g')
str = str.replace(r, '\\' + characters)
})
return str;
}
/* ---- 生命周期 ---- */
mounted() {
this.highlightKey.sort((a, b) => b.length - a.length);// 為了使高亮正常,關鍵詞長的排在前面
$('#input-textarea').scroll((e) => {
this.setHighlightArea('scrollTop', true);
});
}
}
使用:import HighlightTextarea from "..."引入后,
<highlight-textarea :highlightKey="['=', '<', '>', '!=', '<=', '>=', 'like', '(', ')']" placeholder="請輸入監測" :autosize="{minRows: 4, maxRows: 10}" v-model="str"> </highlight-textarea>
