console
const inEnter = document.querySelector('#inEnter');
const inImg = document.querySelector('#inImg');
const inText = document.querySelector('#inText');
const edit = document.querySelector('.edit')
function replaceEmotion(
content,
srcMap,
replacer = (e) => {
return e;
}
) {
const pattern = /\[[\u4e00-\u9fa5_a-zA-Z]+\]/g;
let emotions = content.match(pattern);
emotions = Array.from(new Set(emotions))
let str = content;
str = str.replace(
pattern,
(s) => {
if (emotions.indexOf(s) > -1 && srcMap[s]) {
return replacer({ src: srcMap[s], alt: s })
} else {
return s
}
}
);
return str;
}
function editCacheRange(editNode) {
if (window.getSelection) {
const sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
editNode._cache_range = sel.getRangeAt(0);
}
} else if (document.selection && document.selection.createRange) {
editNode._cache_range = document.selection.createRange();
}
}
function editChangeCacheRange() {
editCacheRange(edit)
}
['keyup', 'focus', 'click'].forEach((key) => {
edit.removeEventListener(key, editChangeCacheRange)
edit.addEventListener(key, editChangeCacheRange)
})
function html2text(html) {
const enterRegExp = /<\/(p|div)>/g;
const altRegExp = /<img.*?alt="(.*?)">/ig;
const text = html.replace(enterRegExp, '\n').replace(altRegExp, "$1").replace(/<[^>]+>/g, '')
return text.trim()
}
const mgsFilters = (text) => {
const txt2imgMap = {
'[猫猫]': 'https://img.yzcdn.cn/vant/cat.jpeg'
}
return replaceEmotion(text, txt2imgMap, ({ src, alt }) => {
return `<img src="${src}" alt="${alt}" />`
})
}
function deleteEvent(e) {
if (e.keyCode !== 8 && e.keyCode !== 46) return
const isEmpty = !edit.innerHTML.replace(/<[^>]+>/g, '')
if (isEmpty) {
edit.innerHTML = ''
const br = document.createElement('p')
br.innerHTML = '<br>'
insertNodeAtCaret(edit, br, 1)
}
}
edit.removeEventListener('keydup', deleteEvent)
edit.addEventListener('keydup', deleteEvent)
function insertHtml(node, html) {
const text = mgsFilters(html2text(html))
const content = document.createElement('p')
content.innerHTML = text || '<br>'
edit.innerHTML = ''
node.appendChild(content)
}
function pasteEvent(e) {
if (!(e.clipboardData && e.clipboardData.items)) {
return;
}
const clipboardData = e.clipboardData
let pasteText = ''
if (clipboardData == null) {
pasteText = window.clipboardData && window.clipboardData.getData('text')
} else {
pasteText = clipboardData.getData('text/plain')
}
const text = mgsFilters(html2text(pasteText))
if (text) {
const content = document.createElement('p')
content.innerHTML = text
insertNodeAtCaret(edit, content)
}
e.preventDefault()
return false
}
edit.removeEventListener('paste', pasteEvent)
edit.addEventListener("paste", pasteEvent);
let mgsValue = ''
insertHtml(edit, mgsValue)
const showMsg = document.querySelector('#showMsg');
const showText = document.querySelector('#showText');
function setMsg(text) {
showText.innerHTML = text
showMsg.innerHTML = mgsFilters(text)
}
setMsg(mgsValue)
function mutationsFilter(mutations, tar) {
return mutations.filter(({ type, target, attributeName }) => {
return (
type != 'attributes' ||
(type == 'attributes' && (attributeName == 'contenteditable' || target != tar))
)
})
}
function observeNode(node, op = {}) {
const { onChange } = op;
const config = {
subtree: true,
childList: true,
attributes: true,
attributeOldValue: true,
characterData: true,
characterDataOldValue: true,
};
const _nodeObserver = new MutationObserver((mutationsList, observer) => {
onChange && onChange(mutationsList, observer);
});
_nodeObserver.observe(node, config);
return _nodeObserver;
}
observeNode(edit, {
onChange(mutationsList, observer) {
const text = html2text(edit.innerHTML)
setMsg(text)
editCacheRange(edit)
}
})
function insertNodeAtCaret(editNode, insertNode, isEnter, enterNodeName = 'P') {
let sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
}
if (!range && editNode._cache_range) {
range = editNode._cache_range
sel.removeAllRanges();
sel.addRange(range);
inEditRange = true
}
const editRange = document.createRange()
editRange.selectNode(editNode)
let inEditRange = range && editRange.compareBoundaryPoints(Range.START_TO_END, range) > -1 && range.endContainer != editNode;
if (!inEditRange) {
range = editRange
range.selectNodeContents(editNode)
if (editNode.lastChild) {
range.selectNodeContents(editNode.lastChild)
range.setStart(editNode.lastChild, 1);
range.setEnd(editNode.lastChild, 1);
inEditRange = true
}
}
range.deleteContents();
const isInText = range.endContainer.nodeName === '#text'
const isInEnter = inEditRange && range.endContainer.nodeName === enterNodeName.toUpperCase()
const rangeNodes = range.endContainer.childNodes
const justBr = range.endContainer.innerHTML === '<br>'
if (isEnter) {
if (isInText) {
const textLen = range.endContainer.textContent.length
const parentNodes = range.endContainer.parentNode.childNodes
const parentNodeLen = parentNodes.length
const index = [...parentNodes].indexOf(range.endContainer)
const isPEditNode = range.endContainer.parentNode === editNode
let endContainer = range.endContainer.parentNode
if (isPEditNode) {
endContainer = range.endContainer
}
if (textLen <= range.endOffset && index == parentNodeLen - 1) {
range.setStartAfter(endContainer);
} else if (range.endOffset === 0 && index === 0) {
range.setEndBefore(endContainer);
} else {
insertNode = document.createTextNode('\n')
}
} else if (isInEnter) {
const textLen = range.endContainer.textContent.length
if (!justBr && range.endOffset === 0) {
range.setEndBefore(range.endContainer);
} else if (!justBr && rangeNodes.length > range.endOffset) {
insertNode = document.createTextNode('\n')
} else {
range.setStartAfter(range.endContainer);
}
}
} else {
if (justBr) {
range.endContainer.innerHTML = '';
}
}
const isEmptyEditNode = !editNode.innerHTML
if (isEmptyEditNode) {
if (isEnter) {
range.insertNode(insertNode);
range.setStartAfter(insertNode);
insertNode = insertNode.cloneNode(true)
} else {
const _d = document.createElement(enterNodeName || 'p')
_d.appendChild(insertNode)
insertNode = _d
}
}
range.insertNode(insertNode);
if (isEnter || isEmptyEditNode) {
range.selectNodeContents(insertNode);
}
range.collapse(false);
sel.removeAllRanges();
sel.addRange(range);
} else if (document.selection && document.selection.createRange) {
}
}
inEnter.addEventListener('click', () => {
const br = document.createElement('p')
br.innerHTML = '<br>'
insertNodeAtCaret(edit, br, 1)
})
inText.addEventListener('click', () => {
const text = document.createTextNode('哈哈')
insertNodeAtCaret(edit, text)
})
inImg.addEventListener('click', () => {
const img = document.createElement('img');
img.src = 'https://img.yzcdn.cn/vant/cat.jpeg';
img.alt = '[猫猫]';
insertNodeAtCaret(edit, img)
})
<div contenteditable="true" tabindex="1" class="edit"></div>
<div class="cc">
<button id="inEnter">插入换行</button>
<button id="inText">插入哈哈</button>
<button id="inImg">插入图片</button>
</div>
<div>信息显示器:</div>
<div class="show-msg" id="showMsg"></div>
<div>纯文本:</div>
<div class="show-txt" id="showText"></div>
body {
background: #fff;
}
.edit {
width: 200px;
padding: 10px;
white-space: pre-wrap;
background-color: #f5f5f5
}
.edit:focus {
outline: none
}
.edit img {
vertical-align: text-bottom;
padding: 0 1px;
width: 50px;
height: 50px;
}
.edit p {
margin: 0;
}
.cc {
padding: 20px 0
}
.show-msg {
width: 200px;
min-height: 40px;
padding: 10px;
border-radius: 6px;
white-space: pre-wrap;
color: #fff;
background-color: #3EB575
}
.show-msg img {
vertical-align: text-bottom;
padding: 0 1px;
width: 50px;
height: 50px;
}
.show-txt {
width: 200px;
min-height: 40px;
padding: 10px;
border-radius: 6px;
white-space: pre-wrap;
color: #fff;
background-color: #999
}