Является ли каретка первой линией текстовой области? На последней строке?

Учитывая текстовое поле с шрифтом нефиксированной ширины, я хочу знать, что ключ, если каретка (как указано element.selectionEnd) находится в первой строке или в последней строке текста.

Enter image description here

Чтобы избежать плохих ответов, вот некоторые решения, которые не работают:

  • Разделение на \n: предложение может быть разбито на две строки, поскольку оно слишком длинное для ширины textarea.
  • Измерение текста перед каретой (например, путем копирования текста в div с тем же стилем и измерения высоты пролета): Некоторые символы после каретки могут изменить точку обертывания (обычно между словами).

Здесь скрипка для тестов и удаление некоторой двусмысленности (да, это элемент textarea, и это не только одна строка и т.д.): http://jsbin.com/qifezupu/4/edit

Примечания:

  • Мне не нужны браузеры Internet Explorer 9 или mobile.
  • Мне нужно надежное решение, работающее на всех позициях каретки.
  • Есть много сложных деталей, которые делают большинство идей непригодными на практике, пожалуйста, создайте рабочую скрипту перед ответом.

Ответ 1

Лучшее решение на данный момент:

  • Создайте временный div с двумя пролетами и стилями CSS, сделанными для имитации (шрифта, wordwrapping, полос прокрутки) оберточного поведения текстового поля.
  • Заполните первый интервал текстом перед кареткой.
  • Заполните второй интервал текстом после каретки.
  • Используйте высоту offset.top и высоту элемента, чтобы узнать, находимся ли мы в первой строке или в последней строке.

Могу ли я увидеть, как он работает?

Я сделал скрипку, которая ясно показывает, как она работает: http://jsbin.com/qifezupu/31/edit

Как его использовать:

Я создал плагин jQuery: https://github.com/Canop/taliner.js

Здесь приведена соответствующая страница демонстрации: http://dystroy.org/demos/taliner/demo.html

Код:

$.fn.taliner = function(){

    var $t = this,
        $d = $('<div>').appendTo('body'),
        $s1 = $('<span>').text('a').appendTo($d),
        $s2 = $('<span>').appendTo($d),
        lh =  $s1.height();

    $d.css({
        width: $t.width(),
        height: $t.height(),
        font: $t.css('font'),
        fontFamily: $t.css('fontFamily'), // for FF/linux
        fontSize: $t.css('fontSize'),
        whiteSpace : 'pre-wrap',
        wordWrap : 'break-word',
        overflowY: 'auto',
        position: 'fixed',
        bottom: 0,
        left: 0,
        padding: 0,
        zIndex: 666
    });

    var lh = $s1.height(),
        input = this[0],
        se = input.selectionEnd,
        v = input.value,
        res = {};

    $s1.text(v);
    res.linesNumber = $s1.height()/lh|0;

    $s1.text(v.slice(0, se));
    $s2.text(v.slice(se));
    res.caretOnFirstLine = input.selectionEnd===0
        || ($s1.height()<=lh && $s1.offset().top===$s2.offset().top);
    res.caretOnLastLine = $s2.height()===lh;

    $d.remove();
    return res;
}

Действительно ли это работает?

По-прежнему существует проблема. Единственная информация, которую мы получаем в JavaScript относительно позиции каретки, - element.selectionEnd. И это очень плохо.

Вот возможные позиции каретки в двухстрочном текстовом поле:

| A | B | C |
| D | E |

Как вы можете видеть, есть более возможные позиции каретки, чем межсимвольные положения. Более конкретно, у вас есть две разные позиции каретки между C и D, но DOM не дает возможности различать их.

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

Ответ 2

Существует несколько способов получения позиции каретки (в пикселях) внутри текстового поля:
Смещение каретки в текстовом поле в пикселях

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

Я не тестировал ни один из методов, но предположил, что функция, используемая для получения позиции каретки (в пикселях), возвращает компонент y по отношению к нижней части каретки.
Вы указываете высоту строки 15 px.
Пусть say возвращает метод (20, 45). Текущая строка = y/15 = 45/15 = 3

JS Fiddle, чтобы продемонстрировать, как это можно сделать на основе решения Дэна:
Текущая строка из координат каретки

Я добавил диапазон, в котором отображается текущая строка. К сожалению, мои тесты в Chrome показывают, что это не на 100% точно: когда вы выбираете третью строку с помощью мыши, вы говорите, что находитесь на 4-й строке.

Line:
<span id="line">0</span>

В конце функции update (в разделе HTML) я добавил следующую строку:

//24 is the top padding of the textarea
//16 is the line-height
line.innerText = (coordinates.top - 24) / 16;

Ответ 3

Другое решение, основанное на том, что @Flater говорит о добавлении ширины текста. Идея (в резюме):

  • Получить ширину текстовой области
  • Получить текст до позиции курсора и рассчитать его ширину
  • Получить текст после позиции курсора и рассчитать ширину
  • Если перед текстом > ширина текстовой области или не содержит разрыва строки (если текст на первой строке меньше), курсор не сначала линия
  • Если после текстa > ширина текстовой области или не содержит разрыва строки, курсор не находится в последней строке

код:

$('#t').keyup(function(e){
  first_line="true";
  last_line="true";
  text = $(this).val();
  width = $(this).width();
  var cursorPosition = $(this).prop("selectionStart");
  txtBeforeCaret = text.substring(0, cursorPosition);
  txtAfterCaret = text.substring(cursorPosition);
  widthBefore = $('#holder').text(txtBeforeCaret).width();
  widthAfter = $('#holder').text(txtAfterCaret).width();
  match1 = txtBeforeCaret.match(/\n/);
  if(txtAfterCaret!==null){match2=txtAfterCaret.match(/\n/g);}
  if(widthBefore>width || match1){first_line="false";}
  if(widthAfter>width || (match2!==null && match2.length)) {last_line="false";}
  $('#f').html(first_line);
  $('#l').html(last_line);
});

#holder - это div, созданный и скрытый (чтобы найти ширину текста).

DEMO

Ответ 4

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

Короче, посмотрите на jsFiddle. Каждый раз, когда вы меняете текст в текстовом поле, вы будете уведомлены о ширине текста.

Поэтому я предлагаю сделать следующее:

  • Найдите как первую, так и последнюю строку вашего содержимого textarea. Следующие шаги применяются к любой строке.
  • Чтобы узнать, была ли строка разбита из-за ширины textarea, используйте мой предложенный фрагмент, чтобы проверить, больше ли ширина текста больше ширины textarea.
  • Если он длиннее, вы можете итеративно начать проверку подстрок строки. Если вы найдете тот, который соответствует ширине вашего текстового поля, вы можете быть уверены, что там, где разрыв строки.

Я не уверен, какие действия вы хотите предпринять, но вы можете использовать этот метод для приближения, где находится ваш карет. Это может быть не идеальный пиксель, хотя, так как ширина div немного отличается от ширины текста.

Здесь фрагмент для измерения ширины текста:

$(document).ready(function() {

    $("#txtbox").keyup(function() {
        var the_Text = $("#txtbox").val();

        var width = $("#testdiv").text(the_Text).width();

        alert("The text width is : " + width + " px");
    });

});

$("#testdiv") - скрытый div на странице. Ничего необычного, единственный используемый CSS - убедиться, что пользователь этого не видит.

Ответ 5

Вот скрипка с использованием этого плагина jQuery (https://github.com/Codecademy/textarea-helper), чтобы получить позицию каретки (позиция y - это то, что вам нужно): http://jsbin.com/qifezupu/11/edit

К сожалению, некоторые настройки необходимы, как вы увидите. Это всего лишь идея.