This is the 7th day of my participation in the August More Text Challenge
This article explains how Web pages can implement custom menu functionality.
Viewing online Demo
Train of thought
The core idea is: Register the ContextMenu event, cancel the default behavior of the event, and use the Event object to get the coordinates of the cursor relative to the viewport (event.clientX and Event.clienty). Display your own custom div blocks that are not visible at initialization.
implementation
DOM structure
The first is the DOM structure. The structure is as follows:
div.page-view
Is the element that registers the ContextMenu event;div.contextmenu-mask
It’s a mask. It covers the whole window. It appears along with the right menu, the function is to prevent users from calling up the right menu, but also can click the button outside the menu. You can also add a background color that is transparent, but this is similar to a popover. Generally speaking, is not set background color.div.contextmenu-content
Right-click the contents of the menu.
<div class="page-view">Click on the area</div>
<div class="contextmenu-mask" style="display: none;"></div>
<div class="contextmenu-content">
<div class="list">
<div class="item">copy</div>
<div class="item">shear</div>
<div class="item">Paste paste paste paste paste paste paste paste</div>
<div class="item">select all</div>
</div>
</div>
Copy the code
CSS styles
.page-view {
margin: 0 auto;
width: 90%;
height: calc(100vh - 30px);
background-color: azure;
}
/* Mask layer */
.contextmenu-mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
/* background-color: #000; * /
/* opacity: .2; * /
z-index: 45;
}
/* Container for menu contents */
.contextmenu-content {
position: fixed;
left: 999999px;
top: 999999px;
z-index: 50;
user-select: none;
}
/* Examples use content */
.list {
border: 1px solid # 555;
border-radius: 4px;
min-width: 180px;
overflow: hidden; /* Handle rounded corners */
}
.item {
box-sizing: border-box;
padding: 0 5px;
height: 30px;
line-height: 30px;
word-break: keep-all; /* is important, otherwise line breaks */
background-color: #fff;
cursor: default;
}
.item:hover {
background-color: dodgerblue;
color: #fff;
}
Copy the code
Here are a few caveats:
.contextmenu-content
It’s not useddisplay: none
Instead, run away from the window by setting very large left and top. There are reasons for this, which we’ll explain in more detail in the script logic later..item
You need to set upword-break: keep-all;
. Because when the menu goes out of the window, the width becomes the minimum width, in this case 180px. Only when this property and value are set will the text not wrap and have the desired width..contextmenu-content
Fixed positioning is required, not absolute positioning. Because the left and top elements are set to large values, nooverflow: hidden
The container element produces a very long scroll bar. Fixed positioning does not.
Script logic
Right click to display menu
Start by undoing the default behavior for menu events in the click area.
Get the coordinates of the cursor, and adjust the coordinates to prevent the menu part from running out of the window and being cut. To do this, we need to get the width of the menu and the width of the window viewable area. In addition, in order to prevent the edge of the menu from clinging to the edge of the window, it is necessary to set a minimum padding value for calculation.
Truncated menu:
Menu attached to the edge of the window:
Take setting the abscissa as an example:
if (e.clientX + contextmenuWidth > document.documentElement.clientWidth - PADDING_RIGHT) {
finalX = e.clientX - contextmenuWidth
}
Copy the code
When the prediction finds that the current cursor is on the left side of the menu, part of the right side of the menu will be cut, so the current coordinate is taken as the right side of the menu, and the coordinate of the upper left corner is the value of the cursor minus the width of the menu.
The complete code is:
const areaEl = document.querySelector('.page-view')
const mask = document.querySelector('.contextmenu-mask')
const contentEl = document.querySelector('.contextmenu-content')
/ * * * *@param {number} X The upper-left coordinate of the menu to be set x *@param {number} Y top left corner y star@param {number} W menu width *@param {number} H Menu height *@returns {x, y} Adjusted coordinates */
const adjustPos = (x, y, w, h) = > {
const PADDING_RIGHT = 6 // Leave some space on the right side to prevent direct edge, which is not good
const PADDING_BOTTOM = 6 // Leave some space at the bottom
const vw = document.documentElement.clientWidth
const vh = document.documentElement.clientHeight
if (x + w > vw - PADDING_RIGHT) x -= w
if (y + h > vh - PADDING_BOTTOM) y -= h
return {x, y}
}
const onContextMenu = e= > {
e.preventDefault()
const rect = contentEl.getBoundingClientRect()
// console.log(rect)
const { x, y } = adjustPos(e.clientX, e.clientY, rect.width, rect.height)
showContextMenu(x, y)
}
// Block menu events under the specified element
areaEl.addEventListener('contextmenu', onContextMenu, false)
Copy the code
Hide the right-click menu without using the normal display: None; Instead, use left and top with very high values. This is because I want to implement an adaptive width and height right-click menu. Therefore need menu width is high, the dynamic Element needed. GetBoundingClientRect () method, this method needs Element in the DOM tree, and as the visible elements, to get high wide, otherwise can only get two 0.
If you are implementing a menu whose width is manually written and whose height is measured by the number of menu items, the best way to hide the menu is display: None.
Hide the menu and click the menu item
Then click on the mask layer to hide the menu and mask. And click the menu item to execute the corresponding command
const hideContextMenu = () = > {
mask.style.display = 'none'
contentEl.style.top = '99999px'
contentEl.style.left = '99999px'
}
// Click mask to hide
mask.addEventListener('mousedown'.() = > {
hideContextMenu()
}, false)
// Click menu to hide
contentEl.addEventListener('click'.(e) = > {
console.log('Click:', e.target.textContent)
// Run the command corresponding to the menu item
hideContextMenu()
}, false)
Copy the code
Other things to consider
- Window shrinking situation: window shrinking will cause the menu at the bottom right to run outside the window area, whether to consider monitoring window events.
- The logic of re-clicking the right button on the menu: Repositioning the right button in this position is the same as clicking the left button, or not processing, the browser’s native right button menu is displayed, you need to select according to your requirements.
At the end
The logic of implementing a custom menu is not complicated, that is, modifying the behavior of the ContextMenu event to show or hide the divs you wrote. But there are some details that need to be worked out to make a good, bug-free right-click menu.