Video version if you want to see it.
Give it a like 😁
Alright let's see how to make a custom menu.
We are going to use vanilla javascript, but you can easily use this to make it on react or vue or other framework of your choosing.
This one is going to be short and sweet for those that just wants the result 😁
First we'll have the menu itself.
<body>
<div class="app" id="app">
<ul class="menu hide" id="menu">
<li class="menuItem"><span>🎵</span>Play</li>
<li class="Separator"></li>
<li class="menuItem"><span>⏭</span>Next</li>
<li class="menuItem"><span>⏹</span>Stop</li>
<li class="menuItem"><span>⏮</span>Last</li>
<li class="Separator"></li>
<li class="menuItem"><span>⏺</span>Record</li>
<li class="menuItem"><span>🔄</span>Reload</li>
<li class="Separator"></li>
<li class="menuItem"><span>🗑️</span>Discard</li>
</ul>
</div>
</body>
Then we define some helper functions and we select some elements in the DOM.
el = (e) => document.querySelector(e)
const app = el('#app')
const menu = el('#menu')
let menuActive = false
menu.classList.add('hide')
const showMenu = () => {
menu.classList.remove('hide')
menu.classList.add('show')
}
const hideMenu = () => {
menu.classList.add('hide')
menu.classList.remove('show')
}
We have a function to select elements, a flag for the state, then two functions one to show the menu and one to hide it, (we can do this with a toggle function but I wanted to make things more clear, so feel free to implement that)
Now lets see where the important stuff is:
const moveMenu = (event) => {
event.preventDefault()
console.log(event)
menuActive = true
showMenu()
let wHeight = window.innerHeight
let scrPosX = event.clientX + window.scrollX
let scrPosY = event.clientY + window.scrollY
let Y_window_offset = event.clientY + menu.clientHeight - wHeight
if (Y_window_offset > 0) {
scrPosY -= menu.clientHeight
}
menu.style.left = `${scrPosX}px`
menu.style.top = `${scrPosY}px`
return false
}
So what we are doing here:
- First we prevent the default operating system menu
- We set the menu state
menuActive
to active - We retrieve the windows inner height, this one is the height of the viewport
- Now we calculate the coordinates of the menu click and we offset them with the scroll from both Y and X axis
let scrPosX = event.clientX + window.scrollX
let scrPosY = event.clientY + window.scrollY
- Now this one
let Y_window_offset = event.clientY + menu.clientHeight - wHeight
is the difference between the bottom of the menu and the bottom of the screen, we'll use this to know if the menu is going offscreen at the bottom and we can shift it up - We do that when that offset is positive and we substract that offset with the height of the menu.
- Finally we set the left and top CSS positions of the menu elemnt to those coordinates, since the element is in position absolute it will move to those coordinates, note that the parent is in position relative.
Then we give this functions to the contextmenu
event listener and whoala:
app.addEventListener('contextmenu', moveMenu)
app.addEventListener('click', (event) => {
if (menuActive) {
hideMenu()
menuActive = false
}
})
We also add a event listener for a click in the app for hiding the menu.
So if you want to make this even better you can do the same shift we do for the bottom of the screen but at the right side too, because now the menu will go offscreen 😅
I will be making better content now that I have some better equipment, going to make Machine Learning, app development, Data science and General programming stuff content 😁
I'll appreciate if you give me a follow, also if you have time check out my youtube channel ramgendeploy drop a sub if you like there 😄
CSS for the braves (jk)
@import url("https://fonts.googleapis.com/css?family=Open+Sans&display=swap");
* {
box-sizing: content-box;
}
body {
margin: 0;
font-family: "Open Sans", sans-serif;
position: relative;
background: #151515;
}
.app {
background: #202020;
width: 80%;
margin: 0 auto;
height: fit-content;
color: white;
padding: 15px;
line-height: 30px;
}
.hide {
display: none;
}
.show {
display: block;
}
.menu {
margin: 0;
color: black;
border: solid black 1px;
position: absolute;
width: 250px;
background: white;
padding: 10px 0px;
border-radius: 4px;
}
.Separator {
display: block;
height: 1px;
background: #7e7b7b;
margin: 5px 10px;
user-select: none;
}
.menuItem {
display: grid;
grid-template-columns: 30px auto;
align-items: center;
padding: 7px 15px 7px 8px;
user-select: none;
cursor: pointer;
}
.menuItem:hover {
background: lightblue;
transition: 500ms;
}
.menuItem:active {
background: cadetblue;
transition: 500ms;
}
.imgL {
float: left;
padding: 10px;
}
.imgR {
float: right;
padding: 10px;
}
.slime {
position: -webkit-sticky;
position: sticky;
top: 0;
background: #202020;
padding: 10px;
border-bottom: 3px solid #303030;
}
Top comments (7)
I once implemented this functionality in a small personal webapp, where the menu items will change based on the element you're clicking. I used some custom properties on the elements and based on that the menu items will dynamically load on right click.
It was fun to build, and the info about css positioning is straight to the point. Informative article
Yea this is a really cool small project to start with javascript, did you use react or just javascript
I used HTML custom elements while I was learning about them at that time along with vanilla js
That's great, I want to do this on react but would be really similar maybe on a full app I'll do it
Awesome
Thanks! 😁
Thanks 😁