2017-03-04 76 views
2

我建立一个实时的HTML荧光笔,这样,当用户选择的文本范围的文本与有背景属性跨度元件围绕选定的文本。更换的鼠标松开

这里是小提琴:https://jsfiddle.net/4hd2vrex/

问题是,这可以得到相当混乱,当用户做多个选择,跨度得到嵌套的,我得到了这样的内容:

<span style="background-color: rgb(255, 255, 131);"> 
    r 
    <span style="background-color: rgb(255, 255, 131);"> 
     <span style="background-color: rgb(255, 255, 131);"> 
      e 
     </span> 
     p 
    </span> 
    r 
    <span style="background-color: rgb(255, 255, 131);"> 
     e 
    </span> 
    h 
    <span style="background-color: rgb(255, 255, 131);"> 
     end 
    </span> 
    e 
    <span style="background-color: rgb(255, 255, 131);"> 
     rit 
    </span> 
</span> 

圣大奖蝙蝠侠!为了弥补这一点,我有以下想法:

在添加任何跨度之前,只需用原始选定的文本window.getSelection()替换所有选定的文本,span标签和所有。

因此,举例来说,如果我选择了跨度的那件事情上面,我的包裹选择的文本更跨度之前,我将取代那些跨,window.getSelection()这仅仅是文字reprehenderit,我会得到。

<span style="background-color: rgb(255, 255, 131);">reprehenderit</span> 

问: 如何更换我的选择选择文字?

+0

我建议你在该相互作用是如何实现的第二个想法。什么意思是用户选择了一个完整的已经选择的选择?选择的一部分?等等。因为你最终可能会减少交互并简化场景。只是评论的情况下,它可以帮助:) – Alvaro

+0

有趣...不知道如何简化这更多,仍然保持功能... –

+0

关闭....但没有雪茄:https://jsfiddle.net/4hd2vrex/2/ –

回答

2

我已经用我的方式做了整个高亮文本,而不是使用window.Selection API,但是使用:select(start,end).then(merge).then(filter).then(highlight)。最有趣的事情是它可以高亮显示复杂的元素,即使只是文本也是如此。我发现select api也可以编写一个wysiwyg html编辑器,所以我将它分享给所有对选择问题感兴趣的人,并希望能够帮助你,这是个好问题!

(function (context, factory) { 
 
    if (typeof module != 'undefined' && typeof module.exports == 'object') { 
 
     module.exports = factory(context); 
 
    } else { 
 
     factory(context, true); 
 
    } 
 
})(window || this, function (context, bind) { 
 
    function promise(executor) { 
 
     return new Promise(executor); 
 
    } 
 

 
    var $TYPE = 'nodeType', $TEXT = 'textContent', $PARENT = 'parentNode', $NEXT = 'nextSibling', $FIRST = 'firstChild', NIL = {}; 
 

 
    function leaf(node) { 
 
     return node[$TYPE] == 3; 
 
    } 
 

 
    function next(node, tree) { 
 
     var it = tree ? node[$FIRST] || node[$NEXT] : node[$NEXT]; 
 
     if (it) { 
 
      if (leaf(it)) return it; 
 
      return next(it, true); 
 
     } 
 
     var parent = node[$PARENT]; 
 
     return parent && next(parent); 
 
    } 
 

 
    function parent(node) { 
 
     return node[$PARENT]; 
 
    } 
 

 
    function wrap(node, start, end) { 
 
     if (!node) throw 'node is null'; 
 
     if (!leaf(node)) throw 'node is not a leaf:' + node.tagName; 
 
     var rawText = node[$TEXT]; 
 
     var rawLength = rawText.length; 
 
     var self = { 
 
      node: node, 
 
      text: function (text) { 
 
       if (text !== undefined) { 
 
        node.textContent = text; 
 
        return wrap(node, 0, text.length); 
 
       } 
 
       return rawText.substring(self.start(), self.end()); 
 
      }, 
 
      is: function (other) { 
 
       return node == other.node; 
 
      }, 
 
      start: function() { 
 
       return start === NIL || !start ? 0 : start; 
 
      }, 
 
      end: function() { 
 
       return end === NIL || !end ? rawLength : end; 
 
      }, 
 
      length: function() { 
 
       return self.end() - self.start(); 
 
      }, 
 
      to: function (end) { 
 
       return wrap(node, self.start(), end.end()); 
 
      }, 
 
      toLast: function() { 
 
       return wrap(node, start, rawLength); 
 
      }, 
 
      next: function() { 
 
       var it = next(node); 
 
       return it && wrap(it); 
 
      }, 
 
      split: function() { 
 
       if (self.length() >= rawLength) return self; 
 
       var stack = [0].concat(self.start() || []).concat(self.end()).concat(self.end() != rawLength ? rawLength : []); 
 
       var start = stack.shift(); 
 
       var separated = []; 
 
       while (stack.length) { 
 
        var end = stack.shift(); 
 
        var text = document.createTextNode(rawText.substring(start, end)); 
 
        self.after(text); 
 
        separated.push(wrap(text)); 
 
        start = end; 
 
       } 
 
       self.remove(); 
 
       return !self.start() ? separated[0] : separated[1]; 
 
      }, 
 
      remove: function (optimized) { 
 
       var parent = node[$PARENT]; 
 
       if (optimized && parent.childNodes.length == 1) { 
 
        parent[$PARENT].removeChild(parent); 
 
       } 
 
       parent.removeChild(node); 
 
       return this; 
 
      }, 
 
      merge: function (other) { 
 
       var it = self.split(); 
 
       return it.text(other.split().remove(true).text() + it.text()); 
 
      }, 
 
      after: function (e) { 
 
       node[$PARENT].insertBefore(e, node); 
 
       return this; 
 
      }, 
 
      wrap: function (e) { 
 
       e.appendChild(self.split().after(e).node); 
 
      } 
 
     }; 
 

 
     return self; 
 
    } 
 

 

 
    function select(start, end) { 
 
     return promise(function (resolve) { 
 
      start = wrap(start.text, start.offset, NIL), end = wrap(end.text, NIL, end.offset); 
 
      var selected = []; 
 
      while (start) { 
 
       if (start.is(end)) { 
 
        selected.push(start.to(end)); 
 
        break; 
 
       } 
 
       selected.push(start.toLast()); 
 
       start = start.next(); 
 
      } 
 
      resolve(selected); 
 
     }); 
 
    } 
 

 
    function merge(filter) { 
 
     return function (parts) { 
 
      var result = [parts.shift()]; 
 
      while (parts.length) { 
 
       var prev = result.pop(); 
 
       var next = parts.shift(); 
 
       if (filter(prev.node, next.node)) { 
 
        result.push(next.merge(prev)); 
 
       } else { 
 
        result.push(prev); 
 
        result.push(next); 
 
       } 
 
      } 
 
      return result; 
 
     } 
 
    } 
 

 
    function filter(test) { 
 
     return function (parts) { 
 
      return parts.filter(function (part) { 
 
       return test(part.node); 
 
      }); 
 
     } 
 
    } 
 

 
    function apply(consume) { 
 
     return function (parts) { 
 
      return parts.forEach(function (part) { 
 
       return consume(part); 
 
      }); 
 
     } 
 
    } 
 

 
    var exports = { 
 
     __esModule: true, 
 
     default: select, 
 
     select: select, 
 
     merge: merge, 
 
     filter: filter, 
 
     apply: apply 
 
    }; 
 
    if (bind)for (var name in exports)context[name] = exports[name]; 
 
    return exports; 
 
}); 
 

 

 
(function() { 
 
    var COMPONENT_ID = 'highlight-' + +new Date; 
 
    var highlighter = { 
 
     init: function() { 
 
      this.bindEvents(); 
 
     }, 
 
     /** 
 
     * 
 
     */ 
 
     bindEvents: function() { 
 
      var self = this; 
 
      $('.swatch').on('click', function() { 
 
       $('.swatch').removeClass('active'); 
 
       $(this).addClass('active'); 
 
      }); 
 
      $('.content').mouseup(function() { 
 
       var current = self.actived(); 
 
       if (current.hasClass('clear')) { 
 
        self.clear(); 
 
       } else { 
 
        self.highlight(); 
 
       } 
 
      }); 
 

 
     }, 
 
     actived: function() { 
 
      return $('.swatch.active'); 
 
     }, 
 
     color: function() { 
 
      return this.actived().css('background-color'); 
 
     }, 
 
     /** 
 
     * 
 
     */ 
 
     highlight: function() { 
 
      var self = this; 
 
      var selection = self.getSelection(); 
 
      if (selection) { 
 
       self.select(selection.getRangeAt(0)).// 
 
       then(merge(function (left, right) { 
 
        var p1 = left.parentNode; 
 
        var p2 = right.parentNode; 
 

 
        var a1 = self.compare(left); 
 
        var a2 = self.compare(right); 
 
        return (a1 && a2 && p1.parentNode == p2.parentNode) || 
 
         (!a1 && !a2 && p1 == p2) || 
 
         (a1 && !a2 && p1.parentNode == p2) || 
 
         (!a1 && a2 && p2.parentNode == p1); 
 
       })).then(filter(function (part) { 
 
        return !self.compare(part); 
 
       })).then(function (parts) { 
 
        parts.map(function (node) { 
 
         node.wrap(self.component()); 
 
        }); 
 
       }).catch(function (e) { 
 
        console.log(e); 
 
       }); 
 
       selection.removeAllRanges(); 
 
      } 
 
     }, 
 
     component: function() { 
 
      return $('<span data-toggle="' + COMPONENT_ID + '">').css('background-color', this.color()).get(0); 
 
     }, 
 
     compare: function (text) { 
 
      var self = this; 
 
      var parent = $(text).parent(); 
 
      var highlighted = parent.is(self.selector()); 
 
      var color = parent.css('background-color'); 
 
      return highlighted && color == self.color(); 
 
     }, 
 
     selector: function() { 
 
      return '[data-toggle="?"]'.replace(/\?/, COMPONENT_ID); 
 
     }, 
 
     clear: function() { 
 
      var self = this; 
 
      var selection = self.getSelection(); 
 
      if (selection) { 
 
       self.select(selection.getRangeAt(0)).then(apply(function (part) { 
 
        var text = $(part.split().node); 
 
        while (true) { 
 
         var comp = text.closest(self.selector()); 
 
         if (!comp || !comp.length) { 
 
          break; 
 
         } 
 
         var children = comp.contents(); 
 
         var first = children[0], last = children[children.length - 1]; 
 
         if (text.is(last)) { 
 
          comp.after(text); 
 
         } else if (text.is(first)) { 
 
          comp.before(text); 
 
         } else { 
 
          var heading = comp.clone().empty(); 
 
          for (var i = 0; i < children.length; i++) { 
 
           if (text.is(children[i])) { 
 
            break; 
 
           } 
 
           heading.append(children[i]); 
 
          } 
 
          comp.before(heading).before(text); 
 
         } 
 

 
         if (first == last) comp.remove(); 
 
        } 
 
       })); 
 
       selection.removeAllRanges(); 
 
      } 
 
     }, 
 
     select: function (range) { 
 
      return select(
 
       {text: range.startContainer, offset: range.startOffset}, 
 
       {text: range.endContainer, offset: range.endOffset} 
 
      ); 
 
     }, 
 
     getSelection: function() { 
 
      var sel = window.getSelection(); 
 
      return /^\s*$/.test(self && sel.toString()) ? null : sel; 
 
     } 
 
    }; 
 

 
    highlighter.init(); 
 

 
})();
body { 
 
    margin: 0; 
 
    background: #fafafa; 
 
    box-shadow: 0 0 5rem rgba(0, 0, 0, 0.25) inset; 
 
} 
 

 
::-moz-selection { 
 
    background-color: rgba(0, 0, 0, 0.2); 
 
} 
 

 
::selection { 
 
    background-color: rgba(0, 0, 0, 0.2); 
 
} 
 

 
.content { 
 
    padding: 100px; 
 
} 
 

 
.footer { 
 
    padding: 0 100px 0 100px; 
 
    flex-basis: 100%; 
 
    height: 60px; 
 
    background: #292B2C; 
 
    position:fixed;top:0;width:100%; 
 
} 
 

 
.footer .items-left { 
 
    float: left; 
 
} 
 

 
.footer-item { 
 
    line-height: 60px; 
 
} 
 

 
#colors { 
 
    padding: 12px; 
 
} 
 

 
.swatch { 
 
    width: 30px; 
 
    height: 30px; 
 
    border-radius: 15px; 
 
    box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.5), 0px 2px 2px rgba(0, 0, 0, 0.5); 
 
    display: inline-block; 
 
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> 
 
<div class="content"> 
 
    <span style="color:red;"><b>Content</b> <i>Lorem</i> <font size='7'>ipsum</font> dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et 
 
    dolore magna aliqua. Ut enim ad minim</span> veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea 
 
    commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 
 
    pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est 
 
    laborum. 
 
    Content Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et 
 
    dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea 
 
    commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 
 
    pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est 
 
    laborum. 
 
    Content Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et 
 
    dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea 
 
    commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 
 
    pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est 
 
    laborum. 
 
    Content Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et 
 
    dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea 
 
    commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 
 
    pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est 
 
    laborum. 
 
</div> 
 
<div class="footer"> 
 
    <div class="items-left"> 
 
     <div id="colors"> 
 
      <div class="swatch active" style="background-color: rgba(255,255,131,.5);"></div> 
 
      <div class="swatch" style="background-color: rgba(255,140,218,.5);"></div> 
 
      <div class="swatch" style="background-color: rgba(144,255,184,.5);"></div> 
 
      <div class="swatch clear"></div> 
 
     </div> 
 
    </div> 
 
</div>

+0

很好的答案,我希望它可以帮助其他人喜欢它帮助我!非常感谢! –