Создание настраиваемых контекстных меню, вызываемых правой кнопкой мыши для моего веб-приложения

У меня есть несколько веб-сайтов, таких как google-docs и map-quest, на которых есть настраиваемые раскрывающиеся меню при щелчке правой кнопкой мыши. Каким-то образом они переопределяют поведение раскрывающегося меню браузера, и теперь я точно знаю, как они это делают. Я нашел плагин jQuery, который делает это, но мне все еще интересно несколько вещей:

  • Как это работает? Действительно ли раскрывающееся меню браузера переопределяется или эффект просто моделируется? Если да, то как?
  • Что абстрагирует плагин? Что происходит за кулисами?
  • Это единственный способ добиться такого эффекта?

изображение пользовательского контекстного меню

Посмотрите несколько настраиваемых контекстных меню в действии


person Gordon Gustafson    schedule 21.12.2010    source источник


Ответы (8)


Я знаю, что этот вопрос очень старый, но я просто столкнулся с той же проблемой и решил ее сам, поэтому я отвечаю на тот случай, если кто-то найдет это через Google, как и я. Я основал свое решение на решении @ Andrew, но потом практически все изменил.

РЕДАКТИРОВАТЬ: видя, насколько это популярно в последнее время, я решил также обновить стили, чтобы они больше походили на 2014, а не на окна 95. Я исправил ошибки, обнаруженные @Quantico и @Trengot, так что теперь это более веский ответ.

РЕДАКТИРОВАТЬ 2: я установил его с помощью StackSnippets, так как это действительно классная новая функция. Я оставляю здесь хороший jsfiddle для справки (щелкните 4-ю панель чтобы увидеть их работу).

Новый фрагмент стека:

// JAVASCRIPT (jQuery)

// Trigger action when the contexmenu is about to be shown
$(document).bind("contextmenu", function (event) {
    
    // Avoid the real one
    event.preventDefault();
    
    // Show contextmenu
    $(".custom-menu").finish().toggle(100).
    
    // In the right position (the mouse)
    css({
        top: event.pageY + "px",
        left: event.pageX + "px"
    });
});


// If the document is clicked somewhere
$(document).bind("mousedown", function (e) {
    
    // If the clicked element is not the menu
    if (!$(e.target).parents(".custom-menu").length > 0) {
        
        // Hide it
        $(".custom-menu").hide(100);
    }
});


// If the menu element is clicked
$(".custom-menu li").click(function(){
    
    // This is the triggered action name
    switch($(this).attr("data-action")) {
        
        // A case for each action. Your actions here
        case "first": alert("first"); break;
        case "second": alert("second"); break;
        case "third": alert("third"); break;
    }
  
    // Hide it AFTER the action was triggered
    $(".custom-menu").hide(100);
  });
/* CSS3 */

/* The whole thing */
.custom-menu {
    display: none;
    z-index: 1000;
    position: absolute;
    overflow: hidden;
    border: 1px solid #CCC;
    white-space: nowrap;
    font-family: sans-serif;
    background: #FFF;
    color: #333;
    border-radius: 5px;
    padding: 0;
}

/* Each of the items in the list */
.custom-menu li {
    padding: 8px 12px;
    cursor: pointer;
    list-style-type: none;
    transition: all .3s ease;
    user-select: none;
}

.custom-menu li:hover {
    background-color: #DEF;
}
<!-- HTML -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.js"></script>

<ul class='custom-menu'>
  <li data-action="first">First thing</li>
  <li data-action="second">Second thing</li>
  <li data-action="third">Third thing</li>
</ul>

<!-- Not needed, only for making it clickable on StackOverflow -->
Right click me

Примечание: вы можете увидеть небольшие ошибки (раскрывающийся список далеко от курсора и т. Д.), Убедитесь, что он работает в jsfiddle, так как он больше похож на вашу веб-страницу, чем на StackSnippets.

person Francisco Presencia    schedule 09.12.2013
comment
Я думаю, у вас может быть проблема с мышью. Это может вызвать состояние гонки, поскольку щелчок по пункту меню вызывает щелчок, который является мышью вниз и мышью вверх. - person Quantico; 29.07.2014
comment
Спасибо @Quantico, это правда, и теперь это должно быть исправлено как в коде, так и в jsfiddle. Есть другие проблемы? Примечание: вау, 170 предыдущих правок jsfiddle, он, несомненно, стал популярным. - person Francisco Presencia; 29.07.2014
comment
Если вы используете новую скрипку, всплывающее окно кажется прозрачным, если вы используете любые другие элементы html на странице. РЕДАКТИРОВАТЬ: добавление цвета фона в css решает эту проблему. - person Holloway; 09.10.2014
comment
Еще одна незначительная проблема: если вы щелкните правой кнопкой мыши где-нибудь, когда меню отображается, оно мерцает перед отображением. Я чувствую, что он должен либо скрыться (как по умолчанию), либо скрыться, а затем появиться в новой позиции. - person Holloway; 09.10.2014
comment
@ChetanJoshi, похоже, должен работать в IE11 согласно MDN: разработчик .mozilla.org / en-US / docs / Web / Events / Вы видите ошибку? - person Francisco Presencia; 07.07.2018

Как сказал Адриан, плагины будут работать точно так же. Вам понадобятся три основные части:

1: Обработчик события 'contextmenu':

$(document).bind("contextmenu", function(event) {
    event.preventDefault();
    $("<div class='custom-menu'>Custom menu</div>")
        .appendTo("body")
        .css({top: event.pageY + "px", left: event.pageX + "px"});
});

Здесь вы можете привязать обработчик событий к любому селектору, для которого вы хотите отобразить меню. Я выбрал весь документ.

2: Обработчик события 'click' (для закрытия пользовательского меню):

$(document).bind("click", function(event) {
    $("div.custom-menu").hide();
});

3: CSS для управления положением меню:

.custom-menu {
    z-index:1000;
    position: absolute;
    background-color:#C0C0C0;
    border: 1px solid black;
    padding: 2px;
}

В CSS важно включить z-index и position: absolute

Было бы не так уж сложно обернуть все это в красивый плагин jQuery.

Здесь вы можете увидеть простую демонстрацию: http://jsfiddle.net/andrewwhitaker/fELma/

person Andrew Whitaker    schedule 21.12.2010
comment
Я думаю, что это контекстное меню было бы более полезным, если бы оно оставалось открытым, когда пользователь щелкнул внутри него (но закрылось, когда пользователь щелкнул за его пределами). Можно ли изменить его, чтобы он работал таким образом? - person Anderson Green; 15.12.2012
comment
Вы бы посмотрели на event.target внутри привязки щелчка на document. Если его нет в контекстном меню, скройте меню: jsfiddle.net/fELma/286 - person Andrew Whitaker; 15.12.2012
comment
Я немного изменил его (чтобы предотвратить одновременное отображение нескольких меню): jsfiddle.net/fELma/287 < / а> - person Anderson Green; 15.12.2012
comment
@AndrewWhitaker, в вашем ответе говорится, что он будет применен ко всему документу. Что, если я хочу, чтобы он был применен к определенному элементу управления, например к кнопке (при условии, что его идентификатор - button1) ..? - person Tk1993; 09.05.2017

вот пример контекстного меню правой кнопки мыши в javascript: Контекстное меню правой кнопки мыши

Используется необработанный код javasScript для функциональности контекстного меню. Не могли бы вы проверить это, надеюсь, это поможет вам.

Живой код:

(function() {
  
  "use strict";


  /*********************************************** Context Menu Function Only ********************************/
  function clickInsideElement( e, className ) {
    var el = e.srcElement || e.target;
    if ( el.classList.contains(className) ) {
      return el;
    } else {
      while ( el = el.parentNode ) {
        if ( el.classList && el.classList.contains(className) ) {
          return el;
        }
      }
    }
    return false;
  }

  function getPosition(e) {
    var posx = 0, posy = 0;
    if (!e) var e = window.event;
    if (e.pageX || e.pageY) {
      posx = e.pageX;
      posy = e.pageY;
    } else if (e.clientX || e.clientY) {
      posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
      posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
    }
    return {
      x: posx,
      y: posy
    }
  }

  // Your Menu Class Name
  var taskItemClassName = "thumb";
  var contextMenuClassName = "context-menu",contextMenuItemClassName = "context-menu__item",contextMenuLinkClassName = "context-menu__link", contextMenuActive = "context-menu--active";
  var taskItemInContext, clickCoords, clickCoordsX, clickCoordsY, menu = document.querySelector("#context-menu"), menuItems = menu.querySelectorAll(".context-menu__item");
  var menuState = 0, menuWidth, menuHeight, menuPosition, menuPositionX, menuPositionY, windowWidth, windowHeight;

  function initMenuFunction() {
    contextListener();
    clickListener();
    keyupListener();
    resizeListener();
  }

  /**
   * Listens for contextmenu events.
   */
  function contextListener() {
    document.addEventListener( "contextmenu", function(e) {
      taskItemInContext = clickInsideElement( e, taskItemClassName );

      if ( taskItemInContext ) {
        e.preventDefault();
        toggleMenuOn();
        positionMenu(e);
      } else {
        taskItemInContext = null;
        toggleMenuOff();
      }
    });
  }

  /**
   * Listens for click events.
   */
  function clickListener() {
    document.addEventListener( "click", function(e) {
      var clickeElIsLink = clickInsideElement( e, contextMenuLinkClassName );

      if ( clickeElIsLink ) {
        e.preventDefault();
        menuItemListener( clickeElIsLink );
      } else {
        var button = e.which || e.button;
        if ( button === 1 ) {
          toggleMenuOff();
        }
      }
    });
  }

  /**
   * Listens for keyup events.
   */
  function keyupListener() {
    window.onkeyup = function(e) {
      if ( e.keyCode === 27 ) {
        toggleMenuOff();
      }
    }
  }

  /**
   * Window resize event listener
   */
  function resizeListener() {
    window.onresize = function(e) {
      toggleMenuOff();
    };
  }

  /**
   * Turns the custom context menu on.
   */
  function toggleMenuOn() {
    if ( menuState !== 1 ) {
      menuState = 1;
      menu.classList.add( contextMenuActive );
    }
  }

  /**
   * Turns the custom context menu off.
   */
  function toggleMenuOff() {
    if ( menuState !== 0 ) {
      menuState = 0;
      menu.classList.remove( contextMenuActive );
    }
  }

  function positionMenu(e) {
    clickCoords = getPosition(e);
    clickCoordsX = clickCoords.x;
    clickCoordsY = clickCoords.y;
    menuWidth = menu.offsetWidth + 4;
    menuHeight = menu.offsetHeight + 4;

    windowWidth = window.innerWidth;
    windowHeight = window.innerHeight;

    if ( (windowWidth - clickCoordsX) < menuWidth ) {
      menu.style.left = (windowWidth - menuWidth)-0 + "px";
    } else {
      menu.style.left = clickCoordsX-0 + "px";
    }

    // menu.style.top = clickCoordsY + "px";

    if ( Math.abs(windowHeight - clickCoordsY) < menuHeight ) {
      menu.style.top = (windowHeight - menuHeight)-0 + "px";
    } else {
      menu.style.top = clickCoordsY-0 + "px";
    }
  }


  function menuItemListener( link ) {
    var menuSelectedPhotoId = taskItemInContext.getAttribute("data-id");
    console.log('Your Selected Photo: '+menuSelectedPhotoId)
    var moveToAlbumSelectedId = link.getAttribute("data-action");
    if(moveToAlbumSelectedId == 'remove'){
      console.log('You Clicked the remove button')
    }else if(moveToAlbumSelectedId && moveToAlbumSelectedId.length > 7){
      console.log('Clicked Album Name: '+moveToAlbumSelectedId);
    }
    toggleMenuOff();
  }
  initMenuFunction();

})();
/* For Body Padding and content */
body { padding-top: 70px; }
li a { text-decoration: none !important; }

/* Thumbnail only */
.thumb {
  margin-bottom: 30px;
}
.thumb:hover a, .thumb:active a, .thumb:focus a {
  border: 1px solid purple;
}

/************** For Context menu ***********/
/* context menu */
.context-menu {  display: none;  position: absolute;  z-index: 9999;  padding: 12px 0;  width: 200px;  background-color: #fff;  border: solid 1px #dfdfdf;  box-shadow: 1px 1px 2px #cfcfcf;  }
.context-menu--active {  display: block;  }

.context-menu__items { list-style: none;  margin: 0;  padding: 0;  }
.context-menu__item { display: block;  margin-bottom: 4px;  }
.context-menu__item:last-child {  margin-bottom: 0;  }
.context-menu__link {  display: block;  padding: 4px 12px;  color: #0066aa;  text-decoration: none;  }
.context-menu__link:hover {  color: #fff;  background-color: #0066aa;  }
.context-menu__items ul {  position: absolute;  white-space: nowrap;  z-index: 1;  left: -99999em;}
.context-menu__items > li:hover > ul {  left: auto;  padding-top: 5px  ;  min-width: 100%;  }
.context-menu__items > li li ul {  border-left:1px solid #fff;}
.context-menu__items > li li:hover > ul {  left: 100%;  top: -1px;  }
.context-menu__item ul { background-color: #ffffff; padding: 7px 11px;  list-style-type: none;  text-decoration: none; margin-left: 40px; }
.page-media .context-menu__items ul li { display: block; }
/************** For Context menu ***********/
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<body>



    <!-- Page Content -->
    <div class="container">

        <div class="row">

            <div class="col-lg-12">
                <h1 class="page-header">Thumbnail Gallery <small>(Right click to see the context menu)</small></h1>
            </div>

            <div class="col-lg-3 col-md-4 col-xs-6 thumb">
                <a class="thumbnail" href="#">
                    <img class="img-responsive" src="http://placehold.it/400x300" alt="">
                </a>
            </div>
            <div class="col-lg-3 col-md-4 col-xs-6 thumb">
                <a class="thumbnail" href="#">
                    <img class="img-responsive" src="http://placehold.it/400x300" alt="">
                </a>
            </div>
            <div class="col-lg-3 col-md-4 col-xs-6 thumb">
                <a class="thumbnail" href="#">
                    <img class="img-responsive" src="http://placehold.it/400x300" alt="">
                </a>
            </div>
            <div class="col-lg-3 col-md-4 col-xs-6 thumb">
                <a class="thumbnail" href="#">
                    <img class="img-responsive" src="http://placehold.it/400x300" alt="">
                </a>
            </div>
            <div class="col-lg-3 col-md-4 col-xs-6 thumb">
                <a class="thumbnail" href="#">
                    <img class="img-responsive" src="http://placehold.it/400x300" alt="">
                </a>
            </div>
            <div class="col-lg-3 col-md-4 col-xs-6 thumb">
                <a class="thumbnail" href="#">
                    <img class="img-responsive" src="http://placehold.it/400x300" alt="">
                </a>
            </div>
            <div class="col-lg-3 col-md-4 col-xs-6 thumb">
                <a class="thumbnail" href="#">
                    <img class="img-responsive" src="http://placehold.it/400x300" alt="">
                </a>
            </div>
            <div class="col-lg-3 col-md-4 col-xs-6 thumb">
                <a class="thumbnail" href="#">
                    <img class="img-responsive" src="http://placehold.it/400x300" alt="">
                </a>
            </div>

        </div>

        <hr>


    </div>
    <!-- /.container -->


    <!-- / The Context Menu -->
    <nav id="context-menu" class="context-menu">
        <ul class="context-menu__items">
            <li class="context-menu__item">
                <a href="#" class="context-menu__link" data-action="Delete This Photo"><i class="fa fa-empire"></i> Delete This Photo</a>
            </li>
            <li class="context-menu__item">
                <a href="#" class="context-menu__link" data-action="Photo Option 2"><i class="fa fa-envira"></i> Photo Option 2</a>
            </li>
            <li class="context-menu__item">
                <a href="#" class="context-menu__link" data-action="Photo Option 3"><i class="fa fa-first-order"></i> Photo Option 3</a>
            </li>
            <li class="context-menu__item">
                <a href="#" class="context-menu__link" data-action="Photo Option 4"><i class="fa fa-gitlab"></i> Photo Option 4</a>
            </li>
            <li class="context-menu__item">
                <a href="#" class="context-menu__link" data-action="Photo Option 5"><i class="fa fa-ioxhost"></i> Photo Option 5</a>
            </li>
            <li class="context-menu__item">
                <a href="#" class="context-menu__link"><i class="fa fa-arrow-right"></i> Add Photo to</a>
                <ul>
                    <li><a href="#!" class="context-menu__link" data-action="album-one"><i class="fa fa-camera-retro"></i> Album One</a></li>
                    <li><a href="#!" class="context-menu__link" data-action="album-two"><i class="fa fa-camera-retro"></i> Album Two</a></li>
                    <li><a href="#!" class="context-menu__link" data-action="album-three"><i class="fa fa-camera-retro"></i> Album Three</a></li>
                    <li><a href="#!" class="context-menu__link" data-action="album-four"><i class="fa fa-camera-retro"></i> Album Four</a></li>
                </ul>
            </li>
        </ul>
    </nav>

    <!-- End # Context Menu -->


</body>

person Robin Hossain    schedule 07.12.2016
comment
Отличная работа с использованием ванильного JS и чистой компоновки! - person kurt; 01.08.2017

Контекстное меню браузера переопределяется. Невозможно расширить собственное контекстное меню ни в одном из основных браузеров.

Поскольку плагин создает собственное меню, единственная часть, которая действительно абстрагируется, - это событие контекстного меню браузера. Плагин создает html-меню на основе вашей конфигурации, а затем размещает это содержимое в том месте, где вы щелкнули.

Да, это единственный способ создать собственное контекстное меню. Очевидно, что разные плагины делают что-то немного по-разному, но все они переопределяют событие браузера и помещают свое собственное меню на основе HTML в нужное место.

person Adrian Gonzales    schedule 21.12.2010
comment
Просто упомяну, что Firefox теперь добавляет поддержку собственного «контекстного меню» HTML5 (объявленного через разметку). Теперь он доступен в бета-версии Firefox 8. (developer.mozilla.org/en/Firefox_8_for_developers). - person poshaughnessy; 03.10.2011

Вы можете посмотреть это руководство: http://www.youtube.com/watch?v=iDyEfKWCzhg Убедитесь, что контекстное меню сначала скрыто и имеет абсолютную позицию. Это гарантирует, что не будет многократного контекстного меню и бесполезного создания контекстного меню. Ссылка на страницу размещена в описании ролика на YouTube.

$(document).bind("contextmenu", function(event){
$("#contextmenu").css({"top": event.pageY +  "px", "left": event.pageX +  "px"}).show();
});
$(document).bind("click", function(){
$("#contextmenu").hide();
});
person Asif Mallik    schedule 22.08.2013

Я знаю, что это тоже довольно старое. Недавно мне потребовалось создать контекстное меню, которое я вставляю на другие сайты с другими свойствами в зависимости от щелчка по элементу.

Это довольно грубо, и, вероятно, есть более эффективные способы добиться этого. Он использует библиотеку контекстного меню jQuery, расположенную здесь

Мне понравилось его создавать, и хотя вам, ребята, это может пригодиться.

Вот скрипка. Я надеюсь, что это может кому-то помочь.

$(function() {
  function createSomeMenu() {
    var all_array = '{';
    var x = event.clientX,
      y = event.clientY,
      elementMouseIsOver = document.elementFromPoint(x, y);
    if (elementMouseIsOver.closest('a')) {
      all_array += '"Link-Fold": {"name": "Link", "icon": "fa-external-link", "items": {"fold2-key1": {"name": "Open Site in New Tab"}, "fold2-key2": {"name": "Open Site in Split Tab"}, "fold2-key3": {"name": "Copy URL"}}},';
    }
    if (elementMouseIsOver.closest('img')) {
      all_array += '"Image-Fold": {"name": "Image","icon": "fa-picture-o","items": {"fold1-key1": {"name":"Download Image"},"fold1-key2": {"name": "Copy Image Location"},"fold1-key3": {"name": "Go To Image"}}},';
    }
    all_array += '"copy": {"name": "Copy","icon": "copy"},"paste": {"name": "Paste","icon": "paste"},"edit": {"name": "Edit HTML","icon": "fa-code"}}';
    return JSON.parse(all_array);
  }

  // setup context menu
  $.contextMenu({
    selector: 'body',
    build: function($trigger, e) {
      return {
        callback: function(key, options) {
          var m = "clicked: " + key;
          console.log(m);
        },
        items: createSomeMenu()
      };
    }
  });
});
person Cody Fidler    schedule 14.02.2017

У меня есть хорошая и простая реализация с использованием начальной загрузки, как показано ниже.

<select class="custom-select" id="list" multiple></select>

<div class="dropdown-menu" id="menu-right-click" style=>
    <h6 class="dropdown-header">Actions</h6>
    <a class="dropdown-item" href="" onclick="option1();">Option 1</a>
    <a class="dropdown-item" href="" onclick="option2();">Option 2</a>
</div>

<script>
    $("#menu-right-click").hide();

    $(document).on("contextmenu", "#list", function (e) {
        $("#menu-right-click")
            .css({
                position: 'absolute',
                left: e.pageX,
                top: e.pageY,
                display: 'block'
            })
        return false;
    });

    function option1() {
        // something you want...
        $("#menu-right-click").hide();
    }

    function option2() {
        // something else 
        $("#menu-right-click").hide();
    }
</script>
person Alexandre Crivellaro    schedule 11.07.2020

Простой

  1. показывать контекстное меню при щелчке правой кнопкой мыши в любом месте документа
  2. избегать скрытия контекстного меню при щелчке внутри контекстного меню
  3. закрыть контекстное меню при нажатии левой кнопки мыши

Примечание: не используйте display: none вместо этого используйте непрозрачность, чтобы скрыть и показать

var menu= document.querySelector('.context_menu');
document.addEventListener("contextmenu", function(e) {      
            e.preventDefault();  
            menu.style.position = 'absolute';
            menu.style.left = e.pageX + 'px';
            menu.style.top = e.pageY + 'px';        
             menu.style.opacity = 1;
        });
       
  document.addEventListener("click", function(e){
  if(e.target.closest('.context_menu'))
  return;
      menu.style.opacity = 0;
  });
.context_menu{

width:70px;
background:lightgrey;
padding:5px;
 opacity :0;
}
.context_menu div{
margin:5px;
background:grey;

}
.context_menu div:hover{
margin:5px;
background:red;
   cursor:pointer;

}
<div class="context_menu">
<div>menu 1</div>
<div>menu 2</div>
</div>

дополнительный css

var menu= document.querySelector('.context_menu');
document.addEventListener("contextmenu", function(e) {      
            e.preventDefault();  
            menu.style.position = 'absolute';
            menu.style.left = e.pageX + 'px';
            menu.style.top = e.pageY + 'px';        
             menu.style.opacity = 1;
        });
       
  document.addEventListener("click", function(e){
  if(e.target.closest('.context_menu'))
  return;
      menu.style.opacity = 0;
  });
.context_menu{

width:120px;
background:white;
border:1px solid lightgrey;

 opacity :0;
}
.context_menu div{
padding:5px;
padding-left:15px;
margin:5px 2px;
border-bottom:1px solid lightgrey;
}
.context_menu div:last-child {
border:none;
}
.context_menu div:hover{

background:lightgrey;
   cursor:pointer;

}
<div class="context_menu">
<div>menu 1</div>
<div>menu 2</div>
<div>menu 3</div>
<div>menu 4</div>
</div>

person ßãlãjî    schedule 30.01.2021