Contenteditable – текстовый редактор

Если добавить атрибут contenteditable к элементу, его содержимое становится доступно для редактирования пользователю, а с использованием JS и JQuery можно сделать простой WYSIWYG редактор.

<div class="editor" contenteditable="true"></div>
HTML

JS-метод document.execCommand(сommand, showDefaultUI, аrgument) применяет команды к элементу с атрибутом contenteditable, который находится в фокусе. Т.к. нет единого стандарта, логика команд может отличатся в разных браузерах так и их поддержка, описание команд на https://developer.mozilla.org.

Проверить доступность команды можно методом queryCommandSupported(сommand).

var enable = document.queryCommandSupported('paste');
if (enable) {
	...
}
JS

Поддержка команд в браузере:

Жирный

Включает и отключает у выделенного текста жирное начертание тегом <b>, IE использует тег <strong>.

document.execCommand('bold', false, null);
JS

Курсив

Тег <i>, IE использует <em>.

document.execCommand('italic', false, null);
JS

Подчеркнутый

Тег <u>.

document.execCommand('underline', false, null);
JS

Перечёркнутый

Тег <strike>.

document.execCommand('strikethrough', false, null);
JS

Верхний индекс

Тег <sup>.

document.execCommand('superscript', false, null);
JS

Нижний индекс

Тег <sub>.

document.execCommand('subscript', false, null);
JS

Маркированный список

Тег <ul>.

document.execCommand('insertUnorderedList', false, null);
JS

Нумерованный список

Тег <ol>.

document.execCommand('insertOrderedList', false, null);
JS

Заголовки, параграф, цитата и т.д.

formatBlock добавляет блочный тег вокруг выделенного текста.

document.execCommand('formatBlock', false, 'h1');
document.execCommand('formatBlock', false, 'h2');
document.execCommand('formatBlock', false, 'h3');
document.execCommand('formatBlock', false, 'p');
document.execCommand('formatBlock', false, 'blockquote');
document.execCommand('formatBlock', false, 'div');
JS

Горизонтальная линия

Тег <hr>.

document.execCommand('insertHorizontalRule', false, null);
JS

Изображение

var url = prompt('Введите адрес изображения', '');
document.execCommand('insertImage', false, url);
JS

Ссылка

var url = prompt('Введите URL', '');
document.execCommand('CreateLink', false, url);
JS

Удаление ссылки:

document.execCommand('unlink', false, null);
JS

Вставка текста

var text = prompt('Введите текст', '');
document.execCommand('insertText', false, text);
JS

Вставка HTML

var html = prompt('Введите HTML код', '');
document.execCommand('insertHTML', false, html);
JS

С помощью insertHTML можно обернурнуть выделенный текст тегом, например <pre>:

document.execCommand('insertHTML', false, '<pre>' + document.getSelection().toString() + '</pre>');
JS

Выравнивание текста

Добавляет родительскому блоку CSS-свойство text-align.

По левому краю:

document.execCommand('justifyLeft', false, null);
JS

По центру:

document.execCommand('justifyCenter', false, null);
JS

По правому краю:

document.execCommand('justifyRight', false, null);
JS

По ширине:

document.execCommand('justifyFull', false, null);
JS

Шрифты

По умолчанию команды установки шрифта добавляются тегом <font> c атрибутами face, size, color, что не очень хорошо.

Команда styleWithCSS переключает режим, в место семантических тегов добавляются <span style="...">.

После установки шрифта нужно переключить styleWithCSS обратно т.к. он распространяется на другие теги (<b>, <i>, <s> и т.д.).

Название шрифта

document.execCommand('styleWithCSS', false, true);
document.execCommand('fontName', false, 'monospace');
document.execCommand('styleWithCSS', false, false);
JS

Размер:

Размер шрифта задаётся в условных единицах (1-7), в режиме styleWithCSS некоторые браузеры используют font-size: xx-small, x-small, small, medium, large, x-large, xx-large.

document.execCommand('styleWithCSS', false, true);
document.execCommand('fontSize', false, '5');
document.execCommand('styleWithCSS', false, false);
JS

Цвет:

document.execCommand('styleWithCSS', false, true);
document.execCommand('foreColor', false, '#eeeeee');
document.execCommand('styleWithCSS', false, false);
JS

Фон:

document.execCommand('styleWithCSS', false, true);
document.execCommand('hiliteColor', false, 'orange');
document.execCommand('styleWithCSS', false, false);
JS

Undo и Redo

Отмена последнего действия, Ctrl + z.

document.execCommand('undo', false, null);
JS

Повтор последнего действия, Ctrl + y.

document.execCommand('redo', false, null);
JS

Выделить всё

document.execCommand('selectAll', false, null);
JS

Удаление выделенного

document.execCommand('delete', false, null);
JS

Очистить стили

Удаляет инлайновые теги и атрибуты style, все блочные теги остаются.

document.execCommand('removeFormat', false, null);
JS

Вырезать

document.execCommand('cut', false, null);
JS

Копировать

document.execCommand('copy', false, null);
JS
<link href="/font-awesome.min.css" rel="stylesheet">
<script src="/jquery.min.js"></script>

<div class="toolbar">
	<a href="#" class="toolbar-b fas fa-bold" title="Жирный"></a>
	<a href="#" class="toolbar-i fas fa-italic" title="Курсив"></a>
	<a href="#" class="toolbar-u fas fa-underline" title="Подчёркнутый"></a>
	<a href="#" class="toolbar-s fas fa-strikethrough" title="Зачёркнутый"></a>
	<a href="#" class="toolbar-sup fas fa-superscript" title="Верхний индекс"></a>
	<a href="#" class="toolbar-sub fas fa-subscript" title="Нижний индекс"></a>
	<a href="#" class="toolbar-ul fas fa-list-ul" title="Маркированный список"></a>
	<a href="#" class="toolbar-ol fas fa-list-ol" title="Нумерованный список"></a>
	<a href="#" class="toolbar-p" title="Параграф">p</a>  
	<a href="#" class="toolbar-h1" title="Заголовок">H1</a>
	<a href="#" class="toolbar-hr" title="Горизонтальная линия">hr</a>    
	<a href="#" class="toolbar-blockquote fas fa-quote-right" title="Цитата"></a>
	<a href="#" class="toolbar-img far fa-image" title="Изображение"></a>
 	<a href="#" class="toolbar-a fas fa-link" title="Ссылка"></a>  
	<a href="#" class="toolbar-unlink fas fa-unlink" title="Удаление ссылки"></a>
	<a href="#" class="toolbar-html" title="Вставить html">HTML</a>  
	<a href="#" class="toolbar-text" title="Вставить текст">Text</a>
	<br>	
	<a href="#" class="toolbar-left fas fa-align-left" title="по левому краю"></a>
	<a href="#" class="toolbar-center fas fa-align-center" title="по центру"></a>
	<a href="#" class="toolbar-right fas fa-align-right" title="по правому краю"></a>
	<a href="#" class="toolbar-justify fas fa-align-justify" title="по ширине"></a>
	<select class="toolbar-font">
		<option selected="selected" disabled="disabled">Шрифт</option>
		<option value="arial">Arial</option>
		<option value="Courier New">Courier New</option>
		<option value="georgia">Georgia</option>
		<option value="impact">Impact</option>
		<option value="roboto">Tahoma</option>
		<option value="Times New Roman">Times New Roman</option>
		<option value="verdana">Verdana</option>
	</select>
	<select class="toolbar-size">
		<option selected="selected" disabled="disabled">Размер</option>
		<option value="1">10px</option>
		<option value="2">12px</option>
		<option value="3">14px</option>
		<option value="4">16px</option>
		<option value="5">18px</option>
		<option value="6">21px</option>
		<option value="7">26px</option>
	</select>
	<span>Цвет</span> <input class="toolbar-color" type="color" value="#ff0000">
	<span>Фон</span> <input class="toolbar-bg" type="color" value="#ffff00">
	<br>
	<a href="#" class="toolbar-undo fas fa-undo" title="Отмена"></a>
	<a href="#" class="toolbar-redo fas fa-redo" title="Повтор"></a>  		
	<a href="#" class="toolbar-delete far fa-trash-alt" title="Удалить"></a>
	<a href="#" class="toolbar-selectAll">Выделить всё</a>
	<a href="#" class="toolbar-removeFormat">Очистить стили</a>
	<a href="#" class="toolbar-cut fas fa-cut" title="Вырезать"></a>
	<a href="#" class="toolbar-copy fas fa-copy" title="Копировать"></a>
</div>

<div class="editor" contenteditable="true">...</div>
HTML
.toolbar a {
	display: inline-block;
	border: 1px solid #888;
	padding: 5px 8px;
	margin: 0 5px 10px 0;
	color: #000;
	border-radius: 3px;
	font-size: 12px;
	box-shadow: 1px 1px 2px #ddd;
	background: #fff;
	vertical-align: top;
	text-decoration: none;
}
.toolbar select {
	display: inline-block;
	height: 28px;
	line-height: 28px;
	background: #fff;
	padding: 0;
	margin: 0 5px 10px 0;
	color: #000;
	box-shadow: 1px 1px 2px #ddd;
	border-radius: 3px;
	vertical-align: top;
  	font-size: 12px;
}
.toolbar input {
	display: inline-block;
	height: 28px;
	line-height: 28px;
	background: #fff;
	padding: 0;
	margin: 0 5px 10px 0;
	color: #000;
	box-shadow: 1px 1px 2px #ddd;
	border-radius: 3px;
	vertical-align: top;
	font-size: 12px;
}
.toolbar span {
	display: inline-block;
	height: 30px;
	line-height: 30px;
	padding: 0;
	margin: 0 0 10px 0;
	color: #000;
	vertical-align: top;
	font-size: 12px;
}
.editor {
	min-height: 150px;
	border: 1px solid #ddd;
	padding: 10px;
	border-radius: 2px;
	box-shadow: 1px 1px 2px #ddd;
	background: #fff;
}
CSS
// Жирный (b)
$('body').on('click', '.toolbar-b', function(){
	document.execCommand('bold', false, null);
	return false;
});

// Курсив (i)
$('body').on('click', '.toolbar-i', function(){
	document.execCommand('italic', false, null);
	return false;
});

// Подчёркнутый текст (u)
$('body').on('click', '.toolbar-u', function(){
	document.execCommand('underline', false, null);
	return false;
});

// Зачёркнутый текст (strike)
$('body').on('click', '.toolbar-s', function(){
	document.execCommand('strikethrough', false, null);
	return false;
});

// Верхний индекс (sup)
$('body').on('click', '.toolbar-sup', function(){
	document.execCommand('superscript', false, null);
	return false;
});

// Нижний индекс (sub)
$('body').on('click', '.toolbar-sub', function(){
	document.execCommand('subscript', false, null);
	return false;
});

// Маркированный список (ul)
$('body').on('click', '.toolbar-ul', function(){
	document.execCommand('insertUnorderedList', false, null);
	return false;
});

// Нумерованный список (ol)
$('body').on('click', '.toolbar-ol', function(){
	document.execCommand('insertOrderedList', false, null);
	return false;
});

// Параграф (p)
$('body').on('click', '.toolbar-p', function(){
	document.execCommand('formatBlock', false, 'p');
	return false;
});

// Заголовок (h1)
$('body').on('click', '.toolbar-h1', function(){
	document.execCommand('formatBlock', false, 'h1');
	return false;
});

// Горизонтальная линия (hr) 
$('body').on('click', '.toolbar-hr', function(){
	document.execCommand('insertHorizontalRule', false, null);
	return false;
});

// Цитата (blockquote)
$('body').on('click', '.toolbar-blockquote', function(){
	document.execCommand('formatBlock', false, 'blockquote');
	return false;
});	

// Изображение (img)
$('body').on('click', '.toolbar-img', function(){
	var url = prompt('Введите адрес изображения', 'https://snipp.ru/demo/526/image.jpg');
	document.execCommand('insertImage', false, url);
	return false;
});

// Ссылка (a)
$('body').on('click', '.toolbar-a', function(){
	var url = prompt('Введите URL', '');
	document.execCommand('CreateLink', false, url);
	return false;
});

// Удаление ссылки
$('body').on('click', '.toolbar-unlink', function(){
	document.execCommand('unlink', false, null);
	return false;
});

// Вставить html
$('body').on('click', '.toolbar-html', function(){
	var html = prompt('Введите HTML код', '');
	document.execCommand('insertHTML', false, html);
	return false;
});

// Вставить текст
$('body').on('click', '.toolbar-text', function(){
	var text = prompt('Введите текст', '');
	document.execCommand('insertText', false, text);
	return false;
});

// Выравнивание текста по левому краю
$('body').on('click', '.toolbar-left', function(){
	document.execCommand('justifyLeft', false, null);
	return false;
});

// Выравнивание текста по центру
$('body').on('click', '.toolbar-center', function(){
	document.execCommand('justifyCenter', false, null);
	return false;
});

// Выравнивание текста по правому краю
$('body').on('click', '.toolbar-right', function(){
	document.execCommand('justifyRight', false, null);
	return false;
});

// Выравнивание по ширине
$('body').on('click', '.toolbar-justify', function(){
	document.execCommand('justifyFull', false, null);
	return false;
});

// Шрифт
$('body').on('input', '.toolbar-font', function(){
	var val = $(this).val();
	document.execCommand('styleWithCSS', false, true);
	document.execCommand('fontName', false, val);
	document.execCommand('styleWithCSS', false, false);
});

// Размер шрифта
$('body').on('input', '.toolbar-size', function(){
	var val = $(this).val();
	document.execCommand('styleWithCSS', false, true);
	document.execCommand('fontSize', false, val);
	document.execCommand('styleWithCSS', false, false);
});

// Цвет шрифта
$('body').on('input', '.toolbar-color', function(){
	var val = $(this).val();
	document.execCommand('styleWithCSS', false, true);
	document.execCommand('foreColor', false, val);
	document.execCommand('styleWithCSS', false, false);
});

// Цвет фона
$('body').on('input', '.toolbar-bg', function(){
	var val = $(this).val();
	document.execCommand('styleWithCSS', false, true);
	document.execCommand('hiliteColor', false, val);
	document.execCommand('styleWithCSS', false, false);
});

// Отмена
$('body').on('click', '.toolbar-undo', function(){
	document.execCommand('undo', false, null);
	return false;
});

// Повтор
$('body').on('click', '.toolbar-redo', function(){
	document.execCommand('redo', false, null);
	return false;
});

// Удалить
$('body').on('click', '.toolbar-delete', function(){
	document.execCommand('delete', false, null);
	return false;
});

// Выделить всё
$('body').on('click', '.toolbar-selectAll', function(){
	document.execCommand('selectAll', false, null);
	return false;
});

// Очистить стили
$('body').on('click', '.toolbar-removeFormat', function(){
	document.execCommand('removeFormat', false, null);
	return false;
});

// Вырезать
$('body').on('click', '.toolbar-cut', function(){
	document.execCommand('cut', false, null);
	return false;
});

// Копировать
$('body').on('click', '.toolbar-copy', function(){
	document.execCommand('copy', false, null);
	return false;
});
JS

В большинстве браузерах при нажатии на Enter, новая строка начнется с преведущего блочного элемента (p, li, blockquote).

Если редактор был пуст, то вставится <div>, в место него можно использовать <p> вызвав команду:

document.execCommand('defaultParagraphSeparator', false, 'p');
JS

Если редактору сделать display: inline-block, то будут вставляться только <br>.

В contenteditable клавиша Tab не добавляет отступ, а переключает фокус на следующий элемент. Включить табуляцию можно, добавив CSS-свойство white-space: pre-wrap чтобы начали выводится пробельные символы, но при этом переносы строк тоже заработают.

.editor {
	white-space: pre-wrap;
	tab-size: 3; /* Ширина табуляции */
}
CSS

И обработчик нажатия клавиши на JQuery:

$('body').on('keydown', '.editor', function(e){
	if (e.keyCode === 9) { 
		e.preventDefault();
		var editor = this;
		var doc = editor.ownerDocument.defaultView;
		var sel = doc.getSelection();
		var range = sel.getRangeAt(0);
		var tabNode = document.createTextNode("\t");
		range.insertNode(tabNode);
		range.setStartAfter(tabNode);
		range.setEndAfter(tabNode); 
		sel.removeAllRanges();
		sel.addRange(range);
	}		
});
JS

Результат:

При вставки текста из буфера переносятся все стили и теги скопированные из HTML страницы или Word файла, это можно предотвратить очисткой:

function escape_text(text) { 
	var map = {'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;'};
	return text.replace(/[&<>"']/g, function(m) {
		return map[m];
	});
}

$('body').on('paste', '.editor', function(e){
	e.preventDefault();
	var text = (e.originalEvent || e).clipboardData.getData('text/plain');
	document.execCommand('insertHtml', false, escape_text(text));
});
JS
<div class="editor" contenteditable="true" placeholder="Введите текст"></div>
HTML
.editor[placeholder]:empty:before {
	content: attr(placeholder);
	color: #555; 
}
.editor[placeholder]:empty:focus:before {
	content: '';
}
CSS
$('body').on('focusout', '.editor', function() {
	var element = $(this);        
	if (!element.text().replace(' ', '').length) {
		element.empty();
	}
});
JS

Показать исходный код можно заменив <div> на <textarea>.

<input type="checkbox" id="toggle-editor"> Показать исходный код

<div contenteditable="true" class="editor">
	...
</div>
HTML
textarea.editor {
	display: block;
	width: 100%;
	box-sizing: border-box;
	white-space: pre-wrap;
}
CSS
$('#toggle-editor').click(function(){
	var target = $('.editor');
	if ($(this).is(':checked')){
		target.replaceWith('<textarea class="editor">' + target.html() + '</textarea>');
	} else {
		target.replaceWith('<div contenteditable="true" class="editor">' + target.val() + '</div>');
	}
});
JS

Для отправки текста вместе с формой нужно добавить скрытый <textarea>, при событии отправки формы скидывать в него содержимое редактора.

<form id="form" method="post" action="">
	<div class="editor" contenteditable="true">...</div>
	<textarea id="textarea" name="text" style="dispalay: none;"></textarea>
	<input type="supmit" value="Отправить">
</form>
HTML
$('#form').on('submit', function(){
	$('#textarea').val($('.editor').html());
	return true;
});
JS
12.12.2019, обновлено 09.10.2020
28368
Предыдущая запись Модальные окна на Fancybox 3

Комментарии 4

Андрей Мирный Андрей Мирный
10 февраля 2021 в 02:33
0
Какой раз выручат данный сайт, большое спасибо автору.
Особенно за 5 пункт.
володя суручан володя суручан
23 ноября 2021 в 11:38
0
Ваш сайт очень крут штобы я делал без вас
Андрей Мирный Андрей Мирный
6 декабря 2021 в 10:54
0
Может кто ни будь подскажет как можно использовать свои модальные окна, вместо prompt?
володя суручан володя суручан
12 февраля 2022 в 12:46
0
Через document.createElement

, чтобы добавить комментарий.

Другие публикации

Как преобразовать текст из textarea в параграфы HTML
Такой вопрос возникает при вставке текста из формы на сайт (отзывы, комментарии и т.д.) с форматированием элементом p.
4137
+1
Получить выделенный текст из текстового поля
У текстовых полей (textarea, input text и т.д.) есть JS-свойства selectionStart и selectionEnd, которые возвращают...
4419
+2
Обернуть выделенный текст тегами в textarea
С помощью JS-свойств selectionStart и selectionEnd можно сделать оборачивание выделенного текста тегами или BB-кодами.
4272
+6
Изменение размеров textarea и других элементов
В CSS 3 появилось свойство resize, которе указывает, можно ли пользователю изменять размеры текстового поля и других...
11939
+1
Очистка данных из форм в PHP
Для предотвращения XSS, SQL-инъекций и других атак, данные полученные из форм нужно чистить. Простое экранирования кавычек не достаточно, нужен комплексный подход по типу данных.
7800
+4
Манипуляции с элементами jQuery
Сборник методов jQuery для управления элементами DOM.
12571
+6