Просмотр полной версии : Отображение строк у TextArea
Alikberov
05.09.2022, 01:00
Сaмый простой способ - поставить два TextArea рядом и в левом отображать все номера строк, имеющихся в правом, синхронизируя по скроллингу.
Однако, такой подход, хоть визуально практически не имеет глюков, крайне медленный, если довольно активно число строк растёт.
Самый быстрый способ - через OL-список, так как требуется попросту создать несколько пустых пунктов, число которых эквивалентно количеству отображаемых строк текста.
Но за месяц экспериментов я так и не смог выйти из тупика глюков.<html>
<head>
<title>Построчная нумерация</title>
<style>
div
{
position :absolute;
width :auto;
height :auto;
}
header
{
background-color:blue;
color :yellow;
}
footer
{
background-color:silver;
color :black;
border :medium silver sunken;
}
</style>
</head>
<body>
<div>
<textarea rows=15 cols=105 spellcheck=false title='Текстовое поле для редактирования'>
Sed ut perspiciatis,
unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam eaque ipsa,
quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt,
explicabo.
Nemo enim ipsam voluptatem,
quia voluptas sit,
aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos,
qui ratione voluptatem sequi nesciunt,
neque porro quisquam est,
qui dolorem ipsum,
quia dolor sit,
amet,
consectetur,
adipisci velit,
sed quia non numquam eius modi tempora incidunt,
ut labore et dolore magnam aliquam quaerat voluptatem.
Ut enim ad minima veniam,
quis nostrum exercitationem ullam corporis suscipit laboriosam,
nisi ut aliquid ex ea commodi consequatur?
Quis autem vel eum iure reprehenderit,
qui in ea voluptate velit esse,
quam nihil molestiae consequatur,
vel illum,
qui dolorem eum fugiat,
quo voluptas nulla pariatur?
At vero eos et accusamus et iusto odio dignissimos ducimus,
qui blanditiis praesentium voluptatum deleniti atque corrupti,
quos dolores et quas molestias excepturi sint,
obcaecati cupiditate non provident,
similique sunt in culpa, qui officia deserunt mollitia animi,
id est laborum et dolorum fuga.
Et harum quidem rerum facilis est et expedita distinctio.
Nam libero tempore,
cum soluta nobis est eligendi optio,
cumque nihil impedit,
quo minus id,
quod maxime placeat,
facere possimus,
omnis voluptas assumenda est, omnis dolor repellendus.
Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet,
ut et voluptates repudiandae sint et molestiae non recusandae.
Itaque earum rerum hic tenetur a sapiente delectus,
ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.
</textarea>
</div>
<script>
// Обновляем колонку нумерации строк
HTMLTextAreaElement.prototype.updateBuddy = function() {
// Номер строки с кареткой
var row = this.value.substr(0, this.selectionStart).split(/\r?\n/).length;
// Номер символа под кареткой
var col = this.value.substr(0, this.selectionEnd).split(/\r?\n/).pop().length + 1;
// Высота одной строки
var lineHeight = this.dataset.lineHeight;
// Смещение по вертикали для синхронности со скролл-баром
var marginTop = Math.abs(lineHeight - ((this.scrollTop - (this.offsetHeight - this.clientHeight)) % (lineHeight)));
var i;
var buddy = this.previousSibling;
// Убеждаемся в наличии колонки
if(buddy && buddy.localName == "ol") {
// Сверяемся количеством отображаемых строк
if(this.rows != buddy.children.length) {
buddy.replaceChildren();
// Обновляем всю колонку нумерации строк
for(i = 0; i < this.getAttribute("rows"); ++ i)
buddy.appendChild(document.createElement("li"));
}
// Резервируем в текстовом поле область под "товарищескую" колонку с нумерацией строк
this.style.paddingLeft = buddy.offsetWidth + "px";
// Вычисляем номер самой верхней отображаемой строки текстового поля
//buddy.start = 1 + Math.floor(this.scrollTop / (this.scrollHeight / this.value.split(/\r?\n/).length));
buddy.start = 1 + this.scrollTop / this.dataset.lineHeight;
buddy.style.marginTop = marginTop;
}
// Дальше следует чуток лишнего отладочного кода
var status = {
Row :row,
Column :col,
margin :marginTop,
lineHeight :lineHeight,
scrollTop :this.scrollTop
}
this.parentElement.querySelector("footer").innerHTML = Object.keys(status).map(key => `${key}:${status[key]}`).join("<br>");
}
</script>
<script>
// Добавляем колонку нумерации строк
HTMLTextAreaElement.prototype.addBuddy = function() {
var buddy = document.createElement("ol");
var header = document.createElement("header");
var footer = document.createElement("footer");
var rows = this.rows;
// Узнаём точную высоту одной строчки в текстовом поле
this.rows = 1;
buddy.style.position = "fixed";
// Приводим нумератор строк к общему стилю
buddy.style.fontFamily = document.defaultView.getComputedStyle(this, null).getPropertyValue("font-family");
buddy.style.marginTop = document.defaultView.getComputedStyle(this, null).getPropertyValue("border-top");
// Запоминаем точное значение высоты одной строки
// \/ - это не даёт желаемого результата - \/
this.dataset.lineHeight = this.clientHeight;
// \/ - это тоже не даёт желаемого результата - \/
this.dataset.lineHeight = parseFloat(document.defaultView.getComputedStyle(t his, null).getPropertyValue("font-size"));
// Восстанавливаем предустановленное значение отображаемых строк
this.rows = rows;
this.style.display = "inline-block";
// Вставляем "товарища" перед основным элементом
this.parentElement.insertBefore(buddy, this);
// Вставляем заголовок над основным элементом
this.parentElement.insertBefore(header, buddy);
header.textContent = this.title;
// Вставляем строку статуса под основным элементом
this.parentElement.appendChild(footer);
// Первая инициация "товарища"
buddy.setAttribute("name", "buddy");
this.updateBuddy();
this.addEventListener("mouseup",
function(evt) {
var src = evt.srcElement;
var lineHeight = src.scrollHeight / src.value.split(/\r?\n/).length;
var rows = Math.floor(src.offsetHeight / lineHeight);
// Проверяем на изменение размера мышкой
if(src.rows != rows) {
src.rows = rows;
src.updateBuddy();
}
}
);
this.addEventListener("keydown",
function(evt) {
var src = evt.srcElement;
src.updateBuddy();
}
);
this.addEventListener("scroll",
function(evt) {
var src = evt.srcElement;
src.updateBuddy();
}
);
}
</script>
<script>
document.querySelector("textarea").addBuddy();
</script>
</body>Потому, прошу помощи в этом математическом лабиринте свойств.:lol:
То, что не нумеруется первая строка - это бага или фича?
По любому строка 129 должна быть так
buddy.style.marginTop = document.defaultView.getComputedStyle(this, null).getPropertyValue("border-top-width");
(Хотя проще
buddy.style.marginTop = getComputedStyle(this, null).borderTopWidth;
)
а строка 104
buddy.style.marginTop = marginTop + 'px';
(а нужна ли она вообще?)
По поводу стр 129.
Там не только в отступе (marginTop) borderTopWidth надо учитывать, но и paddingTop.
как то так должно быть
const {borderTopWidth, paddingTop} = getComputedStyle(this, null)
buddy.style.marginTop = (parseFloat(borderTopWidth) + parseFloat(paddingTop)) + 'px'
Alikberov
05.09.2022, 12:42
То, что не нумеруется первая строка - это бага или фича?Вoт именно, что баг!:lol: По любому строка 129 должна быть так
а строка 104
(а нужна ли она вообще?)Ничего не помогает!:lol:
На самом деле на Си в WinAPI я тоже как-то неделю боролся с подобной задачей (кажется, решил).
А в HTML/CSS никак не получается.
Вот тут в редакторе (https://gistpreview.github.io/?9b26f6e254c372b9b70b93e6e3221805) я просто (временно) пристыковал два TextArea к друг друг - выглядит вполне нормально и сколлер нормально отрабатывается (хотя и с задержкой).
И вот решил это «временное» переписать нормально…
Но, никак не получается!
Всё должно выглядить как в стандартном редакторе. Без фич и исключений.
А то у меня сейчас и последняя строка текста слева индексируется как #49, хотя внизу же указывается «Row:46»: На три единицы ошибка…:-?
<html>
<head>
<title>Построчная нумерация</title>
<style>
div
{
position :absolute;
width :auto;
height :auto;
}
header
{
background-color:blue;
color :yellow;
}
footer
{
background-color:silver;
color :black;
border :medium silver sunken;
}
</style>
</head>
<body>
<div>
<textarea rows=15 cols=105 spellcheck=false title='Текстовое поле для редактирования'>
Sed ut perspiciatis,
unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam eaque ipsa,
quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt,
explicabo.
Nemo enim ipsam voluptatem,
quia voluptas sit,
aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos,
qui ratione voluptatem sequi nesciunt,
neque porro quisquam est,
qui dolorem ipsum,
quia dolor sit,
amet,
consectetur,
adipisci velit,
sed quia non numquam eius modi tempora incidunt,
ut labore et dolore magnam aliquam quaerat voluptatem.
Ut enim ad minima veniam,
quis nostrum exercitationem ullam corporis suscipit laboriosam,
nisi ut aliquid ex ea commodi consequatur?
Quis autem vel eum iure reprehenderit,
qui in ea voluptate velit esse,
quam nihil molestiae consequatur,
vel illum,
qui dolorem eum fugiat,
quo voluptas nulla pariatur?
At vero eos et accusamus et iusto odio dignissimos ducimus,
qui blanditiis praesentium voluptatum deleniti atque corrupti,
quos dolores et quas molestias excepturi sint,
obcaecati cupiditate non provident,
similique sunt in culpa, qui officia deserunt mollitia animi,
id est laborum et dolorum fuga.
Et harum quidem rerum facilis est et expedita distinctio.
Nam libero tempore,
cum soluta nobis est eligendi optio,
cumque nihil impedit,
quo minus id,
quod maxime placeat,
facere possimus,
omnis voluptas assumenda est, omnis dolor repellendus.
Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet,
ut et voluptates repudiandae sint et molestiae non recusandae.
Itaque earum rerum hic tenetur a sapiente delectus,
ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.
</textarea>
</div>
<script>
// Обновляем колонку нумерации строк
HTMLTextAreaElement.prototype.updateBuddy = function() {
// Номер строки с кареткой
var row = this.value.substr(0, this.selectionStart).split(/\r?\n/).length;
// Номер символа под кареткой
var col = this.value.substr(0, this.selectionEnd).split(/\r?\n/).pop().length + 1;
// Высота одной строки
var lineHeight = this.dataset.lineHeight;
// Смещение по вертикали для синхронности со скролл-баром
var marginTop = Math.abs(lineHeight - ((this.scrollTop - (this.offsetHeight - this.clientHeight)) % (lineHeight)));
var i;
var buddy = this.previousSibling;
// Убеждаемся в наличии колонки
if(buddy && buddy.localName == "ol") {
// Сверяемся количеством отображаемых строк
if(this.rows != buddy.children.length) {
buddy.replaceChildren();
// Обновляем всю колонку нумерации строк
for(i = 0; i < this.getAttribute("rows"); ++ i)
buddy.appendChild(document.createElement("li"));
}
// Резервируем в текстовом поле область под "товарищескую" колонку с нумерацией строк
this.style.paddingLeft = buddy.offsetWidth + "px";
// Вычисляем номер самой верхней отображаемой строки текстового поля
//buddy.start = 1 + Math.floor(this.scrollTop / (this.scrollHeight / this.value.split(/\r?\n/).length));
buddy.start = 1 + this.scrollTop / this.dataset.lineHeight;
//buddy.style.marginTop = marginTop + 'px';
}
// Дальше следует чуток лишнего отладочного кода
var status = {
Row :row,
Column :col,
margin :marginTop,
lineHeight :lineHeight,
scrollTop :this.scrollTop
}
this.parentElement.querySelector("footer").innerHTML = Object.keys(status).map(key => `${key}:${status[key]}`).join("<br>");
}
</script>
<script>
// Добавляем колонку нумерации строк
HTMLTextAreaElement.prototype.addBuddy = function() {
var buddy = document.createElement("ol");
var header = document.createElement("header");
var footer = document.createElement("footer");
var rows = this.rows;
// Узнаём точную высоту одной строчки в текстовом поле
this.rows = 1;
buddy.style.position = "absolute";
// Приводим нумератор строк к общему стилю
buddy.style.fontFamily = document.defaultView.getComputedStyle(this, null).getPropertyValue("font-family");
const mt = getComputedStyle(this, null).borderTopWidth;
const pb = getComputedStyle(this, null).paddingBottom;
//document.defaultView.getComputedStyle(this, null).getPropertyValue("border-top-width");
buddy.style.marginTop = (parseFloat(mt) + parseFloat(pb)) +'px';
// Запоминаем точное значение высоты одной строки
// \/ - это не даёт желаемого результата - \/
this.dataset.lineHeight = this.clientHeight;
// \/ - это тоже не даёт желаемого результата - \/
this.dataset.lineHeight = parseFloat(document.defaultView.getComputedStyle(t his, null).getPropertyValue("font-size"));
// Восстанавливаем предустановленное значение отображаемых строк
this.rows = rows;
this.style.display = "inline-block";
// Вставляем "товарища" перед основным элементом
this.parentElement.insertBefore(buddy, this);
// Вставляем заголовок над основным элементом
this.parentElement.insertBefore(header, buddy);
header.textContent = this.title;
// Вставляем строку статуса под основным элементом
this.parentElement.appendChild(footer);
// Первая инициация "товарища"
buddy.setAttribute("name", "buddy");
this.updateBuddy();
this.addEventListener("mouseup",
function(evt) {
var src = evt.srcElement;
var lineHeight = src.scrollHeight / src.value.split(/\r?\n/).length;
var rows = Math.floor(src.offsetHeight / lineHeight);
// Проверяем на изменение размера мышкой
if(src.rows != rows) {
src.rows = rows;
src.updateBuddy();
}
}
);
this.addEventListener("keydown",
function(evt) {
var src = evt.srcElement;
src.updateBuddy();
}
);
this.addEventListener("scroll",
function(evt) {
var src = evt.srcElement;
src.updateBuddy();
}
);
}
</script>
<script>
document.querySelector("textarea").addBuddy();
</script>
</body>
Вроде так лучше.
Что не так?
Alikberov
05.09.2022, 17:00
Идеально должно быть так:<html>
<head>
<title>Построчная нумерация</title>
<style>
div
{
position :absolute;
width :auto;
height :auto;
}
header
{
background-color:blue;
color :yellow;
}
footer
{
background-color:silver;
color :black;
border :medium silver sunken;
}
</style>
</head>
<body>
<div>
<textarea rows=15 cols=90 spellcheck=false title='Текстовое поле для редактирования'>
Sed ut perspiciatis,
unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam eaque ipsa,
quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt,
explicabo.
Nemo enim ipsam voluptatem,
quia voluptas sit,
aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos,
qui ratione voluptatem sequi nesciunt,
neque porro quisquam est,
qui dolorem ipsum,
quia dolor sit,
amet,
consectetur,
adipisci velit,
sed quia non numquam eius modi tempora incidunt,
ut labore et dolore magnam aliquam quaerat voluptatem.
Ut enim ad minima veniam,
quis nostrum exercitationem ullam corporis suscipit laboriosam,
nisi ut aliquid ex ea commodi consequatur?
Quis autem vel eum iure reprehenderit,
qui in ea voluptate velit esse,
quam nihil molestiae consequatur,
vel illum,
qui dolorem eum fugiat,
quo voluptas nulla pariatur?
At vero eos et accusamus et iusto odio dignissimos ducimus,
qui blanditiis praesentium voluptatum deleniti atque corrupti,
quos dolores et quas molestias excepturi sint,
obcaecati cupiditate non provident,
similique sunt in culpa, qui officia deserunt mollitia animi,
id est laborum et dolorum fuga.
Et harum quidem rerum facilis est et expedita distinctio.
Nam libero tempore,
cum soluta nobis est eligendi optio,
cumque nihil impedit,
quo minus id,
quod maxime placeat,
facere possimus,
omnis voluptas assumenda est, omnis dolor repellendus.
Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet,
ut et voluptates repudiandae sint et molestiae non recusandae.
Itaque earum rerum hic tenetur a sapiente delectus,
ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores
repellat.
</textarea>
</div>
<script>
// Обновляем колонку нумерации строк
HTMLTextAreaElement.prototype.updateBuddy = function() {
// Номер строки с кареткой
var row = this.value.substr(0, this.selectionStart).split(/\r?\n/).length;
// Номер символа под кареткой
var col = this.value.substr(0, this.selectionEnd).split(/\r?\n/).pop().length + 1;
var i = 0;
var buddy = this.previousSibling;
// Убеждаемся в наличии колонки
if(buddy && buddy.localName == "textarea") {
// Сверяемся количеством отображаемых строк
if(this.rows != buddy.rows || this.scrollHeight != buddy.scrollHeight || this.offsetHeight != buddy.offsetHeight) {
buddy.rows = this.rows;
buddy.cols = this.value.split(/\r?\n/).length.toString().length;
// Обновляем всю колонку нумерации строк
buddy.value = this.value.replace(/^.*$/gm, () => ++ i);
// Выравниваем поле слева под нумерацию строк
this.style.paddingLeft = buddy.offsetWidth;
buddy.style.height = this.offsetHeight + "px";
}
buddy.scrollTop = this.scrollTop;
}
// Дальше следует информирование о позиции каретки
var status = {
Row :row,
Column :col
}
this.parentElement.querySelector("footer").innerHTML = Object.keys(status).map(key => `${key}:${status[key]}`).join("|");
}
</script>
<script>
// Добавляем колонку нумерации строк
HTMLTextAreaElement.prototype.addBuddy = function() {
var buddy = document.createElement("textarea");
var header = document.createElement("header");
var footer = document.createElement("footer");
buddy.style.position = "fixed";
buddy.style.overflow = "hidden";
buddy.style.textAlign = "right";
buddy.style.resize = "none";
buddy.disabled = true;
this.style.display = "inline-block";
// Вставляем "товарища" перед основным элементом
this.parentElement.insertBefore(buddy, this);
// Вставляем заголовок над основным элементом
this.parentElement.insertBefore(header, buddy);
header.textContent = this.title;
// Вставляем строку статуса под основным элементом
this.parentElement.appendChild(footer);
// Первая инициация "товарища"
buddy.setAttribute("name", "buddy");
this.updateBuddy();
this.addEventListener("mousemove",
function(evt) {
var src = evt.srcElement;
src.updateBuddy();
}
);
this.addEventListener("keydown",
function(evt) {
var src = evt.srcElement;
src.updateBuddy();
}
);
this.addEventListener("keyup",
function(evt) {
var src = evt.srcElement;
src.updateBuddy();
}
);
this.addEventListener("scroll",
function(evt) {
var src = evt.srcElement;
src.updateBuddy();
}
);
}
</script>
<script>
document.querySelector("textarea").addBuddy();
</script>
</body>
У вас всё дрожит дёргается и скачет. Возьми любой готовый редактор на textarea и посмотри как там.
Alikberov
05.09.2022, 19:30
У вас всё дрожит дёргается и скачет. Возьми любой готовый редактор на textarea и посмотри как там.Готовoго решения я как-то не нашёл.
Adding Line Numbers To HTML Textarea (https://stackoverflow.com/questions/1995370/adding-line-numbers-to-html-textarea)
Почему вообще за четверть века не ввели в спецификацию стиля с флагом отображения этих номеров?
Всюду нужно подключать сторонние библиотеки с кучей кода…
P.S.: Несчастный Math.sign (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign) появился совсем недавно.
И что мешало его давно ввести?
Несчастный Math.sign появился совсем недавно.
И что мешало его давно ввести?
Ну вроде в 2013-2014г.
А может он просто не нужен особо был?
Вообще понятие строки довольно размытое.
Что является строкой - то, что выглядит как строка, или это текст от \n до \n?
Во всех редакторах, которые показывают номера строк - это второе. У вас - первое.
Если очень длинная строка занимает в визуальном узком окне несколько строк, то номер имеет только первая часть строки, а те, что перенесены идут без своих номеров, т.к это часть той же строки. А следующий номер имеет следующая строка, которая идет после \n.
А какой смысл в ваших номерах, если эти номера зависят не от реального количества строк, а от ширины окна? На такой номер нельзя сослаться.
Alikberov
05.09.2022, 20:25
Ну вроде в 2013-2014г.Относительнo недавно. Гораздо позднее, чем появились контексты Canvas, Audio, Video.
А может он просто не нужен особо был?Ну, в Бейсике оно было всегда. А в JS иногда бывали моменты, где с этим было бы проще.
Вообще понятие строки довольно размытое.
Что является строкой - то, что выглядит как строка, или это текст от \n до \n?
Во всех редакторах, которые показывают номера строк - это второе.Для активной разработки - очень важны.
Если очень длинная строка занимает в визуальном узком окне несколько строк, то номер имеет только первая часть строки, а те, что перенесены идут без своих номеров, т.к это часть той же строки. А следующий номер имеет следующая строка, которая идет после \n.
А какой смысл в ваших номерах, если эти номера зависят не от реального количества строк, а от ширины окна? На такой номер нельзя сослаться.Вот это - большая проблема. И каждый решает её сам, так как до сих пор стандартного алгоритма (встроенного) не предоставили.
Ну это немного разные понятия - функция в языке, и какой то новый стиль в элементе HTML.
Js к стилям и html имеет очень маленькое отношение (точнее никакого).
Js он и в браузере и в Node один и тот же. А API разные.
Alikberov
05.09.2022, 21:24
Ну это немного разные понятия - функция в языке, и какой то новый стиль в элементе HTML.
Js к стилям и html имеет очень маленькое отношение (точнее никакого).
Js он и в браузере и в Node один и тот же. А API разные.Согласeн, понятия разные и аналогия не совсем корректная.
Просто, хотел обратить внимание на масштаб изменений.
Описание функции Math.sign - не более сотни байтов определённого машинного кода.
Описание тегов canvas/audio/video - больше сотни различных библиотек.
Вопрос лишь в формальностях, что развивать…:yes:
В том же jsfiddle код подсветки синтаксиса и строк - значимый процент на фоне общего кода.
И уважаемый себя ресурс с интерактивной демонстрацией примеров кода всегда предоставляет нумерацию к строкам листинга.
P.S.: Так или иначе, рано или поздно, всё равно поддержку введут на базе известной библиотеки.
Вопрос вот только когда и на какой библиотеке?:)
Alikberov
06.09.2022, 23:03
У вас всё дрожит дёргается и скачет.Именнo на #6 (https://javascript.ru/forum/547792-post6.html)?
Можно поинтересоваться, в каком браузере это наблюдается и как можно побороть?:thanks:
Alikberov, левый нижний угол: http://ajitae.ru/11.gif
Alikberov
07.09.2022, 12:52
Alikberov, левый нижний угол:Дa, в FF такая проблема наблюдается (в Chrome её практически не видно, если на видео не записать).
А можно ли как-то побороть?
Ну, preventDefault поставить, например, со своим кодом скроллинга (что усложняет задачу).
P.S.: Спасибо за анимацию!:thanks:
Alikberov,
Попробуйте вставлять длинные строки (длиннее ширины области), что бы они переносились на 2-3 строки.
Там все еще хуже будет.
Alikberov
07.09.2022, 18:00
Попробуйте вставлять длинные строки (длиннее ширины области), что бы они переносились на 2-3 строки.
Там все еще хуже будет.Эту ситуацию я пока в расчёт совсем не брал, так как там просто не известно, как в двух строчках всё вычислить…
Здесь нужно подключать готовую библиотеку или писать своё.
Думал, что как-то можно реализовать такую простую элементарную функцию одним блоком кода.
Готовoго решения я как-то не нашёл.
jQuery-вский (https://www.codehim.com/text-input/html-textarea-with-line-numbers-jquery-lined-textarea/) вроде норм.
jQuery-вский вроде норм.
Там даже на картинке видно, что номера и строки не совпадают. Номера как бы между строк получаются.
Alikberov
08.09.2022, 16:08
Alikberov,
Попробуйте вставлять длинные строки (длиннее ширины области), что бы они переносились на 2-3 строки.
Там все еще хуже будет.Кажeтся, я нашёл промежуточный костыльный вариант.
Вроде бы работает, но крайне медленный.<html>
<head>
<title>Построчная нумерация</title>
<style>
div
{
position :absolute;
width :auto;
height :auto;
}
header
{
background-color:blue;
color :yellow;
}
footer
{
background-color:silver;
color :black;
border :medium silver sunken;
}
</style>
</head>
<body>
<div>
<textarea rows=15 cols=40 spellcheck=false title='Текстовое поле для редактирования'>
Sed ut perspiciatis,
unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam eaque ipsa,
quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt,
explicabo.
Nemo enim ipsam voluptatem,
quia voluptas sit,
aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos,
qui ratione voluptatem sequi nesciunt,
neque porro quisquam est,
qui dolorem ipsum,
quia dolor sit,
amet,
consectetur,
adipisci velit,
sed quia non numquam eius modi tempora incidunt,
ut labore et dolore magnam aliquam quaerat voluptatem.
Ut enim ad minima veniam,
quis nostrum exercitationem ullam corporis suscipit laboriosam,
nisi ut aliquid ex ea commodi consequatur?
Quis autem vel eum iure reprehenderit,
qui in ea voluptate velit esse,
quam nihil molestiae consequatur,
vel illum,
qui dolorem eum fugiat,
quo voluptas nulla pariatur?
At vero eos et accusamus et iusto odio dignissimos ducimus,
qui blanditiis praesentium voluptatum deleniti atque corrupti,
quos dolores et quas molestias excepturi sint,
obcaecati cupiditate non provident,
similique sunt in culpa, qui officia deserunt mollitia animi,
id est laborum et dolorum fuga.
Et harum quidem rerum facilis est et expedita distinctio.
Nam libero tempore,
cum soluta nobis est eligendi optio,
cumque nihil impedit,
quo minus id,
quod maxime placeat,
facere possimus,
omnis voluptas assumenda est,
omnis dolor repellendus.
Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet,
ut et voluptates repudiandae sint et molestiae non recusandae.
Itaque earum rerum hic tenetur a sapiente delectus,
ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores
repellat.
</textarea>
</div>
<script>
// Обновляем колонку нумерации строк
HTMLTextAreaElement.prototype.updateBuddy = function() {
// Номер строки с кареткой
var row = this.value.substr(0, this.selectionStart).split(/\r?\n/).length;
// Номер символа под кареткой
var col = this.value.substr(0, this.selectionEnd).split(/\r?\n/).pop().length + 1;
// ищем своего "товарища"
var buddy = this.parentElement.querySelector("[name='buddy']") || this.previousSibling;
// Информирование о позиции каретки
var status = {
Row :row,
Column :col
}
// Убеждаемся в наличии колонки
if(buddy && buddy.localName == "textarea") {
buddy.rows = this.rows;
buddy.style.height = this.offsetHeight + "px";
buddy.scrollTop = this.scrollTop;
// Сверяемся количеством отображаемых строк
if(this.dataset.value != this.value || this.scrollHeight != buddy.scrollHeight) {
var i = 1;
// Разбиваем текст на отдельные строки
var text = this.value.split(/\r?\n/);
var lines = [];
// Создаём текстовое поле для построчной проверки признака переноса строки
var dummy = document.createElement("textarea");
// Делаем его невидимым
dummy.style.position='absolute';
dummy.style.top='800px';
dummy.style.visibility='hidden';
// Вставляем в документ, иначе высота прокрутки будет нулевой
document.body.appendChild(dummy);
dummy.rows = 1;
dummy.cols = this.cols;
dummy.style.width = this.offsetWidth + "px";
// Выравниваем поле слева под нумерацию строк
var lineHeight = dummy.scrollHeight;
buddy.cols = text.length.toString().length;
this.style.paddingLeft = buddy.offsetWidth + "px";
dummy.style.paddingLeft = buddy.offsetWidth + "px";
status.lineHeight = lineHeight;
status.scrollHeight = dummy.scrollHeight;
// Прочёсываем все строки
for(line of text) {
lines.push(i);
dummy.value = line;
// Не перенеслась ли строчка?
if(dummy.scrollHeight > lineHeight) {
var separating = Math.floor(dummy.scrollHeight / (lineHeight - 0));
// Разбиваем нумерацию в этом месте
for(j = 0; j < separating; ++ j)
lines.push("");
}
i ++;
}
// Обновляем всю колонку нумерации строк
buddy.value = lines.join("\r\n");
buddy.scrollTop = this.scrollTop;
// Удаляем поле коррекции
document.body.removeChild(dummy);
this.dataset.value = this.value;
}
}
this.parentElement.querySelector("footer").innerHTML = Object.keys(status).map(key => `${key}:${status[key]}`).join("|");
}
</script>
<script>
// Добавляем колонку нумерации строк
HTMLTextAreaElement.prototype.addBuddy = function() {
var buddy = document.createElement("textarea"); // "Товарищеская" колонка строк
var header = document.createElement("header"); // Заголовок текстового поля
var footer = document.createElement("footer"); // Статус текстового поля
// Наш "товарищ" должен располагаться слева от текста
buddy.style.position = "fixed";
buddy.style.overflow = "hidden";
buddy.style.textAlign = "right";
buddy.style.resize = "none";
buddy.disabled = true;
this.style.display = "inline-block";
// Вставляем "товарища" перед основным элементом
this.parentElement.insertBefore(buddy, this);
// Вставляем заголовок над основным элементом
this.parentElement.insertBefore(header, buddy);
header.textContent = this.title;
// Вставляем строку статуса под основным элементом
this.parentElement.appendChild(footer);
// Первая инициация "товарища"
buddy.setAttribute("name", "buddy");
this.dataset.value = this.value;
this.updateBuddy();
// Назначаем события для оперативной синхронизации "товарища"
this.addEventListener("mousemove",
function(evt) {
evt.srcElement.updateBuddy();
}
);
this.addEventListener("keydown",
function(evt) {
evt.srcElement.updateBuddy();
}
);
this.addEventListener("keyup",
function(evt) {
evt.srcElement.updateBuddy();
}
);
this.addEventListener("scroll",
function(evt) {
evt.srcElement.updateBuddy();
}
);
}
</script>
<script>
document.querySelector("textarea").addBuddy();
</script>
</body>Эрзац собственной разработки хоть более-менее работает, но глючит местами довольно конкретно.:blink:
(Если не менять размера поля, то скроллинг до последней строки работает почти нормально, так как невыровненный размер по вертикали сильно влияет на расчёты.)
Alikberov
09.09.2022, 19:00
В общем, пришлось повозиться со стилями и устранить ненужные помехи в расчётах высоты текста.
Судя по значению «Ratio» в статусе, теперь высота прокрутки текста совпадает с высотой прокрутки нумерации, что было проблемой и вызывало расхождения на последней строке…
Сейчас учитывается ширина скролл-бара при построчном анализе высоты текста и сбоев не наблюдалось.<html>
<head>
<title>Построчная нумерация</title>
<style>
div
{
position :absolute;
width :auto;
height :auto;
}
header
{
background-color:blue;
color :yellow;
}
footer
{
background-color:silver;
color :black;
border :medium silver sunken;
}
</style>
</head>
<body>
<div>
<textarea rows=15 cols=40 spellcheck=false title='Текстовое поле для редактирования'>
Sed ut perspiciatis,
unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam eaque ipsa,
quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt,
explicabo.
Nemo enim ipsam voluptatem,
quia voluptas sit,
aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos,
qui ratione voluptatem sequi nesciunt,
neque porro quisquam est,
qui dolorem ipsum,
quia dolor sit,
amet,
consectetur,
adipisci velit,
sed quia non numquam eius modi tempora incidunt,
ut labore et dolore magnam aliquam quaerat voluptatem.
Ut enim ad minima veniam,
quis nostrum exercitationem ullam corporis suscipit laboriosam,
nisi ut aliquid ex ea commodi consequatur?
Quis autem vel eum iure reprehenderit,
qui in ea voluptate velit esse,
quam nihil molestiae consequatur,
vel illum,
qui dolorem eum fugiat,
quo voluptas nulla pariatur?
At vero eos et accusamus et iusto odio dignissimos ducimus,
qui blanditiis praesentium voluptatum deleniti atque corrupti,
quos dolores et quas molestias excepturi sint,
obcaecati cupiditate non provident,
similique sunt in culpa, qui officia deserunt mollitia animi,
id est laborum et dolorum fuga.
Et harum quidem rerum facilis est et expedita distinctio.
Nam libero tempore,
cum soluta nobis est eligendi optio,
cumque nihil impedit,
quo minus id,
quod maxime placeat,
facere possimus,
omnis voluptas assumenda est,
omnis dolor repellendus.
Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet,
ut et voluptates repudiandae sint et molestiae non recusandae.
Itaque earum rerum hic tenetur a sapiente delectus,
ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores
repellat.
</textarea>
</div>
<script>
HTMLTextAreaElement.prototype.getLineBreaks = function(text) {
const cleaner = "0px 0px 0px 0px";
// Без "новой строки" бывает сбой,
// так как вся строка умещается на одной линии и не учитывается ширина скролл-бара
this.value = text + "\r\n";
this.rows = 1;
this.style.paddingTop = "0px";
this.style.marginTop = "0px";
this.style.borderTop = "none";
this.style.paddingBottom = "0px";
this.style.marginBottom = "0px";
this.style.borderBottom = "none";
return this.scrollHeight / this.offsetHeight - 1;
}
</script>
<script>
// Обновляем колонку нумерации строк
HTMLTextAreaElement.prototype.updateBuddy = function() {
// Номер строки с кареткой
var row = this.value.substr(0, this.selectionStart).split(/\r?\n/).length;
// Номер символа под кареткой
var col = this.value.substr(0, this.selectionEnd).split(/\r?\n/).pop().length + 1;
// ищем своего "товарища"
var buddy = this.parentElement.querySelector("[name='buddy']") || this.previousSibling;
// Информирование о позиции каретки
var status = {
Row :row,
Column :col
}
// Убеждаемся в наличии колонки
if(buddy && buddy.localName == "textarea") {
buddy.rows = this.rows;
buddy.style.height = this.offsetHeight + "px";
buddy.scrollTop = this.scrollTop;
// Сверяемся количеством отображаемых строк
if(this.dataset.value != this.value || this.scrollHeight != buddy.scrollHeight) {
var i = 1;
// Разбиваем текст на отдельные строки
var text = this.value.split(/\r?\n/);
var lines = [];
// Выравниваем поле слева под нумерацию строк
buddy.cols = text.length.toString().length;
this.style.paddingLeft = buddy.offsetWidth + "px";
// Создаём текстовое поле для построчной проверки признака переноса строки
var dummy = document.createElement("textarea");
// Настраиваем его на отображение одной строки
dummy.rows = 1;
dummy.cols = this.cols;
dummy.style.width = this.offsetWidth + "px";
dummy.style.paddingLeft = buddy.offsetWidth + "px";
dummy.style.position = "absolute";
dummy.style.top = "-800px";
// Делаем его невидимым
dummy.style.visibility='hidden';
// Вставляем в документ, иначе высота прокрутки будет нулевой
document.body.appendChild(dummy);
// Прочёсываем все строки
for(line of text) {
lines.push(i);
dummy.value = line;
// Не перенеслась ли строчка?
var separating = dummy.getLineBreaks(line);
// Разбиваем нумерацию в этом месте
for(j = 1; j < separating; ++ j)
lines.push("");
i ++;
}
// Обновляем всю колонку нумерации строк
buddy.value = lines.join("\r\n");
buddy.scrollTop = this.scrollTop;
// Удаляем поле коррекции
document.body.removeChild(dummy);
this.dataset.value = this.value;
}
}
status.Ratio = `${this.scrollHeight}/${buddy.scrollHeight}`;
this.parentElement.querySelector("footer").innerHTML = Object.keys(status).map(key => `${key}:${status[key]}`).join("|");
}
</script>
<script>
// Добавляем колонку нумерации строк
HTMLTextAreaElement.prototype.addBuddy = function() {
var buddy = document.createElement("textarea"); // "Товарищеская" колонка строк
var header = document.createElement("header"); // Заголовок текстового поля
var footer = document.createElement("footer"); // Статус текстового поля
// Наш "товарищ" должен располагаться слева от текста
buddy.style.position = "fixed";
buddy.style.overflow = "hidden";
buddy.style.textAlign = "right";
buddy.style.resize = "none";
buddy.disabled = true;
this.style.display = "inline-block";
// Вставляем "товарища" перед основным элементом
this.parentElement.insertBefore(buddy, this);
// Вставляем заголовок над основным элементом
this.parentElement.insertBefore(header, buddy);
header.textContent = this.title;
// Вставляем строку статуса под основным элементом
this.parentElement.appendChild(footer);
// Первая инициация "товарища"
buddy.setAttribute("name", "buddy");
this.dataset.value = this.value;
this.updateBuddy();
// Назначаем события для оперативной синхронизации "товарища"
this.addEventListener("mousemove",
function(evt) {
// При изменении размера текстового поля
evt.srcElement.updateBuddy();
}
);
this.addEventListener("keydown",
function(evt) {
evt.srcElement.updateBuddy();
}
);
this.addEventListener("keyup",
function(evt) {
evt.srcElement.updateBuddy();
}
);
this.addEventListener("scroll",
function(evt) {
// При прокрутке
evt.srcElement.updateBuddy();
}
);
}
</script>
<script>
document.querySelector("textarea").addBuddy();
</script>
</body>P.S: Буду рад выслушать замечания о выявленных сбоях в этом скрипте класса «дёшего и сердито»…:thanks:
Alikberov,
скролл над нумерацией отсутствует, это так и задумано?
Alikberov
09.09.2022, 22:22
Предыдущий вариант не дружит с FireFox и пришлось повозиться, так как всё было ужасно!
(Оставлю вариант выше для сравнения…)
Этот вариант корректно работает и в FF:<html>
<head>
<title>Построчная нумерация</title>
<style>
div
{
position :absolute;
width :auto;
height :auto;
}
header
{
background-color:blue;
color :yellow;
}
footer
{
background-color:silver;
color :black;
border :medium silver sunken;
}
</style>
</head>
<body>
<div>
<textarea rows=15 cols=40 spellcheck=false title='Текстовое поле для редактирования'>
Sed ut perspiciatis,
unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam eaque ipsa,
quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt,
explicabo.
Nemo enim ipsam voluptatem,
quia voluptas sit,
aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos,
qui ratione voluptatem sequi nesciunt,
neque porro quisquam est,
qui dolorem ipsum,
quia dolor sit,
amet,
consectetur,
adipisci velit,
sed quia non numquam eius modi tempora incidunt,
ut labore et dolore magnam aliquam quaerat voluptatem.
Ut enim ad minima veniam,
quis nostrum exercitationem ullam corporis suscipit laboriosam,
nisi ut aliquid ex ea commodi consequatur?
Quis autem vel eum iure reprehenderit,
qui in ea voluptate velit esse,
quam nihil molestiae consequatur,
vel illum,
qui dolorem eum fugiat,
quo voluptas nulla pariatur?
At vero eos et accusamus et iusto odio dignissimos ducimus,
qui blanditiis praesentium voluptatum deleniti atque corrupti,
quos dolores et quas molestias excepturi sint,
obcaecati cupiditate non provident,
similique sunt in culpa, qui officia deserunt mollitia animi,
id est laborum et dolorum fuga.
Et harum quidem rerum facilis est et expedita distinctio.
Nam libero tempore,
cum soluta nobis est eligendi optio,
cumque nihil impedit,
quo minus id,
quod maxime placeat,
facere possimus,
omnis voluptas assumenda est,
omnis dolor repellendus.
Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet,
ut et voluptates repudiandae sint et molestiae non recusandae.
Itaque earum rerum hic tenetur a sapiente delectus,
ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores
repellat.
</textarea>
</div>
<script>
// Возвращает, на какое количество подстрок разбилась строка
HTMLTextAreaElement.prototype.getLineBreaks = function(text) {
this.value = text;
// Здесь проводим принудительный ритуал для повышения точности
this.rows = 1;
this.style.paddingTop = "0px";
this.style.marginTop = "0px";
this.style.borderTop = "none";
this.style.borderTopWidth = "0px"; // Обязательно для FF
this.style.paddingBottom = "0px";
this.style.marginBottom = "0px";
this.style.borderBottom = "none";
this.style.borderBottomWidth = "0px"; // Обязательно для FF
// Теперь вычисляем, сколько подстрок получилось
return this.scrollHeight / this.offsetHeight;
}
</script>
<script>
// Обновляем колонку нумерации строк
HTMLTextAreaElement.prototype.updateBuddy = function() {
// Номер строки с кареткой
var row = this.value.substr(0, this.selectionStart).split(/\r?\n/).length;
// Номер символа под кареткой
var col = this.value.substr(0, this.selectionEnd).split(/\r?\n/).pop().length + 1;
// ищем своего "товарища"
var buddy = this.parentElement.querySelector("[name='buddy']") || this.previousSibling;
// Информирование о позиции каретки
var status = {
Row :row,
Column :col
}
// Убеждаемся в наличии колонки
if(buddy && buddy.localName == "textarea") {
buddy.rows = this.rows;
buddy.style.height = this.offsetHeight + "px";
buddy.scrollTop = this.scrollTop;
// Сверяемся количеством отображаемых строк
if(this.dataset.value != this.value || this.scrollHeight != buddy.scrollHeight) {
var i = 1;
// Разбиваем текст на отдельные строки
var text = this.value.split(/\r?\n/);
var lines = [];
// Выравниваем поле слева под нумерацию строк
buddy.cols = text.length.toString().length;
this.style.paddingLeft = buddy.offsetWidth + "px";
// Создаём текстовое поле для построчной проверки признака переноса строки
var dummy = document.createElement("textarea");
// Настраиваем его на отображение одной строки при известном количестве колонок
dummy.rows = 1;
dummy.cols = this.cols;
dummy.style.width = this.offsetWidth + "px";
dummy.style.paddingLeft = buddy.offsetWidth + "px";
dummy.style.position = "absolute";
dummy.style.top = "400px";
// Делаем его невидимым
dummy.style.visibility='hidden';
// Вставляем в документ, иначе высота прокрутки будет нулевой
document.body.appendChild(dummy);
dummy.style.overflowX = "hidden"; // Обязательно для FF
dummy.style.overflowY = "scroll"; // Обязательно для точности
// Прочёсываем все строки
for(line of text) {
lines.push(i);
dummy.value = line;
// Не перенеслась ли строчка?
var separating = dummy.getLineBreaks(line);
// Разбиваем нумерацию в этом месте
for(j = 1; j < separating; ++ j)
lines.push("");
i ++;
}
// Обновляем всю колонку нумерации строк
buddy.value = lines.join("\r\n");
buddy.scrollTop = this.scrollTop;
// Удаляем поле коррекции
document.body.removeChild(dummy);
this.dataset.value = this.value;
}
}
// Добавляем необязательную отладочную информацию
status.Ratio = `${this.scrollHeight}/${buddy.scrollHeight}`;
this.parentElement.querySelector("footer").innerHTML = Object.keys(status).map(key => `${key}:${status[key]}`).join("|");
}
</script>
<script>
// Добавляем колонку нумерации строк
HTMLTextAreaElement.prototype.addBuddy = function() {
var buddy = document.createElement("textarea"); // "Товарищеская" колонка строк
var header = document.createElement("header"); // Заголовок текстового поля
var footer = document.createElement("footer"); // Статус текстового поля
// Наш "товарищ" должен располагаться слева от текста
buddy.style.position = "fixed";
buddy.style.overflow = "hidden";
buddy.style.textAlign = "right";
buddy.style.resize = "none";
buddy.disabled = true;
this.style.display = "inline-block";
// Вставляем "товарища" перед основным элементом
this.parentElement.insertBefore(buddy, this);
// Вставляем заголовок над основным элементом
this.parentElement.insertBefore(header, buddy);
header.textContent = this.title;
// Вставляем строку статуса под основным элементом
this.parentElement.appendChild(footer);
// Первая инициация "товарища"
buddy.setAttribute("name", "buddy");
this.dataset.value = this.value;
this.updateBuddy();
// Назначаем события для оперативной синхронизации "товарища"
this.addEventListener("mousemove",
function(evt) {
// При изменении размера текстового поля
evt.srcElement.updateBuddy();
}
);
this.addEventListener("keydown",
function(evt) {
evt.srcElement.updateBuddy();
}
);
this.addEventListener("keyup",
function(evt) {
evt.srcElement.updateBuddy();
}
);
this.addEventListener("scroll",
function(evt) {
// При прокрутке
evt.srcElement.updateBuddy();
}
);
}
</script>
<script>
document.querySelector("textarea").addBuddy();
</script>
</body>Alikberov,
скролл над нумерацией отсутствует, это так и задумано?Что Вы имеете в виду?
Нумерация строк не прокручивается вместе с текстом?
Что Вы имеете в виду?
курсор на цифры, колесо мышки круть, нефига не круть)))
Alikberov
09.09.2022, 22:54
курсор на цифры, колесо мышки круть, нефига не круть)))A на сколько важна такая функция?:D
Ведь основные усилия я прикладывал к известной задаче (адекватное отображение нумерации без подключения сторонних тяжёлых библиотек).
В DOM столбик нумератора имеет флаг «disabled» и «fixed», а попытки сейчас поправить положение вещей чуть не поломало всё…:agree:
Сейчас скрипт функционирует на принципе «работает - не трожь!» и я уже опасаюсь что-то менять…
Нужно потом в копии скрипта опыты проводить, если потребуется…
А так, в контексте вдруг предложенной задачи #16 (https://javascript.ru/forum/547827-post16.html) вопрос исчерпан.
Нужно только какую-то обвёртку написать, чтобы не вызывать функцию явно, а лишь добавлять к любому TextArea атрибут, например, "numbering".
P.S.: Зашёл на страницу Война и Мир (http://az.lib.ru/t/tolstoj_lew_nikolaewich/text_0073.shtml) и вставил сюда всю страницу целиком…
Chrome: Скрипт задумался на целых 2 минуты, а после выдал 7751 строку без глюка.
FireFox: Скрипт задумался примерно на 50 секунд и выдал 7751 строку.
Alikberov
11.09.2022, 22:00
Идeально было бы использовать фоном element (https://developer.mozilla.org/en-US/docs/Web/CSS/element#examples) нумератора, как я понял. Который, к сожалению, в FF только и работает.
На данный момент я попытался использовать фоновый наблюдатель мутаций:// Create an observer instance linked to the callback function
window.user_observer = new MutationObserver(WatchMutations);
// Options for the observer (which mutations to observe)
const config = { attributes: false, childList: true, subtree: true };
// Start observing the target node for configured mutations
window.user_observer.observe(document, config);
function WatchMutations(mutations, observer) {
for(var mutation of mutations) {
// examine new nodes, is there anything to highlight?
for(var node of mutation.addedNodes) {
// we track only elements, skip other nodes (e.g. text nodes)
if(node instanceof HTMLElement) {
// check the inserted element for being a code snippet
if(node.matches("textarea[data-lines]")) {
node.addBuddy();
}
}
}
}
}Который выполняет свою функцию и сам добавляет нумератор строк к каждому текстовому полю с флагом «data-lines». Не знаю, как сильно это скажется на производительности и просто экспериментирую…
Также, теперь прокрутка мышью работает и над нумерацией, но имеются нюансы, так как пришлось само текстовое поле поставить поверх нумератора, сделав фон главного элемента прозрачным. Из-за чего вся страница позади стала фоном под текстом. А сам нумератор пришлось правымм бордюром растянуть так, чтобы он и стал фоном под текстом. В следствии чего имеются приличные глюки с просветами при ресайзинге мышью.
Да, эти два "товарища" помещены внутрь div вместе с header и footer.
Но, как Вы понимаете, это для визуального акцента на демострацию того, что я хочу. А в идеале должен быть собственно сам textarea без каких-либо сущностей, чтобы засорять вёрстку лишним матрёшиством (чем грешит абсолютное большинство сайтов).
P.S.: Есть ещё вариант с canvas-фоном…
Alikberov
12.09.2022, 02:01
Хотелoсь бы выслушать критику профессионалов по поводу такого варианта:<html>
<head><title>Построчная нумерация</title>
<script title='Пробная заготовка "наблюдателя" для автоматического оформления TextArea'>
window.my_observer = new MutationObserver(WatchMutations);
const config = { attributes: false, childList: true, subtree: true };
window.my_observer.observe(document, config);
function WatchMutations(mutations, observer) {
for(var mutation of mutations) {
for(var node of mutation.addedNodes) {
if(node instanceof HTMLElement) {
if(node.matches("textarea[data-lines]")) {
// Условие, иначе всё повиснет!!!
if(!("value" in node.dataset))
node.addBuddy();
}
}
}
}
}
</script>
<script title='"Запасной" метод вычисления числа разрывов/переноса строки у конкретного TextArea'>
HTMLTextAreaElement.prototype.getLineBreaks = function(text) {
this.value = text;
// Здесь проводим принудительный ритуал для повышения точности
this.rows = 1;
this.style.paddingTop = "0px";
this.style.marginTop = "0px";
this.style.borderTop = "none";
this.style.borderTopWidth = "0px"; // Обязательно для FF
this.style.paddingBottom = "0px";
this.style.marginBottom = "0px";
this.style.borderBottom = "none";
this.style.borderBottomWidth = "0px"; // Для FF
// Сколько подстрок получилось?
return this.scrollHeight / this.offsetHeight;
}
</script>
<script title='Описание метода обновления колонки строк у TextArea'>
// Обновляем колонку нумерации строк
HTMLTextAreaElement.prototype.updateBuddy = function() {
var row = this.value.substr(0, this.selectionStart).split(/\r?\n/).length;
var col = this.value.substr(0, this.selectionEnd).split(/\r?\n/).pop().length + 1;
// ищем своего "товарища"
var buddy = this.parentElement.querySelector("[name='buddy']") || this.previousSibling;
var status = {
Row :row,
Column :col
}
// Убеждаемся в наличии колонки
if(buddy && buddy.localName == "textarea") {
buddy.rows = this.rows;
buddy.style.height = this.offsetHeight + "px";
buddy.scrollTop = this.scrollTop;
// Сверяемся количеством отображаемых строк
if(this.dataset.value != this.value || this.scrollHeight != buddy.scrollHeight) {
var i = 1;
// Разбиваем текст на строки
var text = this.value.split(/\r?\n/);
var lines = [];
// Выравниваем поле слева под нумерацию строк
buddy.cols = text.length.toString().length;
buddy.style.borderRightWidth = "0px";
this.style.paddingLeft = buddy.offsetWidth + "px";
// Создаём текстовое поле для построчной проверки признака переноса строки
var dummy = document.createElement("textarea");
// Настраиваем его на отображение одной строки при известном количестве колонок
dummy.rows = 1;
dummy.cols = this.cols;
dummy.style.width = this.offsetWidth + "px";
dummy.style.paddingLeft = buddy.offsetWidth + "px";
dummy.style.position = "absolute";
dummy.style.top = "400px";
dummy.style.paddingTop = "0px";
dummy.style.marginTop = "0px";
dummy.style.borderTop = "none";
dummy.style.borderTopWidth = "0px"; // Обязательно для FF
dummy.style.paddingBottom = "0px";
dummy.style.marginBottom = "0px";
dummy.style.borderBottom = "none";
dummy.style.borderBottomWidth = "0px"; // Обязательно для FF
// Делаем его невидимым
dummy.style.visibility='hidden';
// Вставляем в документ, иначе высота прокрутки будет нулевой
document.body.appendChild(dummy);
dummy.style.overflowX = "hidden"; // Обязательно для FF
dummy.style.overflowY = "scroll"; // Обязательно для точности
//buddy.style.borderRightWidth = (this.offsetWidth - buddy.offsetWidth) + "px";
this.style.background = `linear-gradient(90deg, rgba(0,0,0,0) ${buddy.clientWidth - 1}px, white ${buddy.clientWidth}px, white 100%)`;
// Прочёсываем все строки
console.time("Calculating line-breaks");
var fromTime = window.performance.now();
for(line of text) {
lines.push(i);
dummy.value = line;
// Не перенеслась ли строчка?
var separating = dummy.scrollHeight / dummy.offsetHeight;
// Разбиваем нумерацию в этом месте
for(j = 1; j < separating; ++ j)
lines.push("");
i ++;
}
console.timeEnd("Calculating line-breaks");
var lastTime = window.performance.now();
this.parentElement.querySelector("footer").title = `Last performance: ${(lastTime - fromTime).toPrecision(10)}ms`;
status["Scan in"] = `${((lastTime - fromTime) / 1000).toPrecision(5)} secs`;
// Обновляем всю колонку нумерации строк
buddy.value = lines.join("\r\n");
buddy.scrollTop = this.scrollTop;
// Удаляем поле коррекции
document.body.removeChild(dummy);
this.dataset.value = this.value;
}
}
// Добавляем необязательную отладочную информацию
status.Ratio = `${this.scrollHeight}/${buddy.scrollHeight}`;
this.parentElement.querySelector("footer").innerHTML = Object.keys(status).map(key => `${key}:${status[key]}`).join("|");
}
</script>
<script title='Описание метода добавления колонки нумерации строк у TextArea'>
// Добавляем колонку нумерации строк
HTMLTextAreaElement.prototype.addBuddy = function() {
var buddy = document.createElement("textarea"); // "Товарищеская" колонка строк
var keeper = document.createElement("div"); // "Хранитель" связки элементов
// Чтобы всё не повисло при очередной "мутации", присваиваем значение прямо сейчас
this.dataset.value = this.value;
// Вставляем "хранителя" перед основным элементом
this.parentElement.insertBefore(keeper, this);
// Передаём основной элемент "хранителю"
keeper.appendChild(this);
// Текстовое поле поверх нумератора строк должно быть прозрачным, чтобы колёсико мышки действовало
this.style.backgroundColor = "transparent";
this.style.position = "relative";
// Наш "товарищ" должен располагаться слева от текста и отображать номера строк
buddy.style.position = "absolute";
buddy.style.overflow = "hidden";
buddy.style.textAlign = "right";
buddy.style.resize = "none";
// Нумерация строк "чёрным по серебристому"
buddy.style.backgroundColor = "silver";
// Доступ к цвету "товарищу" через основной элемент
Object.defineProperty(this, "buddy", {
get: (function() {
return this.buddy;
}).bind({buddy: buddy})
});
// Вставляем "товарища" перед основным элементом
this.parentElement.insertBefore(buddy, this);
// Вставляем заголовок над основным элементом?
if("caption" in this.dataset) {
var header = document.createElement("header"); // Заголовок текстового поля
this.parentElement.insertBefore(header, buddy); // Вставляем над "товарищем" и основным элементом
header.textContent = this.dataset["caption"] != "" ? this.dataset["caption"] : this.title;
// Предоставляем доступ к строке заголовка через основной элемент
Object.defineProperty(this, "caption", {
get: (function() {
return this.caption;
}).bind({caption: header})
});
}
if("status" in this.dataset) {
var footer = document.createElement("footer"); // Статус текстового поля
this.parentElement.appendChild(footer); // Вставляем строку статуса под основным элементом
// Предоставляем доступ к статусной строке через основной элемент
Object.defineProperty(this, "status", {
get: (function() {
return this.status;
}).bind({status: footer})
});
}
// Первая инициация "товарища"
buddy.setAttribute("name", "buddy");
this.updateBuddy();
// Примечание: Данная череда назначения обработчиков не обязательна,
// но необходима для демонстрации вызова в пользовательских функциях
this.addEventListener("mousemove",
function(evt) {
evt.srcElement.updateBuddy();
}
);
this.addEventListener("mouseup",
function(evt) {
evt.srcElement.updateBuddy();
}
);
this.addEventListener("keydown",
function(evt) {
evt.srcElement.updateBuddy();
}
);
this.addEventListener("keyup",
function(evt) {
evt.srcElement.updateBuddy();
}
);
this.addEventListener("scroll",
function(evt) {
evt.srcElement.updateBuddy();
}
);
}
</script>
<style>
div
{
position :absolute;
width :auto;
height :auto;
}
header
{
background-color:blue;
color :yellow;
}
footer
{
background-color:silver;
color :black;
border :medium silver sunken;
}
</style>
</head>
<body>
<input type=text placeholder='Цвет фона заголовка' onchange='hTextArea.caption.style.backgroundColor = this.value'>
<input type=text placeholder='Текст заголовка' onchange='hTextArea.caption.textContent = this.value'><br>
<input type=text placeholder='Цвет фона статуса' onchange='hTextArea.status.style.backgroundColor = this.value'>
<input type=text placeholder='Текст статуса' onchange='hTextArea.status.textContent = this.value'><br>
<input type=text placeholder='Цвет фона нумератора' onchange='hTextArea.buddy.style.backgroundColor = this.value'><br>
<textarea rows=5 cols=40 id=Main spellcheck=false title='Текстовое поле для редактирования' data-lines=show data-caption='Опциональный текст заголовка' data-status>
lorem ipsum,
quia dolor sit,
amet,
consectetur,
adipisci velit,
sed quia non numquam eius modi tempora incidunt,
ut labore et dolore magnam aliquam quaerat voluptatem.</textarea>
<script>
var hTextArea = document.querySelector("textarea#Main");
</script></body>Так как здесь используются специфические запрещённые приёмы, которые не всем браузерам придутся по вкусу.
Код разбит на отдельные законченные фрагменты и достаточно прокомментирован, чтобы легче воспринимался.
Теперь строка статуса имеет всплывающую подсказку с точным периодом времени, затраченного на расчёт строк.
В качестве эксперимента добавил несколько input'ов для непосредственного управления "заголовком" и "статусом", а также цветом самого нумератора, чтобы продемонстрировать простоту доступа ко всем сопутствующим элементам через основной.
В DOM столбик нумератора имеет флаг «disabled» и «fixed», Почему fixed, а не absolute?
Зачем при каждой обработке события заново создавать dummy? И каждый раз присваивать фиксированные значения (типа 'none', '0px') в style. Не эффективнее ли будет создать один dummy один раз, заполнить все фиксированные значения. А в обработчике только задавать значения зависящие от конкретного элемента textarea и присоединять это dummy в DOM, а потом удалять из DOM.
Alikberov
12.09.2022, 14:31
Почему fixed, а не absolute?Кажeтся там успели побывать все значения. Что было последним, то и осталось (fixed короче absolute в рамках лимита размера поста на данном форуме в 10 тысяч символов), так как особой разницы не заметил.:no:
Зачем при каждой обработке события заново создавать dummy? И каждый раз присваивать фиксированные значения (типа 'none', '0px') в style.В выходные попытался оптимизировать со статическим dummy - всё слетело!Не эффективнее ли будет создать один dummy один раз, заполнить все фиксированные значения. А в обработчике только задавать значения зависящие от конкретного элемента textarea и присоединять это dummy в DOM, а потом удалять из DOM.Всё слетело и потребовалось бы писать кругомИ каждый раз присваивать фиксированные значения (типа 'none', '0px') в style.как Вы и предлагаете. А это - излишнее захламление кода в рамках поста на данном форуме.:D
То есть, я не подчеркнул главного: Здесь представлена усечённая форумная реализация.
Естественно, производительность страдает.
(В полной реализации контейнер div перетаскивается мышью, как полноценное окно, в отладчике ассемблера (тема (https://javascript.ru/forum/misc/84271-webassembly-slozhnosti-simd-arifmetiki.html)) и жалкой сотней строчек листинга без переносов.)
Для оптимизации мало dummy сделать постоянным.
Необходимо создать отдельный массив оценки переносов строк, чтобы сравнивать два текста и перевычислять только изменённый фрагмент (не только по onkeypress и selectionStart/End, но и изменении value). Тогда при редактировании текста «Война и Мир» не должно быть зависаний на каждом новом символе…
И расчёт переносов нужно сделать не одним циклом, а генератором для работы в фоновом режиме.
Работы - много!:lol:
Здесь возник вопрос, на который поисковики не выдают готовых решений.
Как можно сравнить два текста (аналогично github) и локализовать именно область изменений?
(С такой задачей я ещё не сталкивался…)
P.S.: А что на счёт моих экспериментов с MutationObserver и Object.defineProperty для buddy, caption и status?
На сколько легально так делать в рамках совместимостей?:yes:
И расчёт переносов нужно сделать не одним циклом, а генератором для работы в фоновом режиме.
Наверно worker имеется в виду.
Не думаю, что это что-нибудь даст. Там большая часть времени тратится на вставку одной строки в dummy, вычисление ее высоты и проч. Но worker не может работать с DOM.
Как можно сравнить два текста (аналогично github) и локализовать именно область изменений?
Ну для поиска первого несовпадения напрашивается что то вроде поиска методом деления пополам (берем половину первой и такое же кол-во символов второй строки, если не равны делим опять их на половины и т.д., если равны, значит идем к задним половинам)А что на счёт моих экспериментов с MutationObserver и Object.defineProperty для buddy, caption и status?
На сколько легально так делать в рамках совместимостей?
Про MutationObserver я думаю не можно ли, а нужно ли.
А вот про модификацию существующих системных объектов, я - категорический противник. Если так делать во всех библиотеках, то потом не разобраться, что откуда в них взялось, и не будет ли коллизий сейчас или впоследствии.
Создавайте свои объекты.
Можно хоть рассмотреть возможность создания своего пользовательского элемента, наследующего от textarea.
Alikberov
12.09.2022, 19:00
Наверно worker имеется в виду.Нет.Ну для поиска первого несовпадения напрашивается что то вроде поиска методом деления пополам (берем половину первой и такое же кол-во символов второй строки, если не равны делим опять их на половины и т.д., если равны, значит идем к задним половинам)Значит, готового метода нет.:-?
Ясно: Снова костыльничать…
Про MutationObserver я думаю не можно ли, а нужно ли.
А вот про модификацию существующих системных объектов, я - категорический противник. Если так делать во всех библиотеках, то потом не разобраться, что откуда в них взялось, и не будет ли коллизий сейчас или впоследствии.Буду учитывать…
Создавайте свои объекты.
Можно хоть рассмотреть возможность создания своего пользовательского элемента, наследующего от textarea.Уже создавал, но всё сломалось с новыми ревизиями.
(К 25-летию Befunge (https://jsfiddle.net/Alikberov/2m1cuxnw/) использовал в своём скрипте свой элемент, который потом перестал работать во всех браузерах.)
К 25-летию Befunge использовал
В РФ jsfiddle.net заблочен. ничего не могу посмотреть.
Но сейчас все API пользовательских элементов стабилизировались.
Наверно worker имеется в виду.
Нет.
А что имеется в виду под "генератором для работы в фоновом режиме."?
Ну для поиска первого несовпадения напрашивается что то вроде поиска методом деления пополам (берем половину первой и такое же кол-во символов второй строки, если не равны делим опять их на половины и т.д., если равны, значит идем к задним половинам)
Хотя может быть простое линейное сравнение символов (с помощью charCodeAt()) будет эффективнее.
Alikberov
12.09.2022, 21:01
В РФ jsfiddle.net заблочен.Очeнь жаль…
ничего не могу посмотреть.А там и нечего смотреть, так как ничего не работает из-за того, что я накастомничал свой элемент (как в Windows: Заголовок с тремя кнопками и меню под ним - классика!) в качестве эксперимента.
(В теме (https://javascript.ru/forum/misc/84271-webassembly-slozhnosti-simd-arifmetiki.html) ссылка на gist не заблокирована? Там я простенько заголовок с меню сделал такой же - без кастома, можно посмотреть.)
Но сейчас все API пользовательских элементов стабилизировались.Очень приятно это слышать.
(Хотя, может я ошибаюсь, но FF сейчас стоит в той же позиции, что и некогда IE: Некоторые уникальные вещи работают только в FF, хотя некоторые банальные вещи в FF так и не работают.)
Пытался пару лет назад снова кастомный элемент сделать. Типа, получилось, но настроения уже не было до такой степени мозгами скрипеть.А что имеется в виду под "генератором для работы в фоновом режиме."?Я говорил про функцию-генератор с циклом прочёсывания всего текста на признак переноса строк, чтобы buddy заполнялся на лету, не подвешивая всё кругом.
Или вообще, точно определять область видимости текста и прочёсывать только конкретный фрагмент, заполняя buddy по мере прокрутки текста.
Вариантов несколько, но у всех свои недостатки.
Сейчас нужно с dummy как-то разобраться, сделав его глобальным и статичным, добиться отсутствия глюков в переносах. А потом взяться за кастомный TextArea.
Но меня больше расстроило отсутствие метода сравнения строк, так как это тоже не самая последняя функция из востребованных.Хотя может быть простое линейное сравнение символов (с помощью charCodeAt()) будет эффективнее.Может массив хэш-сумм строк делать и потом их сравнивать на первом этапе?
Alikberov
13.09.2022, 03:18
Создавайте свои объекты.
Можно хоть рассмотреть возможность создания своего пользовательского элемента, наследующего от textarea.Xм, как-то так:<script>
class FineTextArea extends HTMLTextAreaElement {
constructor() {
super();
this.dummy = document.createElement("textarea");
this.dummy.style.position = "absolute";
this.dummy.style.top = "-400px";
this.dummy.style.paddingTop = "0px";
this.dummy.style.marginTop = "0px";
this.dummy.style.borderTop = "none";
this.dummy.style.borderTopWidth = "0px"
this.dummy.style.paddingBottom = "0px";
this.dummy.style.marginBottom = "0px";
this.dummy.style.borderBottom = "none";
this.dummy.style.borderBottomWidth = "0px";
this.dummy.style.visibility = "hidden";
this.dummy.style.overflowX = "hidden";
*!*
this.dummy.style.overflowY = "scroll"; // Важно для FF
*/!*
document.body.appendChild(this.dummy);
}
connectedCallback() {
this.buddy = document.createElement("TextArea");
this.buddy.className = "FineTextArea-Buddy";
this.buddy.id = this.id + "_buddy";
this.parentElement.insertBefore(this.buddy, this);
this.addEventListener("mousemove", this.update.bind(this));
this.addEventListener("mouseup", this.update.bind(this));
this.addEventListener("scroll", this.update.bind(this));
setTimeout(this.update.bind(this), 0);
}
set lines(value) {
this.buddy.style.visibility = value == true ? "visible" : "hidden";
this.style.paddingLeft = value == true ? this.buddy.offsetWidth + "px" : "0px";
if(value)
this.update();
}
get row() {
return this.value.substr(0, this.selectionStart).split(/\r?\n/).length;
}
get col() {
return this.value.substr(0, this.selectionEnd).split(/\r?\n/).pop().length + 1;
}
update() {
this.buddy.cols = this.value.split(/\r?\n/).length.toString().length;
this.buddy.rows = this.rows;
this.buddy.style.height = this.offsetHeight + "px";
this.buddy.scrollTop = this.scrollTop;
this.style.paddingLeft = this.buddy.offsetWidth + "px";
this.dummy.rows = 1;
this.dummy.cols = this.cols;
this.dummy.style.width = this.offsetWidth + "px";
this.dummy.style.paddingLeft = this.buddy.offsetWidth + "px";
// Прочёсываем все строки
var lines = [];
var i = 1;
console.time("Calculating line-breaks");
var fromTime = window.performance.now();
for(var line of this.value.split(/\r?\n/)) {
lines.push(i);
this.dummy.value = line;
// Не перенеслась ли строчка?
var separating = this.dummy.scrollHeight / this.dummy.offsetHeight;
// Разбиваем нумерацию в этом месте
while(-- separating > 0)
lines.push("");
i ++;
}
console.timeEnd("Calculating line-breaks");
var lastTime = window.performance.now();
// Обновляем всю колонку нумерации строк
this.buddy.value = lines.join("\r\n");
this.buddy.scrollTop = this.scrollTop;
}
static get observedAttributes() {
return "lines value".split(/\s+/);
}
attributeChangedCallback(name, oldValue, newValue) {
switch(name) {
case "lines":
//
break;
}
}
}
customElements.define("fine-textarea", FineTextArea, {extends: "textarea"});
</script>
<style>
textarea[is='fine-textarea']
{
position :relative;
background-color:transparent;
}
textarea.FineTextArea-Buddy
{
position :absolute;
background-color:red;
overflow :hidden;
text-align :right;
resize :none;
}
div
{
position :absolute;
width :auto;
height :auto;
}
header
{
background-color:blue;
color :yellow;
}
footer
{
background-color:silver;
color :black;
border :medium silver sunken;
}
</style>
</head>
<body>
<input type=text placeholder='Атрибут = Значение' onchange='this.style.backgroundColor = "red"; eval(`hTextArea.${this.value}`); this.style.backgroundColor = "yellow"'><br>
<input type=text placeholder='Цвет фона заголовка' onchange='hTextArea.caption.style.backgroundColor = this.value'>
<input type=text placeholder='Текст заголовка' onchange='hTextArea.caption.textContent = this.value'><br>
<input type=text placeholder='Цвет фона статуса' onchange='hTextArea.status.style.backgroundColor = this.value'>
<input type=text placeholder='Текст статуса' onchange='hTextArea.status.textContent = this.value'><br>
<input type=text placeholder='Цвет фона нумератора' onchange='hTextArea.buddy.style.backgroundColor = this.value'><br>
<textarea rows=15 cols=40 id=Main spellcheck=false title='Текстовое поле для редактирования' is=fine-textarea caption='Опциональный текст заголовка' status lines=true>
Sed ut perspiciatis,
unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam eaque ipsa,
quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt,
explicabo.
Nemo enim ipsam voluptatem,
quia voluptas sit,
aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos,
qui ratione voluptatem sequi nesciunt,
neque porro quisquam est,
qui dolorem ipsum,
quia dolor sit,
amet,
consectetur,
adipisci velit,
sed quia non numquam eius modi tempora incidunt,
ut labore et dolore magnam aliquam quaerat voluptatem.
Ut enim ad minima veniam,
quis nostrum exercitationem ullam corporis suscipit laboriosam,
nisi ut aliquid ex ea commodi consequatur?
Quis autem vel eum iure reprehenderit,
qui in ea voluptate velit esse,
quam nihil molestiae consequatur,
vel illum,
qui dolorem eum fugiat,
quo voluptas nulla pariatur?
At vero eos et accusamus et iusto odio dignissimos ducimus,
qui blanditiis praesentium voluptatum deleniti atque corrupti,
quos dolores et quas molestias excepturi sint,
obcaecati cupiditate non provident,
similique sunt in culpa, qui officia deserunt mollitia animi,
id est laborum et dolorum fuga.
Et harum quidem rerum facilis est et expedita distinctio.
Nam libero tempore,
cum soluta nobis est eligendi optio,
cumque nihil impedit,
quo minus id,
quod maxime placeat,
facere possimus,
omnis voluptas assumenda est,
omnis dolor repellendus.
Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet,
ut et voluptates repudiandae sint et molestiae non recusandae.
Itaque earum rerum hic tenetur a sapiente delectus,
ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores
repellat.
</textarea>
<script>
var hTextArea = document.querySelector("textarea#Main");
</script>
</body>Здесь возникла ещё проблема в том, что невозможно отследить изменение value.:nono:
Или я не до конца разобрался во невсех тонкостях?:no:
Проблем больше, чем я предполагал.
Одна из очевидных: Сломался калькулятор переносов, так как при ресайзинге нумерация строк сбивается на последней строке, что говорит о необходимости более основательной зачистки атрибутов dummy, о чём выше речь и шла.
P.S.: А так, в целом, как видно, класс получился не таким уж большим - менее сотни строк. Что радует в определённой степени.:yes:
В Firefox не совсем хорошо
Alikberov
13.09.2022, 14:17
В Firefox не совсем хорошоУпустил строку одну (исправил и пометил её в листинге).:thanks:
Заметил другую особенность:
FireFox отображает колонку нумерации как я и задумывал: Шириной достаточной, чтобы вместить номер строки самой последней строки.
Chrome добавляет какой-то отступ, подавить который я не могу: Весь перечень вычисленных свойств просмотрел - нету намёка. Подозреваю, что ширина бара вертикального скролла учитывается, хотя он и скрыт.
(Скриншоты прикрепил.)
Внезапно обнаружил, что встроенных функций вычисления хэш-сумм текста нет, что негативно скажется на производительности (привет, полифил-костыли!) в очередной раз…
Alikberov
13.09.2022, 23:23
<html>
<head>
<title>Построчная нумерация</title>
<script>
class FineTextArea extends HTMLTextAreaElement {
constructor() {
super();
}
connectedCallback() {
this._lines = true;
this.buddy = document.createElement("TextArea");
this.buddy.className = "FineTextArea-Buddy";
this.buddy.id = this.id + "_buddy";
this.buddy.disabled = true;
this.parentElement.insertBefore(this.buddy, this);
this.addEventListener("change", this.update.bind(this));
this.addEventListener("keypress", this.update.bind(this));
this.addEventListener("mousemove", this.update.bind(this));
this.addEventListener("mouseup", this.update.bind(this));
this.addEventListener("scroll", this.update.bind(this));
setTimeout(this.update.bind(this), 0);
}
set lines(value) {
this._lines = value;
this.buddy.style.visibility = value != false ? "visible" : "hidden";
this.style.paddingLeft = value != false ? this.buddy.offsetWidth + "px" : "0px";
if(value != false)
this.update(true);
}
get row() {
return this.value.substr(0, this.selectionStart).split(/\r?\n/).length;
}
get col() {
return this.value.substr(0, this.selectionEnd).split(/\r?\n/).pop().length + 1;
}
update(force) {
this.buddy.scrollTop = this.scrollTop;
var rows = this.value.split(/\r?\n/);
var row = 1;
var len = rows.length.toString().length;
var spc = "string" == typeof this._lines && this._lines != "" ? this._lines.charAt(0) : "\xA0";
// Здесь следуют некоторые манипуляции со стилями
this.style.paddingLeft = `${len}em`;
this.buddy.style.paddingRight = window.getComputedStyle(this, null).getPropertyValue("padding-left");
this.buddy.style.height = window.getComputedStyle(this, null).getPropertyValue("height");
this.buddy.style.width = window.getComputedStyle(this, null).getPropertyValue("width");
// Далее следует довольно важная строчка, которая влияет на накапливание ошибок
this.buddy.style.paddingLeft = window.getComputedStyle(this, null).getPropertyValue("padding-right");
// Затем всё совсем просто
if(force || this._value != this.value) {
// Переносим весь текст в поле нумератора, заменяя всё (почти: дефисы - нельзя),
// кроме Пробела, на символ неразрывного пробела,
// попутно вставляя нумерацию
this.buddy.value = this.value.replace(/^.*$/gm,
function(str) {
return Number(row ++).toString().padStart(len, spc) + str.substr(len).replace(/-/gm, " ").replace(/\S/gm, spc);
}
);
this._value = this.value;
}
this.buddy.scrollTop = this.scrollTop;
}
static get observedAttributes() {
return "lines".split(/\s+/);
}
attributeChangedCallback(name, oldValue, newValue) {
switch(name) {
case "lines":
this._lines = newValue;
break;
}
}
}
customElements.define("fine-textarea", FineTextArea, {extends: "textarea"});
</script>
<style>
textarea[is='fine-textarea']
{
position :relative;
background-color:transparent;
}
textarea.FineTextArea-Buddy
{
--border-right :none;
position :absolute;
--top:400px;
background-color:yellow;
--overflow :hidden;
--text-align :right;
--resize :none;
}
div
{
position :absolute;
width :auto;
height :auto;
}
header
{
background-color:blue;
color :yellow;
}
footer
{
background-color:silver;
color :black;
border :medium silver sunken;
}
</style>
</head>
<body>
<input type=text placeholder='Атрибут = Значение' onchange='this.style.backgroundColor = "red"; eval(`hTextArea.${this.value}`); this.style.backgroundColor = "yellow"'><br>
<input type=text placeholder='Цвет фона нумератора' onchange='hTextArea.buddy.style.backgroundColor = this.value'><br>
<textarea rows=15 cols=40 id=Main spellcheck=false title='Текстовое поле для редактирования' is=fine-textarea caption='Опциональный текст заголовка' status lines=true>
Sed ut perspiciatis,
unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam eaque ipsa,
quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt,
explicabo.
Nemo enim ipsam voluptatem,
quia voluptas sit,
aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos,
qui ratione voluptatem sequi nesciunt,
neque porro quisquam est,
qui dolorem ipsum,
quia dolor sit,
amet,
consectetur,
adipisci velit,
sed quia non numquam eius modi tempora incidunt,
ut labore et dolore magnam aliquam quaerat voluptatem.
Ut enim ad minima veniam,
quis nostrum exercitationem ullam corporis suscipit laboriosam,
nisi ut aliquid ex ea commodi consequatur?
Quis autem vel eum iure reprehenderit,
qui in ea voluptate velit esse,
quam nihil molestiae consequatur,
vel illum,
qui dolorem eum fugiat,
quo voluptas nulla pariatur?
At vero eos et accusamus et iusto odio dignissimos ducimus,
qui blanditiis praesentium voluptatum deleniti atque corrupti,
quos dolores et quas molestias excepturi sint,
obcaecati cupiditate non provident,
similique sunt in culpa, qui officia deserunt mollitia animi,
id est laborum et dolorum fuga.
Et harum quidem rerum facilis est et expedita distinctio.
Nam libero tempore,
cum soluta nobis est eligendi optio,
cumque nihil impedit,
quo minus id,
quod maxime placeat,
facere possimus,
omnis voluptas assumenda est,
omnis dolor repellendus.
Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet,
ut et voluptates repudiandae sint et molestiae non recusandae.
Itaque earum rerum hic tenetur a sapiente delectus,
ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores
repellat.
</textarea>
<script>
var hTextArea = document.querySelector("textarea#Main");
</script>
</body>Сутью метода является то, что два элемента TextArea одинаково себя ведут на одном и том же тексте, даже если во вспомогательном TextArea все зримые символы заменить на неразрывной пробел, чтобы всё стало прозрачным, кроме первых символов каждой строки, которые заменяем на порядковый номер строки.
P.S.: К сожалению, вариант иногда сбоит…
Внезапно обнаружил, что встроенных функций вычисления хэш-сумм текста нет, что негативно скажется на производительности (привет, полифил-костыли!) в очередной раз…
Вы думаете, что считать хеш будет быстрее, чем тупо сравнивать строки?
Alikberov
14.09.2022, 13:42
Вы думаете, что считать хеш будет быстрее, чем тупо сравнивать строки?Алгоритмы сравнения строк тоже разные бывают (Бойера-Мура (http://www.algolib.narod.ru/Search/BoyerMur.html)).
Ну, смoтря по какому алгоритму вычислять так называемую условную хэш-сумму (если не для банка, можно учитывать только коды первого и последнего символов, а также длину самой строки и количество пробелов в ней - четыре параметра), как подсчитывали чек-суммы ещё на ЭВМ типа ДВК/РК.:D
А так - вариант #38 (https://javascript.ru/forum/dom-window/84421-otobrazhenie-strok-u-textarea-4.html#post547959) мне больше нравится своей простотой и производительностью. Нужно лишь со стабильностью разобраться…
Вариант №38 - лучшее компромиссное решение, так как в поле нумератора строк копируется исходный текст, что гарантирует эквивалентый разрыв строк. Остаётся лишь начало каждой строки промаркировать индексом и замаскировать остальные символы, так как графически текст смещён и нельзя допустить визуального смешивания контентов.
И вот тут нас ждёт подвох, так как не все символы можно равноценно подменить под неразрывной пробел - пиксельная длина строки местами отличается, что ломает всё!
Но именно в этом направлении следует исследовать вопрос, так как код варианта №38 самый простой, компактный (практически 10 строк цикла) и не требует всяких скрытых dummy-элементов.
Сравнение строк тут будет выполнять ассемблерная оптимизированная функция. Простой пробег по символам.
А писать вычисление хеша придется на js. Все равно проход по строке (хоть суммируй их, хоть пробелы считай) в цикле. Но этот цикл уже будет не на ассемблере, а на js.
И еще совет
Вот кусок вашего кода. Он крайне не эффективен и может тормозить
this.style.paddingLeft = `${len}em`;
this.buddy.style.paddingRight = window.getComputedStyle(this, null).getPropertyValue("padding-left");
this.buddy.style.height = window.getComputedStyle(this, null).getPropertyValue("height");
this.buddy.style.width = window.getComputedStyle(this, null).getPropertyValue("width");
// Далее следует довольно важная строчка, которая влияет на накапливание ошибок
this.buddy.style.paddingLeft = window.getComputedStyle(this, null).getPropertyValue("padding-right");
Что тут происходит.
this.buddy.style.paddingRight = window.getComputedStyle(this, null).getPropertyValue("padding-left"); - установили какой то стиль элементу buddy.
this.buddy.style.height = window.getComputedStyle(this, null).getPropertyValue("height"); - теперь пытаемся получить стиль от this.
Браузер не всегда знает, как установка стиля одному элементу повлияла на другие. Что бы получить вычисленный стиль элемента после изменения стиля других, ему часто приходится делать перерасчет стилей всего DOM дерева, или какой то его части.
Последовательность
установка стиля
получение стиля
установка стиля
получение стиля
....
может вызывать торможение
Сначала, по возможности, надо получить все необходимые стили (ко всяким метрикам типа offsetWidth и т.п это тоже относится), а потом делать все изменения. Множественные изменения браузер накапливает, и применяет их все разом при расчете стилей перед очередным этапом визуализации.
Alikberov
14.09.2022, 21:12
И еще совет
Вот кусок вашего кода. Он крайне не эффективен и может тормозить
Сначала, по возможности, надо получить все необходимые стили (ко всяким метрикам типа offsetWidth и т.п это тоже относится), а потом делать все изменения. Множественные изменения браузер накапливает, и применяет их все разом при расчете стилей перед очередным этапом визуализации.Этo очень ценное замечание, так как я не понимаю всех механизмов браузера.:thanks:
Не удивительно, что на коротком тексте и без его парсинга у меня всё как-то лениво срабатывает.
В этом варианте я учёл некоторые нюансы. И нумератор скрывается при перемещениях мышью.<html>
<head>
<title>Построчная нумерация</title>
<script>
class FineTextArea extends HTMLTextAreaElement {
constructor() {
super();
}
connectedCallback() {
var update = this.update.bind(this, window.event, true);
this._lines = true;
this.buddy = document.createElement("TextArea");
this.buddy.className = "FineTextArea-Buddy";
this.buddy.id = this.id + "_buddy";
this.buddy.disabled = true;
this.parentElement.insertBefore(this.buddy, this);
this.addEventListener("change", this.update.bind(this));
this.addEventListener("keypress", this.update.bind(this));
this.addEventListener("mousemove", this.update.bind(this));
this.addEventListener("mouseup", this.update.bind(this));
this.addEventListener("scroll", this.update.bind(this));
// Форсируем коррекцию расчётов
setTimeout(update, 0); setTimeout(update, 0);
}
set lines(value) {
this._lines = value;
this.buddy.style.visibility = value != false ? "visible" : "hidden";
this.style.paddingLeft = value != false ? this.buddy.offsetWidth + "px" : "0px";
if(value != false)
this.update(window.event, true);
}
get row() {
return this.value.substr(0, this.selectionStart).split(/\r?\n/).length;
}
get col() {
return this.value.substr(0, this.selectionEnd).split(/\r?\n/).pop().length + 1;
}
update(evt, force) {
var buttons = evt && evt.buttons || 0;
this.buddy.scrollTop = this.scrollTop;
var rows = this.value.split(/\r?\n/);
var row = 1;
var len = rows.length.toString().length;
var spc = ("string" == typeof this._lines && this._lines != "" ? this._lines.charAt(0) : "\xA0").repeat(len);
var rex = new RegExp(`([ -])([^ -]{${len}})`, "gim"); // Выявление первых символов каждого слова
if((buttons == 0 && (this.buddy.scrollHeight != this.scrollHeight || this.buddy.scrollWidth != this.scrollWidth)) || force == true) {
// Здесь следуют некоторые манипуляции со стилями
this.buddy.style.paddingRight = "0px";
this.buddy.style.width = "auto";
this.buddy.cols = len;
// Маскируем задний план так, чтобы была видна только область нумерации
this.style.background = `linear-gradient(90deg, rgba(0,0,0,0) ${this.buddy.clientWidth - 1}px, white ${this.buddy.clientWidth}px, white 100%)`;
this.style.paddingLeft = `${this.buddy.clientWidth}px`;
// Копируем визуальные реквизиты элемента
this.buddy.cols = this.cols;
this.buddy.style.height = window.getComputedStyle(this, null).getPropertyValue("height");
this.buddy.style.width = window.getComputedStyle(this, null).getPropertyValue("width");
// Компенсируем визуальные отступы как у текста, так и у нумератора
this.buddy.style.paddingLeft = window.getComputedStyle(this, null).getPropertyValue("padding-right");
this.buddy.style.paddingRight = window.getComputedStyle(this, null).getPropertyValue("padding-left");
}
// Затем всё совсем просто
if(force == true || this._value != this.value) {
// Переносим весь текст в поле нумератора,
// заменяя всё, кроме Пробела и Дефиса,
// на символ неразрывного пробела в начале каждого слова,
// попутно вставляя нумерацию вместо первых символов
console.time("Форматирование текста под задний план");
this.buddy.value = this.value.replace(/^.*$/gm, function(str) {
return Number(row ++).toString().padStart(len, spc) + str.substr(str.charAt(0) == "\t" ? 0 : len).replace(rex, `$1${spc}`);
}
);
console.timeEnd("Форматирование текста под задний план");
this._value = this.value;
}
this.buddy.style.visibility = buttons || this._lines == false ? "hidden" : "visible";
this.buddy.scrollTop = this.scrollTop;
}
static get observedAttributes() {
return "lines".split(/\s+/);
}
attributeChangedCallback(name, oldValue, newValue) {
switch(name) {
case "lines":
this._lines = newValue;
break;
}
}
}
customElements.define("fine-textarea", FineTextArea, {extends: "textarea"});
</script>
<style>
textarea[is='fine-textarea']
{
position :relative;
background-color:transparent;
}
textarea.FineTextArea-Buddy
{
--border-right :none;
position :absolute;
--top:400px;
background-color:yellow;
--overflow :hidden;
--text-align :right;
--resize :none;
}
div
{
position :absolute;
width :auto;
height :auto;
}
header
{
background-color:blue;
color :yellow;
}
footer
{
background-color:silver;
color :black;
border :medium silver sunken;
}
</style>
</head>
<body>
<input type=text placeholder='Атрибут = Значение' onchange='this.style.backgroundColor = "red"; eval(`hTextArea.${this.value}`); this.style.backgroundColor = "yellow"'><br>
<input type=text placeholder='Цвет фона нумератора' onchange='hTextArea.buddy.style.backgroundColor = this.value'><br>
<textarea rows=15 cols=40 id=Main spellcheck=false title='Текстовое поле для редактирования' is=fine-textarea lines=true>
Sed ut perspiciatis,
unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam eaque ipsa,
quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt,
explicabo.
Nemo enim ipsam voluptatem,
quia voluptas sit,
aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos,
qui ratione voluptatem sequi nesciunt,
neque porro quisquam est,
qui dolorem ipsum,
quia dolor sit,
amet,
consectetur,
adipisci velit,
sed quia non numquam eius modi tempora incidunt,
ut labore et dolore magnam aliquam quaerat voluptatem.
Ut enim ad minima veniam,
quis nostrum exercitationem ullam corporis suscipit laboriosam,
nisi ut aliquid ex ea commodi consequatur?
Quis autem vel eum iure reprehenderit,
qui in ea voluptate velit esse,
quam nihil molestiae consequatur,
vel illum,
qui dolorem eum fugiat,
quo voluptas nulla pariatur?
At vero eos et accusamus et iusto odio dignissimos ducimus,
qui blanditiis praesentium voluptatum deleniti atque corrupti,
quos dolores et quas molestias excepturi sint,
obcaecati cupiditate non provident,
similique sunt in culpa, qui officia deserunt mollitia animi,
id est laborum et dolorum fuga.
Et harum quidem rerum facilis est et expedita distinctio.
Nam libero tempore,
cum soluta nobis est eligendi optio,
cumque nihil impedit,
quo minus id,
quod maxime placeat,
facere possimus,
omnis voluptas assumenda est,
omnis dolor repellendus.
Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet,
ut et voluptates repudiandae sint et molestiae non recusandae.
Itaque earum rerum hic tenetur a sapiente delectus,
ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores
repellat.
</textarea>
<script>
var hTextArea = document.querySelector("textarea#Main");
</script>
</body>Правда, имеются проблемы в зоне нумерации строк, так как там иногда отображаются символы, подавление которых ухудшает ситуацию разрыва строк.
Alikberov
16.09.2022, 16:25
<html>
<head>
<title>Построчная нумерация</title>
<script>
class FineTextArea extends HTMLTextAreaElement {
constructor() {
super();
}
connectedCallback() {
var update = this.update.bind(this, window.event, true);
this.buddy = document.createElement("TextArea");
this.buddy.className = "FineTextArea-Buddy";
this.buddy.id = this.id + "_buddy";
this.buddy.disabled = true;
this.parentElement.insertBefore(this.buddy, this);
this.addEventListener("change", this.update.bind(this));
this.addEventListener("keyup", this.update.bind(this));
this.addEventListener("keydown", this.update.bind(this));
this.addEventListener("keypress", this.update.bind(this));
this.addEventListener("mousemove", this.update.bind(this));
this.addEventListener("mouseup", this.update.bind(this));
this.addEventListener("scroll", this.update.bind(this));
// Создаём элементы подсветки синтаксиса
const keywords = "break case const continue else for if let switch while var".replace(/\s+/g, "|");
this.syntaxers = [];
this.syntexps = [
new RegExp(`\\b(${keywords})\\b`, "gim"),
new RegExp(`\\b(?!${keywords})\\b`, "gim"),
new RegExp(`\\b(${keywords})\\b`, "gim"),
/[^\x21-\x7F\n\r\s\t]+/gim,
/[\x21-\x7F]+/gim
];
for(var i in this.syntexps) {
var syn = document.createElement("TextArea");
syn.className = "FineTextArea-Syntax" + i;
syn.id = this.id + "_syntax" + i;
syn.disabled = true;
this.parentElement.insertBefore(syn, this);
this.syntaxers.push(syn);
}
// Форсируем (альфа-отладка)
this.lines = true;
this.syntax = true;
setTimeout(update, 0); setTimeout(update, 0);
}
set lines(value) {
this._lines = value;
this.buddy.style.visibility = value != false ? "visible" : "hidden";
this.style.paddingLeft = value != false ? this.buddy.offsetWidth + "px" : "0px";
if(value != false)
this.update(window.event, true);
}
set syntax(value) {
this._syntax = !!value;
this.update(window.event, true);
}
get row() {
return this.value.substr(0, this.selectionStart).split(/\r?\n/).length;
}
get col() {
return this.value.substr(0, this.selectionEnd).split(/\r?\n/).pop().length + 1;
}
update(evt, force) {
var buttons = evt && evt.buttons || 0;
var rows = this.value.split(/\r?\n/), len = rows.length.toString().length;
var syn, i, row = 1;
this.buddy.scrollTop = this.scrollTop;
for(syn of this.syntaxers)
syn.scrollTop = this.scrollTop;
var spc = ("string" == typeof this._lines && this._lines != "" ? this._lines.charAt(0) : "\xA0").repeat(len);
var rex = new RegExp(`([ -])([^ -]{${len}})`, "gim"); // Выявление первых символов каждого слова
// Распределяем текст по слоям
var value = this.value, box = "\u2588";
for(i in this.syntexps) {
this.syntaxers[i].value = value.replace(this.syntexps[i], s => box.repeat(s.length));
box = "\xA0";
if(i != 0)
value = value.replace(this.syntexps[i], s => box.repeat(s.length));
}
if((buttons == 0 && (Math.abs(this.buddy.scrollHeight - this.scrollHeight) > 30 || this.buddy.scrollWidth != this.scrollWidth)) || force == true) {
var width;
var height;
var bg;
// Здесь следуют некоторые манипуляции со стилями
this.style.width = (parseInt(window.getComputedStyle(this, null).getPropertyValue("width")) & 0xFFE) + "px";
this.buddy.style.paddingRight = "0px";
this.buddy.style.width = "auto";
this.buddy.cols = len;
// Маскируем задний план так, кроме области нумерации
bg = `linear-gradient(90deg, rgba(0,0,0,0) ${this.buddy.clientWidth - 1}px, white ${this.buddy.clientWidth}px, white 100%)`;
if(this._syntax == false || buttons)
this.style.background = bg;
else
this.style.background = "transparent",
this.syntaxers[0].style.background = bg;
this.style.paddingLeft = `${this.buddy.clientWidth}px`;
// Расчёт
height = window.getComputedStyle(this, null).getPropertyValue("height");
width = window.getComputedStyle(this, null).getPropertyValue("width");
// Обновляем синтаксические помощники
for(syn of this.syntaxers) {
syn.cols = this.cols;
syn.style.paddingLeft = `${this.buddy.clientWidth}px`;
syn.style.height = height;
syn.style.width = width;
}
// Копируем визуальные реквизиты элемента
this.buddy.cols = this.cols;
this.buddy.style.height = height;
this.buddy.style.width = width;
// Компенсируем визуальные отступы у текста и нумератора
this.buddy.style.paddingLeft = window.getComputedStyle(this, null).getPropertyValue("padding-right");
this.buddy.style.paddingRight = window.getComputedStyle(this, null).getPropertyValue("padding-left");
if(this.scrollHeight != this.buddy.scrollHeight)
force = !true;
}
// Затем всё совсем просто
if(force == true || this._value != this.value) {
// Переносим весь текст в поле нумератора, кроме первых символов каждого слова, заменяя их нумерацией
console.time("Numbering");
for(var fine = 0; fine < 2; ++ fine) {
this.buddy.value = this.value.replace(/^.*$/gm, function(str) {
return Number(row ++).toString().padStart(len, spc) + str.substr(str.charAt(0) == "\t" ? 0 : len).replace(rex, `$1${spc}`);
}
);
if(this.scrollHeight == this.buddy.scrollHeight)
fine = 1;
else
fine = 1;
}
console.timeEnd("Numbering");
this._value = this.value;
}
this.buddy.style.visibility = buttons || this._lines == false ? "hidden" : "visible";
this.buddy.scrollTop = this.scrollTop;
for(syn of this.syntaxers)
syn.scrollTop = this.scrollTop,
syn.style.visibility = buttons || this._syntax == false ? "hidden" : "visible";
this.style.color = buttons || this._syntax == false ? "black" : "transparent";
}
static get observedAttributes() {
return "lines syntax".split(/\s+/);
}
attributeChangedCallback(name, oldValue, newValue) {
switch(name) {
case "lines":
this._lines = newValue;
break;
case "syntax":
this._syntax = !!newValue;
//this.update(window.event, true);
break;
}
}
}
customElements.define("fine-textarea", FineTextArea, {extends: "textarea"});
</script>
<style>
textarea[is='fine-textarea']
{
position :relative;
background-color:transparent;
color :transparent;
caret-color :black;
}
textarea.FineTextArea-Buddy
{
position :absolute;
background-color:yellow;
}
textarea.FineTextArea-Syntax0,
textarea.FineTextArea-Syntax1,
textarea.FineTextArea-Syntax2,
textarea.FineTextArea-Syntax3,
textarea.FineTextArea-Syntax4,
textarea.FineTextArea-Syntax5,
textarea.FineTextArea-Syntax6,
textarea.FineTextArea-Syntax7,
textarea.FineTextArea-Syntax8,
textarea.FineTextArea-Syntax9
{
position :absolute;
background-color:transparent;
color :orange;
}
textarea.FineTextArea-Syntax0 { color :lightgreen;}
textarea.FineTextArea-Syntax1 { color :darkgreen;}
textarea.FineTextArea-Syntax2 { color :magenta;}
textarea.FineTextArea-Syntax3 { color :blue;}
</style>
</head>
<body>
<input type=text placeholder='Атрибут = Значение' onchange='this.style.backgroundColor = "red"; eval(`hTextArea.${this.value}`); this.style.backgroundColor = "yellow"'><br>
<input type=text placeholder='Цвет фона нумератора' onchange='hTextArea.buddy.style.backgroundColor = this.value'><br>
<textarea rows=15 cols=40 id=Main spellcheck=false title='Текстовое поле для редактирования' is=fine-textarea lines=true syntax=true>
____________________________________
|Проверка цветовой подсветки V0.003|
====================================
format
for (i = 0; !(i > 9); ++ i)
if (i == j)
continue;
------------------------------------
Sed ut perspiciatis,
unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam eaque ipsa,
quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt,
explicabo.
Nemo enim ipsam voluptatem,
quia voluptas sit,
aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos,
qui ratione voluptatem sequi nesciunt,
neque porro quisquam est,
qui dolorem ipsum,
quia dolor sit,
amet,
consectetur,
adipisci velit,
sed quia non numquam eius modi tempora incidunt,
ut labore et dolore magnam aliquam quaerat voluptatem.
Ut enim ad minima veniam,
quis nostrum exercitationem ullam corporis suscipit laboriosam,
nisi ut aliquid ex ea commodi consequatur?
Quis autem vel eum iure reprehenderit,
qui in ea voluptate velit esse,
quam nihil molestiae consequatur,
vel illum,
qui dolorem eum fugiat,
quo voluptas nulla pariatur?
At vero eos et accusamus et iusto odio dignissimos ducimus,
qui blanditiis praesentium voluptatum deleniti atque corrupti,
quos dolores et quas molestias excepturi sint,
obcaecati cupiditate non provident,
similique sunt in culpa, qui officia deserunt mollitia animi,
id est laborum et dolorum fuga.
Et harum quidem rerum facilis est et expedita distinctio.
Nam libero tempore,
cum soluta nobis est eligendi optio,
cumque nihil impedit,
quo minus id,
quod maxime placeat,
facere possimus,
omnis voluptas assumenda est,
omnis dolor repellendus.
Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet,
ut et voluptates repudiandae sint et molestiae non recusandae.
Itaque earum rerum hic tenetur a sapiente delectus,
ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores
repellat.
</textarea>
<script>
var hTextArea = document.querySelector("textarea#Main");
</script>
</body>
borzik2h
17.09.2022, 00:52
Alikberov, на айфоновском сафари нумерации нет.
Alikberov
17.09.2022, 06:33
Меня тут заинтересовал вопрос:
Как правильно (с позиции вёрстки) оформить управление синтаксическими правилами подсветки?
Через атрибуты: Указать имя списка options?
Через стиль: А можно ли там объявить регулярные выражения?
Ссылаться на тэг var?
И нужно ли вообще отделить подсветку синтаксиса: Данный TextArea-класс оставить чисто для отображения нумераций строк, а для синтаксической подсветки - описать другой класс.
(Как можно видеть, я просто использую наслоение TextArea друг над другом с разными цветами и разными фильтрами текста.
Alikberov, на айфоновском сафари нумерации нет.Интерeсное замечание.
К сожалению, ничего поделать не могу: Не имею ни одного, ни другого.
И нужно ли вообще отделить подсветку синтаксиса
Ее слишком непросто сделать.
Я как то пытался, что то даже получилось, но это без редактирования.
Большую проблему представляют строки и комментарии, в которых могут встретиться любые конструкции.
Сначала приходится выявлять их, что бы потом не рассматривать.
Ну и всякие фичи JS, где ключевое слово не всегда ключевое слово.
Например совершенно легальный код
let a= [1,2,3];
for (const of of a) console.log(of);
Alikberov
17.09.2022, 13:35
Ее слишком непросто сделать.A в конкретной реализации (https://javascript.ru/forum/dom-window/84421-otobrazhenie-strok-u-textarea-5.html#post548000) данный фрагмент
this.syntexps = [
*!*
new RegExp(`\\b(${keywords})\\b`, "gim"),
new RegExp(`\\b(?!${keywords})\\b`, "gim"),
new RegExp(`\\b(${keywords})\\b`, "gim"),
/[^\x21-\x7F\n\r\s\t]+/gim,
/[\x21-\x7F]+/gim
*/!*
];куда можно вынести, в какой тег?
Что могли бы предложить?;)Ну и всякие фичи JS, где ключевое слово не всегда ключевое слово.Для начала я хочу попробовать просто мнемоники ассемблера подсветить.
vBulletin® v3.6.7, Copyright ©2000-2026, Jelsoft Enterprises Ltd. Перевод: zCarot