2009-10-19 67 views
20

我需要在用户所选文本旁边放置一个绝对定位的按钮。就像IE8在内部做的一样。如何在用户文本选择旁边放置元素?

我一个jQuery的鼠标松开事件结合到文档,并获得选定的文本,但我目前不能就如何知道实际的选择定位的思想,没有中某些元素包裹它,因为文本的选择可以跨越多个元素,并且如果我将其包裹,它会混乱结构。

+0

的HTTP: //www.nytimes.com在他们的文章中做到这一点 – vsync 2009-10-19 16:34:27

回答

23

你可以在选择的结束位置的标记跨度,得到它的坐标使用jQuery,将你的按钮放在这些坐标处并删除标记跨度。

下面应该让你开始:

var markSelection = (function() { 
    var markerTextChar = "\ufeff"; 
    var markerTextCharEntity = ""; 

    var markerEl, markerId = "sel_" + new Date().getTime() + "_" + Math.random().toString().substr(2); 

    var selectionEl; 

    return function() { 
     var sel, range; 

     if (document.selection && document.selection.createRange) { 
      // Clone the TextRange and collapse 
      range = document.selection.createRange().duplicate(); 
      range.collapse(false); 

      // Create the marker element containing a single invisible character by creating literal HTML and insert it 
      range.pasteHTML('<span id="' + markerId + '" style="position: relative;">' + markerTextCharEntity + '</span>'); 
      markerEl = document.getElementById(markerId); 
     } else if (window.getSelection) { 
      sel = window.getSelection(); 

      if (sel.getRangeAt) { 
       range = sel.getRangeAt(0).cloneRange(); 
      } else { 
       // Older WebKit doesn't have getRangeAt 
       range = document.createRange(); 
       range.setStart(sel.anchorNode, sel.anchorOffset); 
       range.setEnd(sel.focusNode, sel.focusOffset); 

       // Handle the case when the selection was selected backwards (from the end to the start in the 
       // document) 
       if (range.collapsed !== sel.isCollapsed) { 
        range.setStart(sel.focusNode, sel.focusOffset); 
        range.setEnd(sel.anchorNode, sel.anchorOffset); 
       } 
      } 

      range.collapse(false); 

      // Create the marker element containing a single invisible character using DOM methods and insert it 
      markerEl = document.createElement("span"); 
      markerEl.id = markerId; 
      markerEl.appendChild(document.createTextNode(markerTextChar)); 
      range.insertNode(markerEl); 
     } 

     if (markerEl) { 
      // Lazily create element to be placed next to the selection 
      if (!selectionEl) { 
       selectionEl = document.createElement("div"); 
       selectionEl.style.border = "solid darkblue 1px"; 
       selectionEl.style.backgroundColor = "lightgoldenrodyellow"; 
       selectionEl.innerHTML = "&lt;- selection"; 
       selectionEl.style.position = "absolute"; 

       document.body.appendChild(selectionEl); 
      } 

      // Find markerEl position http://www.quirksmode.org/js/findpos.html 
     var obj = markerEl; 
     var left = 0, top = 0; 
     do { 
      left += obj.offsetLeft; 
      top += obj.offsetTop; 
     } while (obj = obj.offsetParent); 

      // Move the button into place. 
      // Substitute your jQuery stuff in here 
      selectionEl.style.left = left + "px"; 
      selectionEl.style.top = top + "px"; 

      markerEl.parentNode.removeChild(markerEl); 
     } 
    }; 
})(); 
+0

当我执行这段代码时,top变量是一个对象。可能有一个叫做top的全局变量。最好写var var = 0; var top = 0;而不是var left = top = 0; – 2013-06-19 18:31:54

+0

@ Z-CORE:你绝对正确:'top'是对包含当前文档的最外层窗口的引用。对于我来说,复制和粘贴别人的代码是非常不寻常的(在这种情况下,来自quirksmode.org),但至少很明显,错误并不是我的原因:) – 2013-06-19 23:00:56

+0

虽然不太可能,但可能存在的CSS规则依赖于某些像'#example> span + span'这样的元素顺序。将标记跨度插入内容会破坏那些可能改变/破坏布局的规则。 range.getBoundingClientRect()可能是更健壮的解决方案。 – Rick 2015-06-08 19:25:45

2

您应该在“范围”末尾插入绝对位置元素。这在不同的浏览器中有不同的作用,所以你最好的选择可能是嗅探。

既然你问:这是纽约时报是如何做的在他们的“altClickToSearch.js”文件:

function insertButton() { 

selectionButton = new Element(
     'span', { 
      'className':'nytd_selection_button', 
      'id':'nytd_selection_button', 
      'title':'Lookup Word', 
      'style': 'margin:-20px 0 0 -20px; position:absolute; background:url(http://graphics8.nytimes.com/images/global/word_reference/ref_bubble.png);width:25px;height:29px;cursor:pointer;_background-image: none;filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="http://graphics8.nytimes.com/images/global/word_reference/ref_bubble.png", sizingMethod="image");' 
     } 
    ) 

if (Prototype.Browser.IE) { 
    var tmp = new Element('div'); 
    tmp.appendChild(selectionButton); 
    newRange = selection.duplicate(); 
    newRange.setEndPoint("StartToEnd", selection); 
    newRange.pasteHTML(tmp.innerHTML); 
    selectionButton = $('nytd_selection_button'); 
} 
else { 
    var range = selection.getRangeAt(0); 
    newRange = document.createRange(); 
    newRange.setStart(selection.focusNode, range.endOffset); 
    newRange.insertNode(selectionButton); 
} 
} 
+0

谢谢,是的,他们似乎只是在选择中的特定点插入它,而不是绝对的文档本身。谢谢! – vsync 2009-10-19 18:20:06

+3

那里有一个不必要的浏览器嗅探。你可以检查你需要的对象/方法。另外,旧版WebKits中的选择对象(Safari 2,也许3,我对此有点朦胧,而且我没有轻松掌握所有相关的浏览器)没有'getRangeAt'方法。 – 2009-10-19 20:18:53

+0

我并不是建议上面的代码作为最佳解决方案,尤其是浏览器嗅探。他在他的评论中多次问到。这只是该网站的代码。 – 2011-03-16 20:18:16

8

我用getBoundingClientRect()时,我需要的内容,以保持不受干扰,同时将更多的内容在它附近。

var r=window.getSelection().getRangeAt(0).getBoundingClientRect(); 
    var relative=document.body.parentNode.getBoundingClientRect(); 
    ele.style.top =(r.bottom -relative.top)+'px';//this will place ele below the selection 
    ele.style.right=-(r.right-relative.right)+'px';//this will align the right edges together 

这部作品在浏览器,但IE喜欢给奇怪的事情,所以这里是一个跨浏览器的解决方案:(经测试,在Chrome和IE,可能工作在其他地方)

https://jsfiddle.net/joktrpkz/7/

+0

嗨,你能解释你为什么在你的小提琴中使用cal1/cal2吗?谢谢。 – 2015-12-30 10:33:14

+0

@Guy这是处理IE放大时处理像素的方式。我不记得在其他各种场景中是否有必要。 – Rick 2015-12-31 14:17:01

+0

我的要求仅限于我的扩展程序才能在Chrome中运行,所以此解决方案对我来说非常有用。 – SKYnine 2017-03-28 11:28:20

相关问题