Make a Custom Right Click Menu with Javascript
Tags: beginners, javascript, webdev
Published: 2021-10-14 16:57:36

Video version if you want to see it.
Give it a like 😁
{%youtube ptKgni0vOwM%}

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.

```html
<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.

```javascript
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:
```javascript
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
```javascript
  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:
```javascript
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](https://www.youtube.com/c/ramgendeploy) drop a sub if you like there 😄

---

CSS for the braves (jk)
```CSS
@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;
}
```