Выделение текстового диапазона с использованием JavaScript
Я хотел бы выделить (применить css to) определенный диапазон текста, обозначенный его начальной и конечной позицией. Это более сложно, чем кажется, поскольку в тексте могут быть другие теги, которые нужно игнорировать.
Пример:
<div>abcd<em>efg</em>hij</div>
highlight(2, 6)
необходимо выделить "cdef
"без удаления тега.
Я уже пробовал использовать объект TextRange, но безуспешно.
Спасибо заранее!
Ответы
Ответ 1
Ниже приведена функция, чтобы установить выделение на пару смещений символов в пределах определенного элемента. Это наивная реализация: она не учитывает любой текст, который может быть невидимым (например, CSS или находясь внутри элемента <script>
или <style>
) и может иметь отклонения браузера (IE по сравнению со всеми остальными) с разрывами строк и не учитывает свернутые пробелы (например, два или более последовательных символа пробега, совпадающие с одним видимым пространством на странице). Тем не менее, он работает для вашего примера во всех основных браузерах.
С другой стороны, подсветка, я бы предложил использовать document.execCommand()
для этого. Вы можете использовать мою функцию ниже, чтобы установить выделение, а затем вызвать document.execCommand()
. Вам нужно будет сделать документ временно редактируемым в браузерах, отличных от IE, для того, чтобы команда работала. См. Мой ответ здесь для кода: getSelection и surroundContents для нескольких тегов
Вот пример jsFiddle, показывающий все это, работающий во всех основных браузерах: http://jsfiddle.net/8mdX4/1211/
И код установки выбора:
function getTextNodesIn(node) {
var textNodes = [];
if (node.nodeType == 3) {
textNodes.push(node);
} else {
var children = node.childNodes;
for (var i = 0, len = children.length; i < len; ++i) {
textNodes.push.apply(textNodes, getTextNodesIn(children[i]));
}
}
return textNodes;
}
function setSelectionRange(el, start, end) {
if (document.createRange && window.getSelection) {
var range = document.createRange();
range.selectNodeContents(el);
var textNodes = getTextNodesIn(el);
var foundStart = false;
var charCount = 0, endCharCount;
for (var i = 0, textNode; textNode = textNodes[i++]; ) {
endCharCount = charCount + textNode.length;
if (!foundStart && start >= charCount
&& (start < endCharCount ||
(start == endCharCount && i <= textNodes.length))) {
range.setStart(textNode, start - charCount);
foundStart = true;
}
if (foundStart && end <= endCharCount) {
range.setEnd(textNode, end - charCount);
break;
}
charCount = endCharCount;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (document.selection && document.body.createTextRange) {
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(true);
textRange.moveEnd("character", end);
textRange.moveStart("character", start);
textRange.select();
}
}
Ответ 2
Вы можете посмотреть, как работает эта мощная утилита JavaScript, которая поддерживает выбор по нескольким элементам DOM:
МАША (сокращение от Mark and Share) позволяет вам отмечать интересные части содержимого веб-страницы и делиться ею
http://mashajs.com/index_eng.html
Это также на GitHub https://github.com/SmartTeleMax/MaSha
Работает даже на Mobile Safari и IE!
Ответ 3
Следующее решение не работает для IE, для этого вам нужно применить объекты TextRange и т.д. Поскольку для этого используется выбор, он не должен нарушать HTML в обычных случаях, например:
<div>abcd<span>efg</span>hij</div>
С highlight(3,6);
выходы:
<div>abc<em>d<span>ef</span></em><span>g</span>hij</div>
Обратите внимание, как он переносит первый символ за пределы диапазона в em
, а затем остальные в пределах span
в новый. Если бы он просто открыл его на символе 3 и заканчивался символом 6, это дало бы недопустимую разметку, например:
<div>abc<em>d<span>ef</em>g</span>hij</div>
Код:
var r = document.createRange();
var s = window.getSelection()
r.selectNode($('div')[0]);
s.removeAllRanges();
s.addRange(r);
// not quite sure why firefox has problems with this
if ($.browser.webkit) {
s.modify("move", "backward", "documentboundary");
}
function highlight(start,end){
for(var st=0;st<start;st++){
s.modify("move", "forward", "character");
}
for(var st=0;st<(end-start);st++){
s.modify("extend", "forward", "character");
}
}
highlight(2,6);
var ra = s.getRangeAt(0);
var newNode = document.createElement("em");
newNode.appendChild(ra.extractContents());
ra.insertNode(newNode);
Пример: http://jsfiddle.net/niklasvh/4NDb9/
edit Похоже, что у моего FF4 были проблемы с
s.modify("move", "backward", "documentboundary");
но в то же время он работает без него, поэтому я просто изменил его на
if ($.browser.webkit) {
s.modify("move", "backward", "documentboundary");
}
Забастовкa >
изменить
по мере того как Тим указал, изменение доступно только с FF4, поэтому я принял другой подход к выбору, который не нуждается в методе модификации, в надежде сделать его более совместимым с браузером (IE все еще нуждается в собственном решении).
Код:
var r = document.createRange();
var s = window.getSelection()
var pos = 0;
function dig(el){
$(el).contents().each(function(i,e){
if (e.nodeType==1){
// not a textnode
dig(e);
}else{
if (pos<start){
if (pos+e.length>=start){
range.setStart(e, start-pos);
}
}
if (pos<end){
if (pos+e.length>=end){
range.setEnd(e, end-pos);
}
}
pos = pos+e.length;
}
});
}
var start,end, range;
function highlight(element,st,en){
range = document.createRange();
start = st;
end = en;
dig(element);
s.addRange(range);
}
highlight($('div'),3,6);
var ra = s.getRangeAt(0);
var newNode = document.createElement("em");
newNode.appendChild(ra.extractContents());
ra.insertNode(newNode);
example: http://jsfiddle.net/niklasvh/4NDb9/
Ответ 4
Проверьте https://github.com/mir3z/texthighlighter, который делает это очень хорошо.