2017-03-04 54 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

很好的答案,我希望它可以幫助其他人喜歡它幫助我!非常感謝! –