Красивый алгоритм для перемешивания букв в словах (и не только)
medium

Красивый алгоритм для перемешивания букв в словах (и не только)

Расширенная веб-версия простого проекта

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

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

  • Попрактиковаться в стилях и оформлении страницы.
  • Попробовать необычные псевдоклассы в CSS.
  • Поработать с анимацией.
  • Испытать на практике самовызывающиеся функции.

Логика проекта

Вот как это будет работать:

  1. Когда пользователь заходит на страницу, он видит перемешивающиеся буквы, которые складываются в текст.
  2. Пару секунд спустя появляется приглашение попробовать и ниже — поле ввода.
  3. Пользователь вводит свой текст и нажимает энтер.
  4. Алгоритм перемешивает буквы в словах и выдаёт анимированный результат.
  5. Так происходит каждый раз, когда пользователь вводит свой текст и нажимает энтер.

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

Готовим страницу

Из основных элементов на странице нам нужно разместить только блок с результатом и поле ввода, всё остальное — внешние стили и скрипты. Ещё подключим дополнительный шрифт, чтобы было красивее, и jQuery, чтобы было проще работать с элементами на странице:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Перемешиваем буквы в словах</title>
        
        <!-- подключаем свои стили и шрифт -->
        <link rel="stylesheet" href="styles.css" />
        <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Open+Sans+Condensed:300" type="text/css" />
    </head>
    
    <body>
		
        <!-- тут будет готовый результат -->
		<div id="container">Перемешиваем буквы</div>

        <!-- поле ввода текста, который нужно перемешать-->
		<input type="text" id="userText" />

        <!-- подключаем jQuery и скрипты -->
		<script src="https://code.jquery.com/jquery-3.7.1.js"></script>
		<script src="jquery.shuffleLetters.js"></script>
        <script src="script.js"></script>
           
    </body>
</html>
Красивый алгоритм для перемешивания букв в словах (и не только)

Настраиваем внешний вид страницы

Теперь создадим файл styles.css и настроим общий вид страницы. Начнём с глобальных настроек и стиля блока с итоговым результатом:

/* убираем везде отступы */
*{
	margin:0;
	padding:0;
}

/* общие настройки для страницы */
body{
	/* фон и отступ */
	color:#fff;
	padding:10px;
	/* минимальная высота */
	min-height:600px;
	/* настройки шрифта */
	font:14px/1.3 'Segoe UI',Arial, sans-serif;
}

/* стили блока с результатом */
#container{
	/* цвет текста */
	color: #555;
	/* размер шрифта */
	font-size: 58px;
	/* отступы */
	margin: 0 auto;
	padding: 200px 0 100px;
	/* ширина блока */
	width: 650px;
	/* включаем относительное позиционирование */
	position:relative;
	/* минимальная высота */
	min-height: 90px;
	/* настройки шрифта */
	font-family:'Open Sans Condensed',sans-serif;
	text-shadow:1px 1px 0 rgba(255,255,255,0.5);
}
Красивый алгоритм для перемешивания букв в словах (и не только)

Теперь добавим в начало итогового блока стрелку — пусть она показывает начало текста, чтобы он не висел в пустоте. Для этого используем псевдоэлемент :before — он отвечает за контент и настройки стилей до выбранного элемента:

/* добавляем указатель-стрелку к блоку с результатом */
#container:before{
	/* стрелка */
	content: ">";
	/* размер шрифта */
	font-size: 50px;
	/* сдвигаем левее */
	left: -40px;
	/* прозрачность */
	opacity: 0.25;
	/* включаем абсолютное позиционирование */
	position: absolute;
	/* добавляем тень */
	text-shadow: 1px 1px 0 white;
	top: 210px;
}
Красивый алгоритм для перемешивания букв в словах (и не только)

Финальный штрих в оформлении — сделаем красивое поле для ввода пользовательского текста. По стилям будет всё то же самое: выравниваем по центру, убираем лишнее и настраиваем шрифт:

/* поле ввода текста */
#userText{
	/* убираем фон и границы поля ввода, оставляем только нижнюю линию */
	background:none;
	border:none;
	border-bottom:1px solid #aaa;
    outline: none;
	
	/* цвет текста */
	color: #777777;
    display: block;
    /* настройки шрифта */
    font-family: 'Open Sans Condensed',sans-serif;
    font-size: 20px;
    /* отступы */
    margin: 0 auto 0px;
    padding: 10px;
    /* выравнивание текста */
    text-align: center;
    /* ширина блока */
    width: 300px;
}
Красивый алгоритм для перемешивания букв в словах (и не только)

Разбираем скрипт анимации перемешивания 

Чтобы не городить всё в одну кучу, разобьём скрипты на два файла: в одном будет скрипт с анимацией перемешивания букв, в другом — механика всей страницы.

Для первой части задачи про анимацию букв мы возьмём скрипт Мартина Ангелова jquery.shuffleLetters.js, немного его упростим и прокомментируем в нём каждый шаг, чтобы было понятно, что там происходит.

Весь этот скрипт — это одна самовызывающаяся функция, которая запускается сразу, как только браузер подключает файл с этим скриптом. Но хитрость в том, что это просто обёртка для внутренней функции shuffleLetters — автозапуск просто загружает её в память и позволяет к ней обратиться в любой момент. Это нам пригодится в основном скрипте.

Также внутри этой функции есть ещё одна функция с автозапуском — shuffle(). В ней происходит вся магия анимации: она несколько раз показывает произвольные символы и следит за тем, чтобы анимация была плавной и равномерной. Она срабатывает, как только в родительском скрипте браузер доходит до объявления этой функции. 

В остальном логика простая: берём символ, несколько раз вместо него показываем что-то другое, а финальным шагом ставим правильный символ и переходим к следующему. Как только счётчик символов в строке скажет, что мы всё обработали, — анимация останавливается. Читайте комментарии, чтобы лучше вникнуть в процесс:

// весь скрипт — это одна самовызывающаяся функция
(function($){
	// название функции
	$.fn.shuffleLetters = function(prop){
		// параметры
		var options = $.extend({
			// сколько раз поменяется каждый символ
			"step"		: 8,			
			// сколько кадров в секунду нужно отображать при анимации
			"fps"		: 25,			
			// заглушка для пустого поля ввода
			"text"		: "", 			
			// при повторном вызове делаем всё то же самое
			"callback"	: function(){}	
		},prop)
		
		// результат работы функции
		return this.each(function(){
			// отсюда будем брать текст — передаём в функцию ссылку на поле ввода
			var el = $(this),
				// строка, которую нужно обработать, на старте пустая
				str = "";
			// если на странице уже есть что перемешивать — делим строку на слова
			if(options.text) { str = options.text.split(''); }
			// иначе делим на слова то, что ввёл пользователь
			else { str = el.text().split(''); }
			
			// переменные для типа символов и их позиции в слове
			var types = [],
				letters = [];

			// перебираем все символы в строке
			for(var i=0;i<str.length;i++){
				// берём очередной символ
				var ch = str[i];
				// если это пробел — помечаем как пробел
				if(ch == " "){ 
					types[i] = "space";
					continue;
				}
				// если это маленькие буквы
				else if(/[а-я]/.test(ch)){ types[i] = "lowerLetter"; }
				// если большие
				else if(/[А-Я]/.test(ch)){ types[i] = "upperLetter"; }
				// всё остальное — символы
				else { types[i] = "symbol"; }
				// запоминаем позицию текущего символа
				letters.push(i);
			}
			// очищаем поле ввода
			el.html("");			

			// функция, которая выполняется сразу после создания
			// на вход она получает номер текущего символа для анимации
			(function shuffle(start){
			
					
				var i,
					// количество букв для анимации
					len = letters.length, 
					// получаем копию оригинальной строки, чтобы с ней дальше работать
					strCopy = str.slice(0);	
				// если обработали все символы — выходим из функции
				if(start>len){ return; }
				
				// перебираем все символы
				for(i=Math.max(start,0); i < len; i++){

					// если символ ещё не поменялся нужное количество раз					
					if( i < start+options.step){
						// выбираем случайный и ставим его на это место
						strCopy[letters[i]] = randomChar(types[letters[i]]);
					}
					// в противном случае ставим туда пустой символ — признак того, что мы обработали этот символ
					else {strCopy[letters[i]] = "";}
				}
				// оставляем обработанные символы на своём месте в итоговом блоке
				el.text(strCopy.join(""));
				
				// готовим анимацию смены букв
				setTimeout(function(){
					// увеличиваем счётчик использованных букв на единицу
					shuffle(start+1);
				// держим букву на экране нужное количество времени
				},1000/options.fps);
			// уменьшаем количество обработанных в анимации букв
			})(-options.step);
			

		});
	};
	
	// выбираем, что показывать во время перемешивания
	function randomChar(type){
		var pool = "";
		// если это маленькая буква — берём все маленькие и цифры
		if (type == "lowerLetter"){
			pool = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя0123456789";
		}
		// если это большие — большие и цифры
		else if (type == "upperLetter"){
			pool = "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ0123456789";
		}
		// иначе берём только символы
		else if (type == "symbol"){
			pool = ",.?/\\(^)![]{}*&^%$#'\"";
		}
		// делим выборку по символам и выбираем оттуда что-то случайным образом
		var arr = pool.split('');
		return arr[Math.floor(Math.random()*arr.length)];
	}
})(jQuery);

Собираем основной скрипт

Создаём файл script.js и сразу добавляем в него код из прошлого проекта, который просто перемешивает буквы в словах и отдаёт новую строку:

// перемешиваем буквы в словах
function shuffelWord(word) {
  // делим слово на отдельные буквы
  word_spl = word.split('');

  // убираем первую и последнюю буквы
  let first = word_spl.shift();
  let last = '';
  // если слово состоит больше чем из одной буквы — запоминаем последнюю
  if (word.length > 1) {last = word_spl.pop()}

  // перемешиваем оставшиеся буквы
  for (let i = word_spl.length - 1; i > 0; i--) {
    // берём случайную букву
    let j = Math.floor(Math.random() * (i + 1));
    // меняем её местами с текущей
    [word_spl[i], word_spl[j]] = [word_spl[j], word_spl[i]];
  }
  // собираем и возвращаем слово
  return first + word_spl.join("") + last;
}

Теперь сделаем основной скрипт с такой логикой:

  1. Получаем доступ к элементам на странице.
  2. Сразу запускаем анимацию приветствия — к этому моменту у нас уже загружена функция shuffleLetters.
  3. Ждём 2 секунды и меняем приветственный текст. Его тоже анимируем.
  4. Параллельно с этим добавляем обработчик нажатия на энтер в поле ввода — так мы научим страницу реагировать на действия пользователя.
  5. Как только началась анимация пользовательского текста — очищаем поле ввода, чтобы оно не повлияло на анимацию.

// основная функция
$(function(){
	
	// получаем доступ к элементам на странице
	var container = $("#container")
		userText = $('#userText'); 
	
	// выводим первую анимацию перемешанных букв в блоке с результатом
	container.shuffleLetters();

	// очищаем поле ввода по клику на нём
	userText.click(function () {
	  userText.val("");

	// добавляем обработчик нажатия на энтер
	}).bind('keypress',function(e){
		// если нажали энтер
		if(e.keyCode == 13){

			// разбиваем строку на слова
			arr = userText.val().split(' ');
			// тут будет финальный результат
			var result = '';
			// перебираем все слова в тексте
			for (let i = 0; i < arr.length; i++) {
			  // перемешиваем буквы и добавляем новое слово в итоговый текст
			  result = result + shuffelWord(arr[i]) + ' ';
			}
			
			// выводим анимацию перемешанных букв 
			container.shuffleLetters({
				"text": result
			});
			// очищаем поле ввода текста
			userText.val("");
		}
	// скрываем поле ввода, пока не закончилась первая анимация
	}).hide();

	// ждём 2 секунды
	setTimeout(function(){
		// выводим приветственную надпись
		container.shuffleLetters({
			"text": "Теперь попробуйте сами"
		});
		// добавляем подсказку в поле ввода
		userText.val("Введите текст и нажмите enter..").fadeIn();
	},2000);
	
});

Собираем всё вместе, обновляем страницу и видим, как всё работает:

Красивый алгоритм для перемешивания букв в словах (и не только)

Посмотреть анимацию на странице проекта

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Перемешиваем буквы в словах</title>
        
        <!-- подключаем свои стили и шрифт -->
        <link rel="stylesheet" href="styles.css" />
        <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Open+Sans+Condensed:300" type="text/css" />
    </head>
    
    <body>
		
        <!-- тут будет готовый результат -->
		<div id="container">Перемешиваем буквы</div>

        <!-- поле ввода текста, который нужно перемешать-->
		<input type="text" id="userText" />

        <!-- подключаем jQuery и скрипты -->
		<script src="https://code.jquery.com/jquery-3.7.1.js"></script>
		<script src="jquery.shuffleLetters.js"></script>
        <script src="script.js"></script>
           
    </body>
</html>

/* убираем везде отступы */
*{
	margin:0;
	padding:0;
}

/* общие настройки для страницы */
body{
	/* фон и отступ */
	color:#fff;
	padding:10px;
	/* минимальная высота */
	min-height:600px;
	/* настройки шрифта */
	font:14px/1.3 'Segoe UI',Arial, sans-serif;
}

/* стили блока с результатом */
#container{
	/* цвет текста */
	color: #555;
	/* размер шрифта */
	font-size: 58px;
	/* отступы */
	margin: 0 auto;
	padding: 200px 0 100px;
	/* ширина блока */
	width: 650px;
	/* включаем относительное позиционирование */
	position:relative;
	/* минимальная высота */
	min-height: 90px;
	/* настройки шрифта */
	font-family:'Open Sans Condensed',sans-serif;
	text-shadow:1px 1px 0 rgba(255,255,255,0.5);
}

/* добавляем указатель-стрелку к блоку с результатом */
#container:before{
	/* стрелка */
	content: ">";
	/* размер шрифта */
	font-size: 50px;
	/* сдвигаем левее */
	left: -40px;
	/* прозрачность */
	opacity: 0.25;
	/* включаем абсолютное позиционирование */
	position: absolute;
	/* добавляем тень */
	text-shadow: 1px 1px 0 white;
	top: 210px;
}

/* поле ввода текста */
#userText{
	/* убираем фон и границы поля ввода, оставляем только нижнюю линию */
	background:none;
	border:none;
	border-bottom:1px solid #aaa;
    outline: none;
	
	/* цвет текста */
	color: #777777;
    display: block;
    /* настройки шрифта */
    font-family: 'Open Sans Condensed',sans-serif;
    font-size: 20px;
    /* отступы */
    margin: 0 auto 0px;
    padding: 10px;
    /* выравнивание текста */
    text-align: center;
    /* ширина блока */
    width: 300px;
}

// перемешиваем буквы в словах
function shuffelWord(word) {
  // делим слово на отдельные буквы
  word_spl = word.split('');

  // убираем первую и последнюю буквы
  let first = word_spl.shift();
  let last = '';
  // если слово состоит больше чем из одной буквы — запоминаем последнюю
  if (word.length > 1) {last = word_spl.pop()}

  // перемешиваем оставшиеся буквы
  for (let i = word_spl.length - 1; i > 0; i--) {
    // берём случайную букву
    let j = Math.floor(Math.random() * (i + 1));
    // меняем её местами с текущей
    [word_spl[i], word_spl[j]] = [word_spl[j], word_spl[i]];
  }
  // собираем и возвращаем слово
  return first + word_spl.join("") + last;
}


// основная функция
$(function(){
	
	// получаем доступ к элементам на странице
	var container = $("#container")
		userText = $('#userText'); 
	
	// выводим первую анимацию перемешанных букв в блоке с результатом
	container.shuffleLetters();

	// очищаем поле ввода по клику на нём
	userText.click(function () {
	  userText.val("");

	// добавляем обработчик нажатия на энтер
	}).bind('keypress',function(e){
		// если нажали энтер
		if(e.keyCode == 13){

			// разбиваем строку на слова
			arr = userText.val().split(' ');
			// тут будет финальный результат
			var result = '';
			// перебираем все слова в тексте
			for (let i = 0; i < arr.length; i++) {
			  // перемешиваем буквы и добавляем новое слово в итоговый текст
			  result = result + shuffelWord(arr[i]) + ' ';
			}
			
			// выводим анимацию перемешанных букв 
			container.shuffleLetters({
				"text": result
			});
			// очищаем поле ввода текста
			userText.val("");
		}
	// скрываем поле ввода, пока не закончилась первая анимация
	}).hide();

	// ждём 2 секунды
	setTimeout(function(){
		// выводим приветственную надпись
		container.shuffleLetters({
			"text": "Теперь попробуйте сами"
		});
		// добавляем подсказку в поле ввода
		userText.val("Введите текст и нажмите enter..").fadeIn();
	},2000);
	
});

// весь скрипт — это одна самовызывающаяся функция
(function($){
	// название функции
	$.fn.shuffleLetters = function(prop){
		// параметры
		var options = $.extend({
			// сколько раз поменяется каждый символ
			"step"		: 8,			
			// сколько кадров в секунду нужно отображать при анимации
			"fps"		: 25,			
			// заглушка для пустого поля ввода
			"text"		: "", 			
			// при повторном вызове делаем всё то же самое
			"callback"	: function(){}	
		},prop)
		
		// результат работы функции
		return this.each(function(){
			// отсюда будем брать текст — передаём в функцию ссылку на поле ввода
			var el = $(this),
				// строка, которую нужно обработать, на старте пустая
				str = "";
			// если на странице уже есть что перемешивать — делим строку на слова
			if(options.text) { str = options.text.split(''); }
			// иначе делим на слова то, что ввёл пользователь
			else { str = el.text().split(''); }
			
			// переменные для типа символов и их позиции в слове
			var types = [],
				letters = [];

			// перебираем все символы в строке
			for(var i=0;i<str.length;i++){
				// берём очередной символ
				var ch = str[i];
				// если это пробел — помечаем как пробел
				if(ch == " "){ 
					types[i] = "space";
					continue;
				}
				// если это маленькие буквы
				else if(/[а-я]/.test(ch)){ types[i] = "lowerLetter"; }
				// если большие
				else if(/[А-Я]/.test(ch)){ types[i] = "upperLetter"; }
				// всё остальное — символы
				else { types[i] = "symbol"; }
				// запоминаем позицию текущего символа
				letters.push(i);
			}
			// очищаем поле ввода
			el.html("");			

			// функция, которая выполняется сразу после создания
			// на вход она получает номер текущего символа для анимации
			(function shuffle(start){
			
					
				var i,
					// количество букв для анимации
					len = letters.length, 
					// получаем копию оригинальной строки, чтобы с ней дальше работать
					strCopy = str.slice(0);	
				// если обработали все символы — выходим из функции
				if(start>len){ return; }
				
				// перебираем все символы
				for(i=Math.max(start,0); i < len; i++){

					// если символ ещё не поменялся нужное количество раз					
					if( i < start+options.step){
						// выбираем случайный и ставим его на это место
						strCopy[letters[i]] = randomChar(types[letters[i]]);
					}
					// в противном случае ставим туда пустой символ — признак того, что мы обработали этот символ
					else {strCopy[letters[i]] = "";}
				}
				// оставляем обработанные символы на своём месте в итоговом блоке
				el.text(strCopy.join(""));
				
				// готовим анимацию смены букв
				setTimeout(function(){
					// увеличиваем счётчик использованных букв на единицу
					shuffle(start+1);
				// держим букву на экране нужное количество времени
				},1000/options.fps);
			// уменьшаем количество обработанных в анимации букв
			})(-options.step);
			

		});
	};
	
	// выбираем, что показывать во время перемешивания
	function randomChar(type){
		var pool = "";
		// если это маленькая буква — берём все маленькие и цифры
		if (type == "lowerLetter"){
			pool = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя0123456789";
		}
		// если это большие — большие и цифры
		else if (type == "upperLetter"){
			pool = "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ0123456789";
		}
		// иначе берём только символы
		else if (type == "symbol"){
			pool = ",.?/\\(^)![]{}*&^%$#'\"";
		}
		// делим выборку по символам и выбираем оттуда что-то случайным образом
		var arr = pool.split('');
		return arr[Math.floor(Math.random()*arr.length)];
	}
})(jQuery);

Текст:

Михаил Полянин

Редактор:

Максим Ильяхов

Художник:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Кирилл Климентьев

Соцсети:

Аня Соколова

Фронтенд-разработка — востребованная профессия
На новом курсе «Практикума» о фронтенде вас обучат самым востребованным технологиям: JS и TypeScript, Flexbox и Grid, React, Git, Bash и др. Это то, что нужно работодателям сегодня. Старт — бесплатно.
Попробовать бесплатно
Фронтенд-разработка — востребованная профессия Фронтенд-разработка — востребованная профессия Фронтенд-разработка — востребованная профессия Фронтенд-разработка — востребованная профессия
Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Начать карьеру в ИТ
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Еще по теме
medium