Объект Range: различия между браузерами на основе Webkit и Mozilla - PullRequest
6 голосов
/ 20 сентября 2011

В настоящее время у меня возникают проблемы с написанием слоя абстракции для браузеров на основе Mozilla и Webkit для использования объекта диапазона DOM (получение и обработка пользовательских выборок).

Я также пытался взглянуть на фреймворки, такие как Rangy, но это кажется слишком сложным для моей задачи (я не знаю, где именно в коде найти нужную мне информацию. Если кто-то может дать мне подсказку, я был бы признателен!).

То, что я хочу, это просто:

  • возвращает ссылку на текстовый узел, в котором начинается выделение, и его смещение
  • возвращает ссылку на текстовый узел, в котором заканчивается выделение, и его смещение

Пока мой слой выглядит так:

var SEL_ABSTR = {
get_selection: function(window_object) {
    return window_object.getSelection();
},
get_range: function(selection) {
    return (selection.getRangeAt) ? selection.getRangeAt(0) : selection.createRange();
},
get_range_info: function(range, div_ele) {
    var first_node, start_offset;
    var last_node, end_offset;

    if (range.startContainer == div_ele) {
        // selections affects the containing div
        first_node = div_ele.childNodes[0];
        last_node = first_node;
        start_offset = 0;
        end_offset = first_node.nodeValue.length;
    } else if (range.startOffset == range.startContainer.nodeValue.length && range.endOffset == 0) {
        // known bug in Firefox
        alert('firefox bug');
        first_node = range.startContainer.nextSibling.childNodes[0];
        last_node = first_node;
        start_offset = 0;
        end_offset = first_node.nodeValue.length;
    } else {
        first_node = range.startContainer;
        last_node = range.endContainer;
        start_offset = range.startOffset;
        end_offset = range.endOffset;
    }

    return {
        first_node: first_node,
        start_offset: start_offset,
        last_node: last_node,
        end_offset: end_offset,
        orig_diff: end_offset - start_offset
    };
},
};

На данный момент я обнаружил две ошибки в Mozilla:

  1. Иногда, когда весь (если он единственный) текстовый узел выбирается в содержащем div, я получаю ссылку на этот div вместо ссылки на текстовый узел. Теперь я могу справиться с этим и вернуть ссылку на дочерний элемент div, который является текстовым узлом

  2. Иногда я получаю ссылку на предыдущего брата со смещением == prevSibling.length и ссылку на следующий брата со смещением == 0. Но правильная ссылка будет в середине. Я также могу справиться с этим.

Так что еще есть для Мозиллы? Webkit работает отлично!

Следует предположить, что объект диапазона DOM является стандартным (и я не говорю о IE, это другая задача ...)

приветствует!


более конкретные подробности здесь:

Это не было предназначено для критики по Ранги. Поэтому извините, если это звучало так. Я могу себе представить, что обрабатывать эти разные API нелегко само по себе.

Вы правы, я не был конкретен в отношении задачи, которую пытаюсь выполнить. Моя структура довольно проста: у меня есть div (с атрибутом contenteditable = true) и текст внутри этого div (один текстовый узел в начале).

Начиная с этой структуры, теперь можно выделять текст мышью и добавлять к нему свойство; это свойство затем выражается диапазоном, охватывающим выделенный текст, и классом, назначенным этому диапазону, представляющему выбранное свойство. Теперь можно снова выбрать текст и свойство. Если это тот же текст и другое свойство, этому классу будет присвоен другой класс или он будет удален, если свойство уже существует. Если выделен текст, охватывающий несколько интервалов, он будет разделен, чтобы выразить эту структуру (возможно, вы помните мой пост в июле).

<div contenteditable="true">
hello I am 
<span class="red">text but I am also <span class="underline">underlined</span></span>
<span class="underline"> also without color</span>
</div>

Алгоритм теперь отлично работает для "симметричных" случаев: я могу построить сложную структуру и затем отменить ее в обратном направлении. Он отлично работает для Safari и Chrome. Теперь я, конечно, должен развивать алгоритм дальше.

Но сейчас у меня проблемы с Mozilla, потому что я не понимаю систему для объектов диапазона DOM: startContainer, endContainer, startOffset, endOffset

В моем восприятии моего конкретного случая с div, содержащим только текстовые узлы и промежутки, я предполагаю:

  • , что startContainer и endContainer всегда указывают на текстовый узел, на который влияет выделение мыши (нет пустых интервалов, они всегда содержат либо другие интервалы, либо текстовый узел), отмечая начало и конец всего выделения
  • , что startOffset и endOffset указывают позицию выделения в текстовом узле в начале и в конце

В приведенном выше коде я выделил два случая, когда Mozilla действует не так, как webkit.

Так что, если бы я знал правила DOM-диапазона Mozilla, я мог бы интегрировать их в свой слой, чтобы поведение было одинаковым для webkit и Mozilla.

Большое спасибо за ваш ответ.

Ответы [ 2 ]

3 голосов
/ 23 мая 2012

Вы замечаете ошибку, когда Gecko / Firefox и Presto / Opera неправильно выбирают узел, в котором щелкнул курсор мыши, хотя и не выбрали. Чего ждать? Случается так, что если щелчок регистрируется меньше, чем ПОЛОВИНА символа (хотя больше, чем 0 пикселей), он ВИЗУАЛЬНО не выбирается ОДНАКО Firefox и Opera по-прежнему выбирают сам узел! Trident / IE и WebKit (Chrome / Safari) этого не делают.

Я некоторое время боролся с этой ошибкой, подал ошибку в Mozilla (не помню, делал ли я это с Opera, так как это было в прошлом году), и сегодня, наконец, написал напрямую редакторам спецификации DOM4, спрашивая их реализовать разъяснение EXPLICIT для поставщиков браузеров о том, как определить startContainer.

Возможно адаптировать код, чтобы справиться с этим, однако у меня нет времени выписать весь код для вас.

Вот список методов, которые мы будем использовать ...

window.getSelection().getRangeAt(0).startContainer;

window.getSelection().anchorNode

window.getSelection().focusNode

ОЧЕНЬ важно помнить, что anchorNode - это НЕ ТОЛЬКО левая начальная позиция, хотя НАЧАЛЬНЫЙ ЩЕЛЧОК. Если пользователь щелкает по правой стороне текста и перетаскивает мышь влево, якорь заканчивается в правой части диапазона. Если пользователь нажимает на левую часть текста, а затем перетаскивает мышь вправо, якорь находится в левой части диапазона.

По сути, вы можете попытаться увидеть, не соответствует ли ни anchorNode, ни focusNode EXPLICITLY startContainer.

var sc = window.getSelection().getRangeAt(0).startContainer;
var an = window.getSelection().anchorNode
var fn = window.getSelection().focusNode

if (sc!==an && sc!==fn) {alert('startContainer bug encountered!');}

Даже если вам не нужен startContainer во ВСЕХ ситуациях, вы все равно будете в конечном итоге ссылаться на него, поэтому лучше использовать объект для представления startContainer, будь то если браузер правильно с первого раза (Trident / WebKit) или вы должны исправить его (Gecko / Presto).

Здесь немного сложно, особенно потому, что у разных людей разные цели и подходы, поэтому я постараюсь сделать следующее как можно более общим.

Либо вы можете определить правильные startContainer, используя anchorNode или focusNode методы, либо вы можете использовать обнаружение объектов и методы, совместимые с W3C. Эти другие методы включают ....

window.getSelection().getRangeAt(0).startContainer
window.getSelection().getRangeAt(0).startContainer.parentNode
window.getSelection().getRangeAt(0).startContainer.previousSibling
window.getSelection().getRangeAt(0).startContainer.nextSibling
window.getSelection().getRangeAt(0).startContainer.childNodes[]

При работе с такими элементами стиля, как s (удар), strong, em (выделение) и т. Д., Вы можете получить доступ к textNode с помощью firstChild, если вокруг текста не заключено несколько элементов стиля. .

.nextSibling.firstChild
.nextSibling.firstChild.nodeValue
<em>textNode here</em>

Если вам сложно определить, какие методы доступны и в каких частях я рекомендую использовать оператор in. В пример ...

for (i in window.getSelection())
{
 document.getElementById('textarea_example').value = document.getElementById('textarea_example').value+'\n'+i;
}

... имейте в виду, что если вы находитесь внутри цикла, он может повторять параметры в вашем элементе textarea, поэтому CTRL + f для первого метода и стирайте его второй экземпляр вниз, чтобы сохранить только соответствующие методы.


Не забудьте использовать оповещение, и я часто использую несколько строк, чтобы показать несколько фрагментов информации одновременно, чтобы помочь мне определить, что у меня есть. В пример ...

var e1 = scp.nodeName;
if (scp.nextSibling) {var e2 = scp.nextSibling.nodeName;} else {var e2 = 'null';}
var e3 = sc.nodeName;
if (sc.nextSibling) {var e4 = sc.nextSibling.nodeName;} else {var e4 = 'null';}

alert(
'startContainer = '+window.getSelection().getRangeAt(0).startContainer.nodeName
+'\n\n'+
'startContainer = '+window.getSelection().getRangeAt(0).startContainer.nodeValue
+'\n\n'+
e1
+'\n\n'+
e2
+'\n\n'+
e3
+'\n\n'+
e4
+'\n\nanchorNode = '+
window.getSelection().anchorNode.nodeName
+'\n\n'+
window.getSelection().anchorNode.nodeValue
+'\n\nfocusNode = '+
window.getSelection().focusNode.nodeName
+'\n\n'+
window.getSelection().focusNode.nodeValue
);

if (e2=='#text') {alert('e2 = '+scp.nextSibling.nodeValue);}
if (e4=='#text') {alert('e4 = '+scp.nextSibling.nodeValue);}
3 голосов
/ 20 сентября 2011

Нет правила, согласно которому границы выбора должны быть выражены в терминах текстовых узлов.Например, рассмотрим выделение внутри элемента, который содержит только <img> элементов.Итак, то, что вы называете ошибками в Mozilla, вовсе не является ошибками;на самом деле, обработка выделения в WebKit намного сложнее, чем в в Mozilla.Тем не менее, ваше наблюдение о том, что браузеры различаются в том смысле, в котором они считают, что граница выделения находится в конце текстового узла, является верным и усложняет ситуацию.Лучший способ справиться с этим на самом деле зависит от того, что вы пытаетесь сделать, что неясно из вашего вопроса.

Если вы хотите, чтобы границы выделения были чисто с точки зрения смещения символов в текстовом содержимом текста.element, вы можете сделать это (хотя я бы вообще рекомендовал против этого по причинам, изложенным в связанном ответе).

Наконец, как автор Rangy, я хотел бы указатьиз-за того, что он основан на тех же API (DOM Range и Selection), которые реализуют браузеры, так что я бы сказал, что он не более или менее сложен, чем эти API.Список литературы:

...