使用JavaScript切换特定文本无法按预期工作

Jon*_*nny 6 html javascript css editor toggle

我有一个切换按钮,可以改变一些文本.我遇到的问题是,如果我有2个单词,并且我想要更改一个单词的文本,但是当我切换它时,样式将从两个跨度而不是所选文本的跨度中删除.

如何从选定的特定文本中删除范围并将范围保留在其他文本上?

function headuppercase(e) {
  tags('span', 'sC');
}

function tags(tag, clas) {
  var ele = document.createElement(tag);
  ele.classList.add(clas);
  wrap(ele);
}

function wrap(tags) {
  var el = document.querySelector('span.sC');
  sel = window.getSelection();
  if (!el) {
    if (sel.rangeCount && sel.getRangeAt) {
      range = sel.getRangeAt(0);
    }
    document.designMode = "on";
    if (range) {
      sel.removeAllRanges();
      sel.addRange(range);
    }
    range.surroundContents(tags);
  } else {
    var parent = el.parentNode;
    while (el.firstChild) parent.insertBefore(el.firstChild, el);
    parent.removeChild(el);
  }
  document.designMode = "off";
}
Run Code Online (Sandbox Code Playgroud)
.ourbutton {
  padding: 5px;
  float: left;
  font-variant: small-caps;
}

.container {
  width: 200px;
  height: 300px;
  float: left;
}

.spanA {
  width: 100px;
  height: 80px;
  max-width: 200px;
  max-height: 300px;
  float: left;
  border: thin blue solid;
}

.sC {
  font-variant: small-caps;
}
Run Code Online (Sandbox Code Playgroud)
<button class="ourbutton" type="button" onclick="headuppercase();">Tt</button>
<div class="container">
  <span class="spanA" contenteditable="true"></span>
</div>
Run Code Online (Sandbox Code Playgroud)

请不要jQuery.谢谢!

Dam*_*aux 1

如果没有库,这并不容易实现,因为您需要处理所有边缘情况。我将首先讨论一些边缘情况,然后给您一个关于如何实现它们的示例。

简单案例

假设我们在文本节点中有以下字符串

“没有人想要太复杂的代码,因为它变得难以管理。”

考虑一个用户选择了“没人想要”一词并按下切换小型大写字母按钮。您最终应该得到如下所示的结果(其中粗体部分代表小型大写字母的文本):

没有人想要太复杂的代码,因为它变得难以管理。”

这是一个简单的案例。只需将“ Nobody Want ”段包装在 a 中<span>并给它sC上课即可。对于尚未成为小盘股的所有其他细分市场也是如此。这里没什么太难的。

边缘情况1

但是假设您处于以下状态(同样,粗体段代表小型大写字母的文本):

“没有人想要太复杂的代码,因为它变得难以管理。

当用户选择并切换“变成”这个词时,事情就变得复杂了。你必须:

  1. <span class="sC">从其包含元素中删除最后两个单词
  2. 在 span 之后添加单词“becomes”(来自步骤 1),并确保它不包含在<span>具有 className 的a 中sC
  3. 在文本 Node “becomes”(在步骤 2 中插入)之后的新元素中添加单词“unmanageable”。<span class="sC">

边缘情况2

或者说您处于以下状态(同样,粗体段代表小型大写字母的文本):

没有人想要复杂的代码,因为它变得难以管理。

当有人选择并切换该段“因为需要太复杂的代码”时会发生什么?有人可能会说:让这个片段中的每个角色

  • 小号大写字母:当不是时
  • 恢复正常:当前为小盘股时

很容易看出您将再次需要大量拆分现有的跨度元素创建新的文本节点等等。

边缘情况 N

假设您从嵌套列表开始

  • A
    • C
  • D

用户同时选择最后两项。然后您需要将每个项目单独包装到一个跨度中。

迈向解决方案的第一步

虽然您的问题尚不清楚应如何处理所有边缘情况,但这是解决方案的第一步。

const allCapsClass = 'sC'
function toggleCase() {
	const selection = window.getSelection()
  if (selection && selection.rangeCount>0) {
  	const spans = wrapInNonNestedSpanners(selection.getRangeAt(0))
    for (const span of spans) {
    	const classes = span.classList
    	const action = classes.contains(allCapsClass) ? 'remove' : 'add'
      classes[action](allCapsClass)
    }
  }
}


const spannerClassName = "non-nested-spanner"
const spannerQuerySelector = `.${spannerClassName}`

function wrapInNonNestedSpanners(range) {
	const containingSpanner = getContainingSpanner(range)
	const result = []
	if (containingSpanner != null) { // Edge case 1
			const endRange = document.createRange() // contents of the span after range
			endRange.selectNode(containingSpanner)
			endRange.setStart(range.endContainer, range.endOffset)
			const endContents = endRange.cloneContents()
			const wrappedSelectionContents = containingSpanner.cloneNode(false)
			wrappedSelectionContents.appendChild(range.cloneContents())
			endRange.deleteContents()
			range.deleteContents()
			const parent = containingSpanner.parentNode
			const next = containingSpanner.nextSibling
			parent.insertBefore(wrappedSelectionContents, next)
			result.push(wrappedSelectionContents)
			if (!isEmptySpanner(endContents.childNodes[0])) parent.insertBefore(endContents, next)
			if (isEmptySpanner(containingSpanner)) parent.removeChild(containingSpanner)
			const newSelection = document.createRange()
			newSelection.selectNode(wrappedSelectionContents)
			window.getSelection().removeAllRanges()
			window.getSelection().addRange(newSelection)
	} else { // Edge case 2
		const contents = range.extractContents()
		const spanners = contents.querySelectorAll(spannerQuerySelector)
		let endRange = document.createRange() // range before the span
		for (let index = spanners.length-1; index>=0; index--) {
			const spanner = spanners[index]
			endRange.selectNodeContents(contents)
			endRange.setStartAfter(spanner)
			if (!endRange.collapsed) {
				const wrappedEndContents = createSpannerWrapping(endRange.extractContents())
				range.insertNode(wrappedEndContents)
				result.unshift(wrappedEndContents)
			}
			range.insertNode(spanner)
			result.unshift(spanner)
		}
		const rest = createSpannerWrapping(contents)
		if (!isEmptySpanner(rest)) {
			range.insertNode(rest)
			result.unshift(rest)
		}
	}
	return result
}

function getContainingSpanner(range) {
	let cursor = range.commonAncestorContainer
	if (cursor.classList == undefined) cursor = cursor.parentElement
	while (cursor.parentElement != null) {
		if (cursor.classList.contains(spannerClassName)) return cursor
		cursor = cursor.parentElement
	}
	return null
}

function createSpannerWrapping(childNode) {
	const spanner = document.createElement('span')
	spanner.classList.add(spannerClassName)
	spanner.appendChild(childNode)
	return spanner
}

function isEmptySpanner(spanner) {
	if (spanner.childNodes.length == 0) return true
	else if (spanner.childNodes.length == 1) {
		const node = spanner.childNodes[0]
		return node instanceof Text && node.length == 0
	}
	return false
}
Run Code Online (Sandbox Code Playgroud)
.sC {
  font-variant: small-caps;
}
Run Code Online (Sandbox Code Playgroud)
<section contenteditable>
  Hello world this is some text
</section>
<button onclick="toggleCase()">Toggle case</button>
Run Code Online (Sandbox Code Playgroud)