Как переносить «висячие» слова на новую строку с помощью JavaScript
В типографике существует понятие “висячих слов” — это короткие слова (предлоги, союзы, местоимения), которые остаются в конце строки при переносе текста. Такие переносы нарушают удобочитаемость и эстетику текста. В русской типографике принято избегать переносов после коротких слов длиной 1-2 символа.
Решение проблемы с помощью JavaScript
Для автоматического предотвращения переносов после коротких слов можно использовать JavaScript, который заменяет обычные пробелы на неразрывные пробелы ( ) после определённых слов.
// Перенос висячих слов
document.addEventListener('DOMContentLoaded', function () {
// 1. Находим все текстовые элементы, которые нужно обработать
const textElements = document.querySelectorAll('p, span, h1, h2, h3, h4, h5, h6, li, dt, dd');
// 2. Список предлогов и союзов, которые нельзя переносить
const prepositions = ['в', 'без', 'до', 'из', 'к', 'на', 'по', 'о', 'от', 'перед', 'при', 'через', 'для', 'с', 'у', 'и', 'а', 'но', 'да', 'или', 'либо', 'что', 'чтобы', 'как', 'когда', 'если', 'вы'];
// 3. Функция для обработки каждого элемента
textElements.forEach(element => {
// Получаем все текстовые узлы внутри элемента
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
const textNodes = [];
while (walker.nextNode()) {
textNodes.push(walker.currentNode);
}
// Обрабатываем каждый текстовый узел
textNodes.forEach(textNode => {
let text = textNode.nodeValue;
// Заменяем пробелы после коротких слов на неразрывные пробелы
text = text.replace(/(^|\s)([а-яё]{1,2})\s/gi, (match, prefix, word) => {
// Проверяем, есть ли слово в списке предлогов
if (prepositions.includes(word.toLowerCase())) {
return prefix + word + '\u00A0'; // \u00A0 - это неразрывный пробел
}
return match;
});
// Обновляем содержимое текстового узла
textNode.nodeValue = text;
});
});
});
Разбор кода по частям
1. Инициализация скрипта
document.addEventListener('DOMContentLoaded', function () {
Код выполняется после полной загрузки DOM-дерева страницы. Это гарантирует, что все элементы будут доступны для обработки.
2. Поиск текстовых элементов
const textElements = document.querySelectorAll('p, span, h1, h2, h3, h4, h5, h6, li, dt, dd');
Скрипт находит все основные текстовые элементы на странице: параграфы, заголовки, элементы списков и другие. Вы можете расширить этот список, добавив другие селекторы.
3. Список коротких слов
const prepositions = ['в', 'без', 'до', 'из', 'к', 'на', 'по', 'о', 'от', 'перед', 'при', 'через', 'для', 'с', 'у', 'и', 'а', 'но', 'да', 'или', 'либо', 'что', 'чтобы', 'как', 'когда', 'если', 'вы'];
Массив содержит наиболее распространённые предлоги, союзы и другие служебные слова русского языка, после которых нежелательны переносы.
4. Обход текстовых узлов
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
TreeWalker позволяет пройти по всем текстовым узлам внутри элемента, включая вложенные теги. Это важно, так как текст может содержать форматирование (, и т.д.).
5. Регулярное выражение для замены
text = text.replace(/(^|\s)([а-яё]{1,2})\s/gi, (match, prefix, word) => {
Регулярное выражение ищет:
· (^|\s) — начало строки или пробел (группа 1);
· ([а-яё]{1,2}) — слово из 1-2 русских букв (группа 2);
· \s — пробел после слова;
· Флаги gi означают глобальный поиск без учёта регистра.
6. Условная замена
if (prepositions.includes(word.toLowerCase())) {
return prefix + word + '\u00A0'; // \u00A0 - это неразрывный пробел
}
Если найденное короткое слово есть в списке предлогов, обычный пробел заменяется на неразрывный пробел (\u00A0). Это предотвращает разрыв строки между предлогом и следующим словом.
Результат работы
После выполнения скрипта текст “Я иду в магазин” не будет разорван на строки как “Я иду в” и “магазин”. Предлог “в” всегда останется на одной строке со следующим словом благодаря неразрывному пробелу.
Настройка скрипта
Вы можете легко адаптировать скрипт под свои нужды:
· Добавить новые селекторы в querySelectorAll() для обработки других элементов;
· Расширить список слов в массиве prepositions;
· Изменить длину слов в регулярном выражении с {1,2} на другое значение;
· Добавить обработку других языков, изменив диапазон символов в регулярном выражении.
Этот простой скрипт значительно улучшает типографику русскоязычных веб-страниц, делая текст более читаемым и профессионально оформленным.
P.S.: это JS решение иногда не во всех случаях срабатывает + бывают сложности с адаптивностью: на смартфонах может переносить совсем не так, как хотелось бы, текст может «уехать» за пределы контейнера, нужно будет «фиксить» эти моменты в CSS.
Разработчики, а как вы решаете вопрос переноса «висячих» слов?
Подписывайся на мой телеграм-канал, чтобы узнать ещё больше полезных фишек верстки и веб-дизайна.
Qwen сгенерировала мне цитаты Говарда Лавкрафта, если бы он был преподавателем JavaScript
1. О приведении типов и `==`
«Не смейте бросать взгляд на оператор `==`, ибо в его бездне затеряны все законы логики. То, что числа и строки кажутся равными, — лишь обман, рожденный хаосом, что правит типами. Берегитесь, ибо даже `null == undefined` — это шепот древнего ужаса, пробуждающегося в вашем коде» .
2. О переменных без `const/let`
«Когда вы опускаетесь до использования необъявленных переменных, вы призываете тень хойстинга. Она ползет из глубин памяти, искажает реальность и порождает баги, коих не в силах победить даже самые опытные рыцари отладки» .
3. О рекурсии
«Рекурсия — это дверь в бесконечность. Каждый вызов функции уводит вас глубже в лабиринт стека, где время теряет смысл, а последний кадр станет вашей могилой. Не ищите выхода — он существует лишь в теории, как и надежда на `O(1)`» .
4. О `this` в JavaScript
«Сердце `this` — это черная дыра, поглощающая контекст. Пытаясь понять его, вы рискуете обнажить разум перед лицом космической случайности. Даже `bind()` лишь временная повязка на глаза, за которой скрывается истинная бездна» .
5. Об асинхронности
«Коли вы решитесь на `callback`, знайте: ваш код разорвется на части, как тело жертвы в пасти демона. Промисы же — это змеи, что шепчут вам: "Всё будет потом", но "потом" — это эпоха, где время не имеет смысла» .
Ответ l.smirnov в «Пояснительная бригада: получаем банан»2
Поздравляю, вы написали хуевый код на Джаве )
Никогда такого не было пиздеж, каждый первый спициалист с скиллбокса не даст соврать и вот опять!
Ну все, Джава мертва, старый унылый кусок говна, пора выкидывать, ведь тупой рандом написал на ней говно...
Но давайте, раз уж мы тут ниибать специалисты, подумаем еще 2 минуточки, над этим ниибически ахуительным тезисом "ахахха язык_название_1 полнае гавно".
Попробуем так сказать в анализ, логику, экстраполяцию даже, не побоюсь этого сложного слова.
Можно ли написать говно на ЛЮБОМ языке?
Отвечает эксперт(я): ага, ващще изи. Ну тоесть гдето сложновато, так как некоторые языки и их инструментарий будут упорно препятствовать, но бля - была бы цель!
Хммммм, а можно ли написать красиво на ЛЮБОМ языке?
Опять отвечает иксперт(я): ну, это конечто блять уметь надо, но вообще - да!
Все языки существуют для решения определённых проблем.
Некоторые узкоспециализированны, и красивые решения там требуют таки навыков, понимания области применения, и опыта.
Но - сделать красиво, и местами элегантно - можно на любом языке. Даже (прости госпади) на Руби.
Я уже устал расписывать что тот же JavaScript (над которым ржут тупорылые неумехи более десятка лет, и который конечно нонче более ускоспециализирован чем его, хммм, скажем для простоты терминов "старший брат" TypeScript) можно и "типизировать", и писать надежно, модульно, и даже красиво. БЫЛО БЫ УМЕНИЕ, ДА ЖЕЛАНИЕ. Да и php, второй по цитируемости в шуточкаж долбаебами язык - уже давно умеет и в типизацию, и вообще во все на свете. И позволяет создавать реально красивый код.
Но - поток идиотиков, сравнивающих вот эти пальцы (рдни языки) с жопами (другие языки), не разбирающихся ни в отм ни в другом - он блять неисчерпаем...
А по поводу конкретного примера из поста, на который я отвечаю - мне уже даже лень расписывать НАСКОЛЬКО чувак нихуя не понял ни в Джаве, ни в конкретно этой, простой в принципе иерархии классов, ни блять даже в самом ООП...
К чему я все это (ДА НЕ ПЕЧЕТ У МЕНЯ!11!!)?
Господа "шутники".
Имейте ввиду, что вы создаете негативный окрас языкам, в которых ничего не смыслите. Люди "снаружи" индустрии, да и просто те кто не очень разбирается - они же ж по этим шуточкам составят мнение, и понесут его в массы...
Вам не стыдно?
Ответ на пост «Пояснительная бригада: получаем банан»2
PHP и Java
PHP, сравни жопу с пальцем!
PHP: жопа это не палец.
Java, сравни жопу с пальцем!
Java: я не знаю, что такое жопа, и что такое палец. Подключите библиотеки классов жопа и палец.
Ок, подключаю!
Java: не могу подключить классы жопа и палец, они наследуют свойства и методы класса "тело", а этот класс не подключен.
Ок, подключаю!
Java: не могу создать классы жопа и палец, подключенный класс "тело" имеет версию эволюции 13.5.3, и не поддерживает классы жопа и палец.
Гуглю, пытаясь понять, чем различаются верси тела 20.3.8 и 20.3.9, и в чем различие женского и мужского тела. Ок, подключаю новую версию класса тело!
Java: чтобы сравнить, создайте объект тело с объектами жопа и палец. Не хватает классов "голова, руки, ноги"!
Гуглю, подключаю голову, руки, ноги.
Java: жопа это палец!
Почему!???!
Java: жопа = объект, палец = объект, соответственно жопа = палец!
Пояснительная бригада: получаем банан2
Комментирует редакция «Кода»:
У нас выражение на JavaScript. Начало переводится так: «Возьми букву b, соедини с буквой a, дальше…»
А дальше интересно. Смотрите на два плюсика в середине. Первый плюсик означает «сложи» или «соедини» — то же самое, что самый первый плюс в этом выражении.
А второй плюс — это унарный оператор, он преобразовывает следующую за ним строку в число. Следующая за ним строка — буква «a», это не число. Унарный оператор возвращает значение NaN — Not a Number, то есть «ожидал, что будет число, а это не число».
Получается baNaN.
Потом к этому добавляется ещё одна «a» по тому же принципу, что в начале. Получается baNaNa.
Дальше эту строку преобразовывают в нижний регистр с помощью .toLowerCase () и получается banana.
Подвох был в унарном операторе. То, что это именно он, со стороны не очевидно, потому что для нас это просто плюс. Но JavaScript смотрит на место этого плюса и понимает, что имелось в виду преобразование в число.