简体   繁体   English

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

[英]Toggle a specific Text with JavaScript not working as expected

I have a toggle button that changes a bit of text. 我有一个切换按钮,可以改变一些文本。 The problem I run into is if I have 2 words and I want to change the text of one it changes one but when I toggle it off style is removed from both spans instead of the span of the selected text. 我遇到的问题是,如果我有2个单词,并且我想要更改一个单词的文本,但是当我切换它时,样式将从两个跨度而不是所选文本的跨度中删除。

How can I remove the span from the specific text selected and leave the span on the other text? 如何从选定的特定文本中删除范围并将范围保留在其他文本上?

 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"; } 
 .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; } 
 <button class="ourbutton" type="button" onclick="headuppercase();">Tt</button> <div class="container"> <span class="spanA" contenteditable="true"></span> </div> 

No jQuery please. 请不要jQuery。 Thanks You! 谢谢!

The issue is with this document.querySelector('span.sC') . 问题出在这个document.querySelector('span.sC') In all the case it will select the first span with sC which is not good as you have to deal with the current one. 在所有情况下,它将选择带有sC的第一个跨度,这是不好的,因为你必须处理当前的跨度。

Here is an idea of fix: 这是一个修复的想法:

 function headuppercase(e) { tags('span', 'sC'); } function tags(tag, clas) { var ele = document.createElement(tag); ele.classList.add(clas); wrap(ele); } function wrap(tags) { sel = window.getSelection(); if (sel.rangeCount && sel.getRangeAt) { range = sel.getRangeAt(0); } document.designMode = "on"; if (range) { sel.removeAllRanges(); sel.addRange(range); } range.surroundContents(tags); if(tags.querySelector('.sC')) { tags.classList.remove('sC'); tags.innerHTML=tags.querySelector('.sC').innerHTML; } document.designMode = "off"; } 
 .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; } 
 <button class="ourbutton" type="button" onclick="headuppercase();">Tt</button> <div class="container"> <span class="spanA" contenteditable="true"></span> </div> 

This is not so easily achievable without a library because of all the edge cases you need to handle. 没有库,这是不容易实现的,因为您需要处理所有边缘情况。 I will first touch on some edge cases and then give you an example on how to implement them. 我将首先介绍一些边缘情况,然后给出一个如何实现它们的示例。

Simple case 简单的案例

Say we have the following string inside a text Node 假设我们在文本节点中有以下字符串

"Nobody wants too complex code because it becomes unmanageable." “没有人想要太复杂的代码,因为它变得无法管理。”

Consider a user who selected the words "Nobody wants" and pressed the toggle small caps button. 考虑选择“无人想要”字样的用户并按下切换小帽按钮。 You should end up with something looking like this (where the bold segments represent text in small-caps): 你应该得到这样的东西(粗体段代表小盘中的文字):

" Nobody wants too complex code because it becomes unmanageable." 没有人想要太复杂的代码,因为它变得无法管理。”

This is an easy case. 这是一个简单的案例。 Just wrap the segment " Nobody wants " inside a <span> and give it the sC class. 只需在<span>包含“ Nobody want<span>并将其赋予sC类。 The same goes for all other segments that are not yet in small caps. 对于尚未处于小盘中的所有其他细分市场也是如此。 Nothing too hard here. 这里没什么难的。

Edge case 1 边缘情况1

But say you are in the following state (again, bold segments represent text in small-caps) : 但是说你处于以下状态(粗体段代表小盘中的文字):

"Nobody wants too complex code because it becomes unmanageable. " “没有人想要太复杂的代码, 因为它变得无法管理。

When the user selects and toggles the word "becomes" things become complicated. 当用户选择并切换单词“变为”时,事情变得复杂。 You have to: 你必须:

  1. Remove the last two words from their containing <span class="sC"> element 从包含<span class="sC">元素中删除最后两个单词
  2. Add the word "becomes" after the span (from step 1.) and make sure it is not contained in a <span> that has the className sC . 在span(步骤1)之后添加单词“become” ,并确保它不包含在具有className sC<span>中。
  3. Add the word "unmanageable" inside a new <span class="sC"> element after the text Node "becomes" (that was inserted in step 2.) 在文本节点“变为” (在步骤2中插入)之后,在新的<span class="sC">元素内添加“unmanageable”一词。

Edge case 2 边缘情况2

Or say you are in the following state (again, bold segments represent text in small-caps) : 或者说你处于以下状态(粗体段代表小盘中的文字):

" Nobody wants too complex code because it becomes unmanageable. " 没有人想要复杂的代码, 因为它变得无法管理。

What should happen when somebody selects and toggles the segment "wants too complex code because" ? 当有人选择并切换片段“想要太复杂的代码因为”时会发生什么? One could say: make every character in this segment 可以说:在这一部分中制作每一个角色

  • small-caps : when it is not 小盘 :什么时候不是
  • back to normal : when it is currently small-caps 恢复正常 :当它是目前的小型股

It's easy to see that you will again need a lot of splitting existing span elements , creating new text nodes , and so forth 很容易看出,您将需要大量拆分现有的span元素创建新的文本节点等等

Edge case N 边缘情况N.

Say you start with a nested list 假设您从嵌套列表开始

  • A 一个
    • B
    • C C
  • D d

and a user selects the two last items at once. 并且用户一次选择最后两个项目。 Then you need to wrap each item separately into a span. 然后,您需要将每个项目分别包装到一个范围内。

First step towards a solution 迈向解决方案的第一步

While it is not clear in your question how all the edge cases should be handled, here is a first step towards a solution. 虽然在您的问题中不清楚应该如何处理所有边缘情况,但这是迈向解决方案的第一步。

 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 } 
 .sC { font-variant: small-caps; } 
 <section contenteditable> Hello world this is some text </section> <button onclick="toggleCase()">Toggle case</button> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM