效果:
思路: 让一个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(' ').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(' '); //按行进行处理 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(' '); } 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>