文本标记实现方案分享
先看如下两个效果:
- 可以选中文字进行标记
- 文本存在换行,标记也可以跨行
- 标记有背景色,不太标记可以交叉
两个有较大的相似之处,因此一并分析,首先显示文字的是一个textarea,textarea里面必定是纯文本,那么背景色如何实现呢。
不饶弯子,直接看下图, 为了方便理解,我将每个标记再原本的位置上依次向右下偏移 + 50 px
图中1为本身的 textarea ,用来显示文本和用户进行选择, 背景色透明。
图中2~5为4个标记图层,如下特点:
- 他们每一个都和 textarea 有相同的文本内容, 这样通过设置相同的字体、行高等属性,可使得和文字显示完美重叠
- 文本颜色是透明的。 这样在重叠的基础上,文字不会显示出来不会有显示问题
- 标记的区域被一个原生包裹,有背景色。这样即实现了标记区域的背景色
- 标记图层的
z-index
比输入框低,或者使用pointer-events: none;
使得鼠标可穿透,建议使用前者兼容性更好。
这样核心的功能就完成了,不过还有几点需要额外注意:
- 除字体、行高、编辑、边框等容易想到的属性之外, div 和 textarea 中默认的
white-space
word-break
word-wrap
的默认值是不一样的,需要统一处理一次。 - textarea中是纯文本,div中的内容是可以是富文本的,创建写入之前需要处理进行转义。
创建核心代码如下:
/**
* 标记高亮构建
*
* @param {string} txt 整个文本
* @param {{from:number,to:number,id?:string,type:string}} item 当前标记对象描述
*/
function buildHightlightItem(txt, item) {
var s = item.from;
var e = item.end;
var id = item.id;
function encodeAndWrap(str) {
return Util.htmlEscape(str).replace(/\n/g, '<br>');
}
if (!id) {
id = Util.uuid();
}
var html = [
encodeAndWrap(txt.substring(0, s)),
'<span class="' + HIGH_LIGHT_CLS + '" style="background-color: ' + getBgColor(item.type) + '">',
encodeAndWrap(txt.substring(s, e)),
'</span>',
encodeAndWrap(txt.substring(e))
].join('');
var $div = $('<div>')
.attr({
class: HIGH_LIGHT_WRAP_CLS,
'data-id': id
})
.html(html);
$div.appendTo($textWrap);
}
选中区域变化的监听,可通过文本框的 select
时间来进行处理,dom 上的 selectionStart
和 selectionEnd
即为选中区域在字符串中的开始和结束位置。
$markupText.on('select', function (e) {
var target = e.target;
var from = target.selectionStart;
var end = target.selectionEnd;
if (from === end) {
eventEmitter.fire('clear');
return;
}
if (from > end) {
var t = from;
from = end;
end = t;
}
var text = target.value.substring(from, end);
if (text) {
eventEmitter.fire('select', {
from: from,
end: end,
text: text
});
} else {
eventEmitter.fire('clear');
}
});