考虑到堆叠层上下文,如何处理 javascript 键绑定?

how to handle javascript keybindings considering a stacked layer context?

假设我有一个 <body> 打开了 full modal。按 [ESC] 键可以关闭此模式。 在此 full modal 中,用户可以打开另一个较小的模式,也可以通过按 [ESC] 键将其关闭。你如何处理 [ESC] 键并关闭 'higher' 层,防止按键传播并关闭正在侦听按键的其他层?

我期待一个直接的答案,使用 preventDefault 或类似的东西。我不考虑设置某种服务,在决定关闭哪个层之前进行多次检查。对我来说,这件事应该工作有点像点击事件,向上传播。这可行吗?

没有直接的 .preventDefault() 或一些黑魔法可以阻止您阻止事件监听器冒泡,除非您每次想要专门为该模态附加事件监听器时都创建一个新的处理程序不干扰其他处理程序。

与其拥有一组处理程序和大量事件侦听器,不如将模态框堆叠在一个数组中,然后一个一个地销毁它们?

这是我能想到的最短的代码,请参阅代码中的注释以获得逐步解释。

// Global variable, we use this to store DOM objects.
var modalStack = [];

// My function to create/open my modals.
function openModal() {
    var modalHTML = '<div id="modal-'+modalStack.length+'" class="modal"><button onclick="openModal()">Open modal '+(modalStack.length+1)+'</button></div>'; // I populate the modal with my stuff and assign it an ID containing the current stack size. 
    document.body.insertAdjacentHTML( 'beforeend', modalHTML ); // Add into the body
    modalStack.push( document.getElementById('modal-'+modalStack.length) ); // And push my DOM object I just created into the array.
}


// My ESC event listener
window.addEventListener('keyup', function(event) {
    var lastIndex = modalStack.length-1; // This gets the last element on the stack.
    if (event.keyCode == 27 && lastIndex >= 0) {
        var thisModal = modalStack[ lastIndex ]; // Just to make sense, I could've called the removeChild directly from the array.
        thisModal.parentNode.removeChild(thisModal); // Destroy the current element.
        modalStack.pop(); // Remove the associated last DOM object from the array.
    }
}, false);

jsFiddle Demo

我们可以通过使用 Stack 数据结构来解决这个问题,该数据结构可用于维护模态 windows 的创建和删除状态。

最顶层的模式将是堆栈的 Top/Head,它将在退出时首先被删除。下面是如何实现的简单演示。

最后,不需要额外的努力来创建堆栈实现,JavaScript数组有内置的pushpop方法,我们将在下面使用它们实施。

var Modal = (function() {

    //Just to give different margins for each modal
    var BASE_MARGIN = 20;
    
    //STACK, in this array we will store all modals
    var modalStack = [];
    
    //Creates all DOM and attach it to document's body
    function createModal() {
        var modal = document.createElement('div');
        modal.className = 'modal';
        modal.style.margin = ((modalStack.length + 1) * BASE_MARGIN) + 'px';
        
        var header = document.createElement('div');
        header.className = 'modalHeader';
        header.innerHTML = 'Level-' + (modalStack.length + 1);
        
        var close = document.createElement('div');
        close.className = 'closeModal';
        close.innerHTML = 'X';
        close.addEventListener("click", function() {
            var index = modalStack.indexOf(modal);
            for(var i = modalStack.length - 1; i >= index; i--) {
  var div = modalStack.pop(); 
               //no need of i, because pop will always get the last element
  document.body.removeChild(div);
            }
        });
        
        header.appendChild(close);
    
        var createModalButton = document.createElement('button');
        createModalButton.className = 'createButton';
        createModalButton.addEventListener("click", createModal);
        createModalButton.innerHTML = "Create Modal";
        
 modal.appendChild(header);
        modal.appendChild(createModalButton);
        
        document.body.appendChild(modal);
        modalStack.push(modal);
    }
    
    /**
    * Should be called on dom-loaded
    */
    function initialize() {
         document.addEventListener("keyup", function(ev) {
  if (ev.keyCode == 27) { // escape key maps to keycode `27`
   var div = modalStack.pop();
   document.body.removeChild(div);
  }
         });
    }
    
    return {
        createModal : createModal,
        initialize: initialize
    }
    
})();
div.modal {
    position: fixed;
    top: 0px;
    bottom: 0px;
    left: 0px;
    right: 0px;
    margin:20px;
    z-index = 100;
    background-color: #fff;
    border: 2px solid #333;
}

button.createButton {
    display: block;
    margin: auto;
    margin-top: 4px;
}

.modalHeader {
    background-color: lightsteelblue;
    border-bottom: 1px solid #555;
    color: white;
    padding: 6px 6px 6px 24px;
}

.closeModal {
    color: red;
    cursor: pointer;
    display: inline-block;
    float: right;
    margin-right: 14px;
}
<html>
<head>
</head>
<body onload="Modal.initialize();">
   <!--    Initial button to create child modals -->
   <input style="margin:50px 200px" class="createButton" 
       type="button" onclick="Modal.createModal();" value="Create Modal" />
</body>
</html>

由于我们可以控制所有模态 windows,我们可以更改其他模态的不透明度或根据新的 window 创建或删除使父模态 show/hide ,如下所示

var Modal = (function() {

    //Just to give different margins for each modal
    var BASE_MARGIN = 20;
    
    //STACK, in this array we will store all modals
    var modalStack = [];
    
    //Creates all DOM and attach it to document's body
    function createModal() {
        var modal = document.createElement('div');
        modal.className = 'modal';
        modal.style.margin = ((modalStack.length + 1) * BASE_MARGIN) + 'px';
        
        var header = document.createElement('div');
        header.className = 'modalHeader';
        header.innerHTML = 'Level-' + (modalStack.length + 1);
        
        var close = document.createElement('div');
        close.className = 'closeModal';
        close.innerHTML = 'X';
        close.addEventListener("click", function() {
            var index = modalStack.indexOf(modal);
            for(var i = modalStack.length - 1; i >= index; i--) {
  var div = modalStack.pop(); 
              if(modalStack.length > 0) modalStack[modalStack.length - 1].style.display = 'block';
               //no need of i, because pop will always get the last element
  document.body.removeChild(div);
            }
        });
        
        header.appendChild(close);
    
        var createModalButton = document.createElement('button');
        createModalButton.className = 'createButton';
        createModalButton.addEventListener("click", createModal);
        createModalButton.innerHTML = "Create Modal";
        
 modal.appendChild(header);
        modal.appendChild(createModalButton);
        
        document.body.appendChild(modal);
        
        
      if(modalStack.length > 0) modalStack[modalStack.length - 1].style.display = 'none';
      modalStack.push(modal);
    }
    
    /**
    * Should be called on dom-loaded
    */
    function initialize() {
         document.addEventListener("keyup", function(ev) {
  if (ev.keyCode == 27) { // escape key maps to keycode `27`
   var div = modalStack.pop();
   document.body.removeChild(div);
            if(modalStack.length > 0) modalStack[modalStack.length - 1].style.display = 'block';
  }
         });
    }
    
    return {
        createModal : createModal,
        initialize: initialize
    }
    
})();
div.modal {
    position: fixed;
    top: 0px;
    bottom: 0px;
    left: 0px;
    right: 0px;
    margin:20px;
    z-index = 100;
    background-color: #fff;
    border: 2px solid #333;
}

button.createButton {
    display: block;
    margin: auto;
    margin-top: 4px;
}

.modalHeader {
    background-color: lightsteelblue;
    border-bottom: 1px solid #555;
    color: white;
    padding: 6px 6px 6px 24px;
}

.closeModal {
    color: red;
    cursor: pointer;
    display: inline-block;
    float: right;
    margin-right: 14px;
}
<html>
<head>
</head>
<body onload="Modal.initialize();">
   <!--    Initial button to create child modals -->
   <input style="margin:50px 200px" class="createButton" 
       type="button" onclick="Modal.createModal();" value="Create Modal" />
</body>
</html>

我认为最简单的 pf 方法,仅使用 JS:

function openModalOnTop(modalHtml)
{
  $modalsContainer = document.getElementById("modalscontainer");
  var modalContainerTemplate = document.createElement("div");
  modalContainerTemplate.className = "modal";
  modalContainerTemplate.innerHTML  = modalHtml;
  $modalsContainer.appendChild(modalContainerTemplate);
}

function closeTopModal()
{
  $modalsContainer = document.getElementById("modalscontainer");
  $modalsContainer.lastChild.remove();
}

window.addEventListener('keyup', function(event) {
    if (event.keyCode == 79) {
      console.log("open");
      openModalOnTop("someHTMLHere");
    }
}, false);


window.addEventListener('keyup', function(event) {
    if (event.keyCode == 27) {
      console.log("close");
      closeTopModal();
    }
}, false);
.modal {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background: rgba(0,0,0,0.2);
}
<div id="modalscontainer"></div>

用o键打开,用esc键关闭。