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


127

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

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

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

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

Ответы:


219

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

РЕДАКТИРОВАТЬ : видя, насколько это популярно в последнее время, я решил также обновить стили, чтобы они были больше похожи на 2014, а не на Windows 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.


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

2
Спасибо @Quantico, это правда, и теперь это должно быть исправлено как в коде, так и в jsfiddle. Есть другие проблемы? Примечание: вау, 170 предыдущих правок jsfiddle, он, несомненно, стал популярным.
Francisco Presencia

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

1
Еще одна небольшая проблема: если вы щелкните правой кнопкой мыши где-нибудь, когда меню отображается, оно мигает перед отображением. Я чувствую, что он должен либо скрыться (как по умолчанию), либо скрыться, а затем появиться в новой позиции.
Холлоуэй,

@ChetanJoshi, похоже, должен работать в IE11 в соответствии с MDN: developer.mozilla.org/en-US/docs/Web/Events/… Вы видите какие-нибудь ошибки?
Francisco Presencia

64

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

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/


Я думаю, что это контекстное меню было бы более полезным, если бы оно оставалось открытым, когда пользователь щелкнул внутри него (но закрылось, когда пользователь щелкнул за его пределами). Можно ли изменить его, чтобы он работал таким образом?
Андерсон Грин

2
Вы бы заглянули event.targetвнутрь привязки щелчка на document. Если его нет в контекстном меню, скройте
Эндрю Уитакер,

2
Я немного изменил его (чтобы предотвратить одновременное отображение нескольких меню): jsfiddle.net/fELma/287
Андерсон Грин

Я пытаюсь создать радиальное контекстное меню, вызываемое правой кнопкой мыши (например, здесь: pushing-pixels.org/wp-content/uploads/2012/07/… ). Это отличное начало в понимании того, как это сделать, спасибо!
Борис

@AndrewWhitaker, в вашем ответе говорится, что он будет применен ко всему документу. Что, если я хочу, чтобы он был применен к определенному элементу управления, например к кнопке (при условии, что его идентификатор - button1) ..?
Tk1993

8

<!DOCTYPE html>
<html>
<head>
    <title>Right Click</title>

    <link href="https://swisnl.github.io/jQuery-contextMenu/dist/jquery.contextMenu.css" rel="stylesheet" type="text/css" />

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
    <script src="https://swisnl.github.io/jQuery-contextMenu/dist/jquery.contextMenu.js" type="text/javascript"></script>

    <script src="https://swisnl.github.io/jQuery-contextMenu/dist/jquery.ui.position.min.js" type="text/javascript"></script>

</head>
<body>
    <span class="context-menu-one" style="border:solid 1px black; padding:5px;">Right Click Me</span>
    <script type="text/javascript">
        
        $(function() {
        $.contextMenu({
            selector: '.context-menu-one', 
            callback: function(key, options) {
                var m = "clicked: " + key;
                window.console && console.log(m) || alert(m); 
            },
            items: {
                "edit": {name: "Edit", icon: "edit"},
                "cut": {name: "Cut", icon: "cut"},
               copy: {name: "Copy", icon: "copy"},
                "paste": {name: "Paste", icon: "paste"},
                "delete": {name: "Delete", icon: "delete"},
                "sep1": "---------",
                "quit": {name: "Quit", icon: function(){
                    return 'context-menu-icon context-menu-icon-quit';
                }}
            }
        });

        $('.context-menu-one').on('click', function(e){
            console.log('clicked', this);
        })    
    });
    </script>
</body>
</html>


4

вот пример контекстного меню правой кнопки мыши в 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>


Отличная работа с использованием vanilla JS и чистой компоновки!
курт

3

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

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

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


2
Просто отметим, что Firefox теперь добавляет поддержку собственного «контекстного меню» HTML5 (объявленного через разметку). Теперь он доступен в бета-версии Firefox 8. ( developer.mozilla.org/en/Firefox_8_for_developers ).
Poshaughnessy 03

2

Вы можете посмотреть это руководство: 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();
});

1

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

Это довольно грубо, и, вероятно, есть более эффективные способы добиться этого. Он использует библиотеку контекстного меню 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()
      };
    }
  });
});

0

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

<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>
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.