4

Lets say user selected something (text/image/anything) on the page and then he clicked right mouse button. Is there any way to detect whether he clicked outside of the selection or inside it?

Danny
  • 41
  • 3
  • check to this it may be helpful you http://www.codeproject.com/Articles/11222/Disabling-the-right-click-on-web-page – Rohit Azad Malik Jul 30 '15 at 09:37
  • Sorry, this is not what I want to achieve. – Danny Jul 30 '15 at 09:39
  • For that very specific case I don't think you have many solutions, the closest you can get is: 1) Detect when the user is selecting something (easy). 2) Retrieve the PARENT NODE of that element. 3) replace the selected element with a newer one containing a placeholder class. 4) add a right click event listener on that class and a right click event listener on the rest of the dom. Some reference that may help you: http://stackoverflow.com/questions/3731328/on-text-highlight-event, http://stackoverflow.com/questions/1206203/how-to-distinguish-between-left-and-right-mouse-click-with-jquery, – briosheje Jul 30 '15 at 09:50
  • @briosheje so if you're selecting a lonely textNode lying directly into the body, you will copy the full document? Also, how do you deal with partial/multiple elements selected? The first link you provided is only able to get textContent of selection, no other elements. – Kaiido Jul 31 '15 at 06:02
  • @Danny, have you found a solution? The points I enumerate in my answer will always be true (mostly the different browsers' behaviours). Maybe you should give us a more precise context of what you need here. – Kaiido Aug 05 '15 at 12:17
  • @Kaiido, I haven't found anything execept the code you wrote. It has few problems though, if you select text like [this](https://i.imgur.com/bcXU3Fz.png) and click [there](https://i.imgur.com/bcXU3Fz.png) function will return `true`. – Danny Aug 06 '15 at 13:48
  • yes because you are actually clicking in the element. To convince yourself, add a CSS rule `*{border:1px solid;}` – Kaiido Aug 06 '15 at 14:05
  • @Danny [with every element having a border](http://i.imgur.com/rOkh0Yt.png) and [if you set `body{display: inline-block;}`](http://i.imgur.com/IN2oPRc.png) – Kaiido Aug 07 '15 at 05:18

2 Answers2

1

After a lot of tries*, I think that there is no clean solution to this.

I finally come up with a tweak of jstonne's answer (which is by itself a tweak of MikeB's answer on the same thread) to something that may fit your needs.

It implies to get the Range of current Selection on right click (easy part), and then to check if the target node of the event is in this range (tricky part). Sadly, there is no easy way to get the Nodes contained in the Range object so we've to iterate through nodes contained between the Range.startContainer and Range.endContainer.

*even one implying range.extractContents() and MutationObservers

NOTES :

  • Since Chrome seems to make a new range selection for us when we do right-click, it will always return true if you right click on a text.
  • When you do select an img tag, you're actually selecting the textNodes before and after the element, so if you select the first image in below example, and you do right-click on the Some Dummy Text text, it will return true. But if you do the same test on the second image, it won't, since the text is wrapped into a <span> element.
  • There might be a lot of other caveats (depending on the browser, the os etc.)
    function isInsideSelection(event){
        event.preventDefault();
     var p = document.querySelector('#result');
     p.innerHTML= ''
     var sel = window.getSelection();
    
     if(!sel.rangeCount) return;
     var range = sel.getRangeAt(0);
     // nothing selected : don't do anything
     if(range.collapsed) return;
     // Firefox allows us to get the real node we clicked on
     var result = isTargetInRange(range, event.explicitOriginalTarget, event.target);
     p.innerHTML= 'clicked in the target: '+result;
    }
     
    function getNextNode(node) {
        if (node.firstChild)
            return node.firstChild;
    
        while (node) {
            if (node.nextSibling) return node.nextSibling;
            node = node.parentNode;
        }
    }
    
    function isTargetInRange(range, nodeTarget, elementTarget) {
        var start = range.startContainer.childNodes[range.startOffset] || range.startContainer;
        var end = range.endContainer.childNodes[range.endOffset] || range.endContainer;
        var commonAncestor = range.commonAncestorContainer;
        var nodes = [];
        var node;
        var result=false;
        for (node = start.parentNode; node; node = node.parentNode)
        {
         // our target is an element
         if(!nodeTarget && elementTarget === node ){
          result=true;
          break;
          }
            nodes.push(node);
            if (node == commonAncestor)
                break;
        }
        nodes.reverse();
    
        // walk children and siblings from start until end is found
        for (node = start; node; node = getNextNode(node))
        {
      // our target can be a textNode
      if((nodeTarget && nodeTarget === node) || (!nodeTarget && elementTarget === node)){
       result=true;
       break;
       }
            if (node == end)
                break;
        }
        return result;
    };
    
    document.addEventListener('contextmenu', isInsideSelection, false);
    #result{position: fixed; bottom:0;right:0}
    <p id="result"></p>
    <img src="http://lorempixel.com/50/50"/>
    Some Dummy Text
    <p>Some other text</p>
    <div><img src="http://lorempixel.com/60/60"/><span>with</span> some text</div>
Community
  • 1
  • 1
Kaiido
  • 123,334
  • 13
  • 219
  • 285
0

Hmm i don't know a direct answer but you can try this :

1- in first click get the x1= clientX and y1= clientY

2- on mouseup get the x2= clientX and y2= ClientY

3- on right click check if the clientX and clientY is betwwen what we get in 1- and 2- (rectangle with corners x1,y1,x2,y2)

code :

var x1,x2,y1,y2;
var selected = false;
document.onmousedown = function(e){
...
}
document.onmouseup = function(e){
    x2 = e.clientX;
    y2 = e.clientY;
    selected = true;
}

http://jsfiddle.net/htpfwwgy/

Elheni Mokhles
  • 3,801
  • 2
  • 12
  • 17
  • I guess that's the closest solution I can get. Any snippets to easily achieve this? The other one I thought about was to get list of the elements that contain selection and then just loop through them on right click event. – Danny Jul 30 '15 at 10:04
  • Thanks a lot. I am a bit confused though. How do I properly manage case when user clicked outside selection? Simple `else` or inverting `>` and `<` doesn't work. – Danny Jul 30 '15 at 11:47