In this blog, we’ll look at how to make a Custom Right-Click (Context) Menu. This Custom Right-Click (Context) Menu is built with HTML, CSS, and Javascript. Let’s start with a simple HTML structure for a Custom Right-Click (Context) Menu in the hopes that you enjoy our blog.
HTML code
<div class="video-bg"> <video width="100%" height="100%" muted autoplay loop> <source src="https://assets.codepen.io/344846/cm.mp4" type="video/mp4"> </video> </div> <div class="right-click">Right-click anywhere</div> <div class="target-light target">Light Menu</div> <div class="target-dark target">Dark Menu</div>
The HTML code for the Custom Right-Click (Context) Menu may be found here. You may see the result without CSS now, and then we’ll develop the Custom Right-Click (Context) Menu in CSS and Javascript. The HTML code for the Custom Right-Click (Context) Menu may be found here. You may see the result without CSS now, and then we’ll develop the Custom Right-Click (Context) Menu in CSS and Javascript.
OUTPUT
CSS Code
@import url("https://fonts.googleapis.com/css2?family=Inter&display=swap"); * { box-sizing: border-box; font-family: "Inter", sans-serif; } html, body { width: 100%; height: 100%; overflow: hidden; } .video-bg { width: 100%; height: 100%; pointer-events: none; } .video-bg video { width: 100%; height: 100%; object-fit: cover; } .target { width: 50%; height: 100%; position: absolute; top: 0; z-index: 1; display: flex; align-items: center; justify-content: center; color: rgba(255, 255, 255, 0.5); font-size: 2vw; } .target-light { left: 0; } .target-dark { right: 0; } body { width: 100%; height: 100%; background-color: #000; overflow: hidden; } .right-click { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 2; pointer-events: none; padding: 2vw; border-radius: 1vw; font-size: 2.4vw; background-color: #fff; } /* Context Menu */ .contextMenu { --menu-border: rgba(255, 255, 255, 0.08); --menu-bg: linear-gradient(45deg, rgba(10, 20, 28, 0.2) 0%, rgba(10, 20, 28, 0.7) 100%); --item-border: rgba(255, 255, 255, 0.1); --item-color: #fff; --item-bg-hover: rgba(255, 255, 255, 0.1); height: 0; overflow: hidden; background: var(--menu-bg); backdrop-filter: blur(5px); position: fixed; top: var(--top); left: var(--left); animation: menuAnimation 0.4s 0s both; transform-origin: left; list-style: none; margin: 4px; padding: 0; display: flex; flex-direction: column; z-index: 999999999; box-shadow: 0 0 0 1px var(--menu-border), 0 2px 2px #000, 0 4px 4px #000, 0 10px 8px #000, 0 15px 15px #000, 0 30px 30px #000, 0 70px 65px #000; } .contextMenu-item { padding: 4px; } .contextMenu-item[data-divider="top"] { border-top: 1px solid; } .contextMenu-item[data-divider="bottom"] { border-bottom: 1px solid; } .contextMenu-item[data-divider="top-bottom"] { border-top: 1px solid; border-bottom: 1px solid; } .contextMenu-item[data-divider] { border-color: var(--item-border); } .contextMenu-button { color: var(--item-color); background: 0; border: 0; white-space: nowrap; width: 100%; border-radius: 4px; padding: 6px 24px 6px 7px; text-align: left; display: flex; align-items: center; font-size: 14px; width: 100%; animation: menuItemAnimation 0.2s 0s both; font-family: "Inter", sans-serif; cursor: pointer; } .contextMenu-button:hover { background-color: var(--item-bg-hover); } .contextMenu[data-theme="light"] { --menu-bg: linear-gradient(45deg, rgba(255, 255, 255, 0.45) 0%, rgba(255, 255, 255, 0.85) 100%); --menu-border: rgba(0, 0, 0, 0.08); --item-border: rgba(0, 0, 0, 0.1); --item-color: #0a141c; --item-bg-hover: rgba(10, 20, 28, 0.09); } @keyframes menuAnimation { 0% { opacity: 0; transform: scale(0.5); } 100% { height: var(--height); opacity: 1; border-radius: 8px; transform: scale(1); } } @keyframes menuItemAnimation { 0% { opacity: 0; transform: translateX(-10px); } 100% { opacity: 1; transform: translateX(0); } }
Here is our updated output CSS.
Javascript code
class ContextMenu { constructor({ target = null, menuItems = [], mode = "dark" }) { this.target = target; this.menuItems = menuItems; this.mode = mode; this.targetNode = this.getTargetNode(); this.menuItemsNode = this.getMenuItemsNode(); this.isOpened = false; } getTargetNode() { const nodes = document.querySelectorAll(this.target); if (nodes && nodes.length !== 0) { return nodes; } else { console.error(`getTargetNode :: "${this.target}" target not found`); return []; } } getMenuItemsNode() { const nodes = []; if (!this.menuItems) { console.error("getMenuItemsNode :: Please enter menu items"); return []; } this.menuItems.forEach((data, index) => { const item = this.createItemMarkup(data); item.firstChild.setAttribute( "style", `animation-delay: ${index * 0.08}s` ); nodes.push(item); }); return nodes; } createItemMarkup(data) { const button = document.createElement("BUTTON"); const item = document.createElement("LI"); button.innerHTML = data.content; button.classList.add("contextMenu-button"); item.classList.add("contextMenu-item"); if (data.divider) item.setAttribute("data-divider", data.divider); item.appendChild(button); if (data.events && data.events.length !== 0) { Object.entries(data.events).forEach((event) => { const [key, value] = event; button.addEventListener(key, value); }); } return item; } renderMenu() { const menuContainer = document.createElement("UL"); menuContainer.classList.add("contextMenu"); menuContainer.setAttribute("data-theme", this.mode); this.menuItemsNode.forEach((item) => menuContainer.appendChild(item)); return menuContainer; } closeMenu(menu) { if (this.isOpened) { this.isOpened = false; menu.remove(); } } init() { const contextMenu = this.renderMenu(); document.addEventListener("click", () => this.closeMenu(contextMenu)); window.addEventListener("blur", () => this.closeMenu(contextMenu)); document.addEventListener("contextmenu", (e) => { this.targetNode.forEach((target) => { if (!e.target.contains(target)) { contextMenu.remove(); } }); }); this.targetNode.forEach((target) => { target.addEventListener("contextmenu", (e) => { e.preventDefault(); this.isOpened = true; const { clientX, clientY } = e; document.body.appendChild(contextMenu); const positionY = clientY + contextMenu.scrollHeight >= window.innerHeight ? window.innerHeight - contextMenu.scrollHeight - 20 : clientY; const positionX = clientX + contextMenu.scrollWidth >= window.innerWidth ? window.innerWidth - contextMenu.scrollWidth - 20 : clientX; contextMenu.setAttribute( "style", `--width: ${contextMenu.scrollWidth}px; --height: ${contextMenu.scrollHeight}px; --top: ${positionY}px; --left: ${positionX}px;` ); }); }); } } const copyIcon = `<svg viewBox="0 0 24 24" width="13" height="13" stroke="currentColor" stroke-width="2.5" style="margin-right: 7px" fill="none" stroke-linecap="round" stroke-linejoin="round" class="css-i6dzq1"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`; const cutIcon = `<svg viewBox="0 0 24 24" width="13" height="13" stroke="currentColor" stroke-width="2.5" style="margin-right: 7px" fill="none" stroke-linecap="round" stroke-linejoin="round" class="css-i6dzq1"><circle cx="6" cy="6" r="3"></circle><circle cx="6" cy="18" r="3"></circle><line x1="20" y1="4" x2="8.12" y2="15.88"></line><line x1="14.47" y1="14.48" x2="20" y2="20"></line><line x1="8.12" y1="8.12" x2="12" y2="12"></line></svg>`; const pasteIcon = `<svg viewBox="0 0 24 24" width="13" height="13" stroke="currentColor" stroke-width="2.5" style="margin-right: 7px; position: relative; top: -1px" fill="none" stroke-linecap="round" stroke-linejoin="round" class="css-i6dzq1"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>`; const downloadIcon = `<svg viewBox="0 0 24 24" width="13" height="13" stroke="currentColor" stroke-width="2.5" style="margin-right: 7px; position: relative; top: -1px" fill="none" stroke-linecap="round" stroke-linejoin="round" class="css-i6dzq1"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>`; const deleteIcon = `<svg viewBox="0 0 24 24" width="13" height="13" stroke="currentColor" stroke-width="2.5" fill="none" style="margin-right: 7px" stroke-linecap="round" stroke-linejoin="round" class="css-i6dzq1"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>`; const menuItems = [ { content: `${copyIcon}Copy`, events: { click: (e) => console.log(e, "Copy Button Click") // mouseover: () => console.log("Copy Button Mouseover") // You can use any event listener from here } }, { content: `${pasteIcon}Paste` }, { content: `${cutIcon}Cut` }, { content: `${downloadIcon}Download` }, { content: `${deleteIcon}Delete`, divider: "top" // top, bottom, top-bottom } ]; const light = new ContextMenu({ target: ".target-light", mode: "light", // default: "dark" menuItems }); light.init(); const dark = new ContextMenu({ target: ".target-dark", menuItems }); dark.init(); // remove message function removeMessage() { const message = document.querySelector(".right-click"); if (message) message.remove(); } window.addEventListener("click", removeMessage); window.addEventListener("contextmenu", removeMessage);
Final output
Now that we’ve finished our javascript portion, here’s our revised javascript output. I hope the Custom Right-Click (Context) Menu is useful to you. You may watch the output video as well as screenshots from the project. Check out our other blogs to learn more about front-end development.
I hope you guys understand how I can do this. Let me know if you face any difficulties.
You can watch my previous blog here.
Happy Coding {;}