Pull to refresh

Автоизменение высоты textarea при вводе текста

Reading time 4 min
Views 62K
Обычно я занимаюсь серверным программированием на php, но время от времени выхожу наружу и копаюсь в верстке, стилях и яваскрипте. Недавно передо мной была поставлена задача изменять высоту textarea при вводе комментариев к различным объектам. В интернете материала по этому поводу так, сказать было не мало и не много. Первый взгляд устремился к реализованным решениям в таких крупных сетех как Вконтакте, Facebook, МойКруг. Однако, во время решения было множество препятствий и далеко не кросбраузерность.



Варианты решения были различны:
* опираться на свойства scrollHeight и offsetHeight
* вычислить кол-во используемых строк распарсив текст
* или использовать идею из выше описанных сетей.

Окончательным вариантом стало вот такое решение. Для textarea создаётся невидимый слой (div) с тойже шириной, шрифтом, размером шрифта, отступами, далее только что изменённый текст копируется в невидимый слой, у слоя узнаётся высота и эта высота устанавливается у textarea. Звучит просто, но в процессе реализации нашлось немало подводных камней.
В текущей реализации используется prototype версии 1.7, но и более ранние тоже пойдет (можно заменить на JQuery или совсем вырезать фреймворк).

<!doctype><html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Auto-size TextArea Demo</title>
<script type="text/javascript" src="/js/prototype/prototype.js"></script><script>
function resizeArea(text_id, minHeight, maxHeight)
{
   var area = $(text_id);
   var area_hidden = $(text_id + "_hidden");
   var text = '';
   area.value.replace(/[<>]/g, '_').split("\n").each( function(s) {
           text = text + '<div>' + s.replace(/\s\s/g, ' &nbsp;') + '&nbsp;</div>'+"\n";
   } );
   area_hidden.innerHTML = text;
   var height = area_hidden.offsetHeight + 15;
   height = Math.max(minHeight, height);
   height = Math.min(maxHeight, height);
   area.style.height = height + 'px';
}
</script><style>
body, textarea {
       font-family: Tahoma, Arial, 'Nimbus Sans L', sans-serif;
       font-size: 13px;
}
.text {
       width:700px !important;
       border:1px solid #000;
}
.text .textarea_behavior{
       border:0;
       width:99%;
       word-wrap: break-word;
}
.text textarea{
       overflow:hidden;
}
.text .comment_text_hidden{
       position: relative;
}
.text #comment_text_hidden{
       visibility:hidden;
       position: absolute;
}
</style></head><body>
<div class="text">
       <div class="comment_text_hidden"><div class="textarea_behavior" id="comment_text_hidden"></div></div>
       <textarea class="textarea_behavior" rows="3" id="comment_text" onkeyup="resizeArea('comment_text', 45, 450);"></textarea>
</div>
</body></html>


Решение может показаться кому-то очень компактным, другим наоборот безумно большим. Одно точно это единственный конечный варинта одинаково хорошо работающий на всех доступных мне браузерах (FF, Ch, Op, IE7-9).

В чем особенность подхода: нельзя просто спросить у textarea сколько места занимает текст, поэтому помещаем текст туда, где можно узнать сколько он действительно занимает места (в нашем случае интересна только высота). Вот тут то и возникает множество подводных камней. Как поместить текст из textarea в div, чтобы в точности сохранить вид и занимаемый объём текста, переносы слов, теги, множественные пробелы — вобщем всё, что в обычном тексте выглядит не так, как в html. Просто пользоваться тегом pre не удалось, т.к. приручить его оказалось сложнее.

По порядку все подводные камни встреченные мною (часть проблем я здесь не привожу, т.к. код эволюционировал и некоторые ранее решенные проболемы просто не могут проявить себя в этом коде). Получается здесь лишь проблемы решённые кодом:

  • Как спрятать слой, чтоб он сохранил свои размеры и свойства. Причем нельзя делать display:none; — теряются размеры. Нельзя выкидывать блок всторону (position:absolute; left: -50000px;), т.к. размер блока задать нельзя, они не известены на момент вставки. Решение должно быть универсальным и легко встраиваемым на любую страницу.
  • Обойти множество пробелов и пустых строк.
  • Экранировать теги без изменения длины текста.
  • Перенос непрерывного текста.


Решение приведенных проблем хорошо отражено в коде. Слой прячется совсем рядом с textarea, это позволяет не думать о задании ширины слою. Подряд несколько пробелов заменяются на пробол + &nbsp;. Пустые строки — <div>&nbsp;</div> (все соц сети приведенные выше используют <br>, но у меня не получилось). Теги экранируются очень просто: символы < и > заменяются на _. Ну и самое интересное для меня открытие, это разрыв непрерывного текста стилями word-wrap: break-word;.
Ну и самое главное в этой идеи — полное совпадение стилей слоя и textarea.

Недостатки других подходов


1. Опираться на свойства scrollHeight и offsetHeight — первый реализованный мною вариант. Всё бы ничего, но вот уменьшать размер textarea тут не удастся.

2. Вычислить кол-во используемых строк распарсив текст. Второй вариан. Это уже получше, но как выяснилось применять можно лишь установив в textarea шрифт типа Monospace, где все символы одной ширины. Только так можно добиться нормальной точности. Однако, это противоречит задумкам дизайнеров…

Приведу код для любопытных (часть функи resizeArea):

var linecount = 1;
area.value.split("\n").each( function(s) {
       linecount += Math.floor( s.length / cols ) + 1;
} )
area.rows = linecount;


Считаем строки и плюсуем длинные строки. Надо лишь знать параметр cols — вот как раз этого мы знать и не можем без шрифта Monospace. Если сравнить кол-во букв W и i, то в строку входит совсем разное кол-во.

Результат


Результат проделанной работы приведен выше. Работает в FF, Ch, Op, IE7-9. Надеюсь он вам будет полезен. После реализации всевозможнейших вариантов так захотелось иметь юнит-тесты на гуйню всех браузеров…

Интересные материалы

Переносы длинных ссылок и непрерывного текста с помощью CSS
Autosizing Textarea
jQuery autoResize
Tags:
Hubs:
+5
Comments 12
Comments Comments 12

Articles