Quiero hacer un área dentro de una página web donde al clickear tanto con el botón derecho como con el izquierdo aparezca un menú de items personalizado.
Perfecto, Gabriel 🙌. Lo que quieres es crear un menú contextual personalizado que aparezca al hacer clic (izquierdo o derecho) dentro de un área específica de tu página. Te muestro cómo hacerlo paso a paso con HTML, CSS y JavaScript.
📂 Ejemplo básico
HTML
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Menú personalizado</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="area">
Haz clic aquí (izquierdo o derecho)
</div>
<!-- Menú oculto -->
<ul id="menu">
<li>Opción 1</li>
<li>Opción 2</li>
<li>Opción 3</li>
</ul>
<script src="script.js"></script>
</body>
</html>
CSS (styles.css)
#area {
width: 300px;
height: 200px;
border: 2px dashed #007bff;
display: flex;
align-items: center;
justify-content: center;
margin: 50px auto;
cursor: pointer;
}
#menu {
position: absolute;
list-style: none;
margin: 0;
padding: 5px;
background: white;
border: 1px solid #ccc;
display: none; /* oculto por defecto */
}
#menu li {
padding: 8px 12px;
cursor: pointer;
}
#menu li:hover {
background: #007bff;
color: white;
}
JavaScript (script.js)
const area = document.getElementById("area");
const menu = document.getElementById("menu");
// Mostrar menú en clic izquierdo o derecho
area.addEventListener("click", mostrarMenu);
area.addEventListener("contextmenu", mostrarMenu);
// Ocultar menú al hacer clic fuera
document.addEventListener("click", (e) => {
if (!menu.contains(e.target) && e.target !== area) {
menu.style.display = "none";
}
});
function mostrarMenu(e) {
e.preventDefault(); // evita menú por defecto en clic derecho
menu.style.display = "block";
menu.style.left = e.pageX + "px";
menu.style.top = e.pageY + "px";
}
🔹 ¿Qué hace este código?
contextmenucaptura el clic derecho y evita el menú del navegador.clickcaptura el clic izquierdo.Ambos llaman a
mostrarMenu, que posiciona el menú en las coordenadas del clic.El menú se oculta si haces clic fuera de él.
✨ Posibles mejoras
Cambiar el contenido del menú según el contexto (ej. diferentes opciones si clicas en distintos elementos).
Animaciones para mostrar/ocultar el menú.
Integrar con frameworks como React o Vue para hacerlo más dinámico.
¿Quieres que te prepare una versión mejorada con animaciones y diferentes opciones según el botón (izquierdo vs derecho) para que veas cómo se puede sofisticar este menú?
Perfecto, Gabriel 🙌. Lo que planteas es un caso muy interesante: un menú contextual dinámico sobre un SVG, que muestre opciones distintas según el elemento clickeado, pero que también tenga un menú por defecto si se hace clic en un área vacía del SVG.
📂 Ejemplo completo
HTML
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Menú contextual en SVG</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<svg id="miSVG" width="400" height="300" style="border:1px solid #ccc;">
<!-- Elementos dentro del SVG -->
<circle id="circulo" cx="100" cy="150" r="40" fill="skyblue" />
<rect id="rectangulo" x="200" y="100" width="80" height="80" fill="tomato" />
</svg>
<!-- Menú contextual -->
<ul id="menu">
<!-- Items se llenan dinámicamente -->
</ul>
<script src="script.js"></script>
</body>
</html>
CSS (styles.css)
#menu {
position: absolute;
list-style: none;
margin: 0;
padding: 5px;
background: white;
border: 1px solid #ccc;
display: none;
min-width: 120px;
}
#menu li {
padding: 8px 12px;
cursor: pointer;
}
#menu li:hover {
background: #007bff;
color: white;
}
JavaScript (script.js)
const svg = document.getElementById("miSVG");
const menu = document.getElementById("menu");
// Definimos menús distintos según el target
const menus = {
circulo: ["Editar círculo", "Eliminar círculo"],
rectangulo: ["Editar rectángulo", "Eliminar rectángulo"],
default: ["Agregar elemento", "Propiedades generales"]
};
// Mostrar menú contextual
svg.addEventListener("contextmenu", (e) => {
e.preventDefault();
// Determinar sobre qué se hizo clic
const targetId = e.target.id;
const opciones = menus[targetId] || menus.default;
// Limpiar menú anterior
menu.innerHTML = "";
// Crear items dinámicamente
opciones.forEach(opcion => {
const li = document.createElement("li");
li.textContent = opcion;
li.addEventListener("click", () => {
alert(`Acción: ${opcion}`);
menu.style.display = "none";
});
menu.appendChild(li);
});
// Posicionar menú en coordenadas del clic
menu.style.display = "block";
menu.style.left = e.pageX + "px";
menu.style.top = e.pageY + "px";
});
// Ocultar menú al hacer clic fuera
document.addEventListener("click", (e) => {
if (!menu.contains(e.target)) {
menu.style.display = "none";
}
});
🔹 ¿Qué hace este código?
Captura el evento
contextmenu(clic derecho) sobre el SVG.Identifica si el clic fue sobre un elemento (
circle,rect) o sobre el área vacía del SVG.Según el
iddel elemento clickeado, carga un menú distinto.Si el clic es en un área vacía, muestra el menú por defecto.
Los items del menú se crean dinámicamente y pueden ejecutar acciones distintas.
✨ Posibles mejoras
Hacer que también funcione con clic izquierdo (
click) además del derecho.Añadir animaciones para mostrar/ocultar el menú.
Cambiar el contenido del menú según atributos del elemento (ej. color, tamaño).
Integrar con React/Vue para manejar estados más complejos.
¿Quieres que te prepare una versión extendida donde el menú cambie también con clic izquierdo y derecho (menús distintos según el botón)?
Perfecto, Gabriel 🙌. Te muestro cómo implementar un menú contextual dinámico sobre un SVG en React con TailwindCSS, que cambia según el elemento clickeado (círculo, rectángulo, o área vacía).
📂 Ejemplo con React + Tailwind
App.jsx
import { useState } from "react";
export default function App() {
const [menu, setMenu] = useState([]);
const [pos, setPos] = useState({ x: 0, y: 0 });
const [visible, setVisible] = useState(false);
const menus = {
circulo: ["Editar círculo", "Eliminar círculo"],
rectangulo: ["Editar rectángulo", "Eliminar rectángulo"],
default: ["Agregar elemento", "Propiedades generales"],
};
const handleContextMenu = (e) => {
e.preventDefault();
// Detectar el target dentro del SVG
const targetId = e.target.id;
const opciones = menus[targetId] || menus.default;
setMenu(opciones);
setPos({ x: e.pageX, y: e.pageY });
setVisible(true);
};
const handleClickOutside = () => {
setVisible(false);
};
return (
<div className="w-full h-screen bg-gray-100" onClick={handleClickOutside}>
<svg
id="miSVG"
width="400"
height="300"
className="border border-gray-400 mx-auto mt-10"
onContextMenu={handleContextMenu}
>
<circle id="circulo" cx="100" cy="150" r="40" fill="skyblue" />
<rect id="rectangulo" x="200" y="100" width="80" height="80" fill="tomato" />
</svg>
{visible && (
<ul
className="absolute bg-white border border-gray-300 rounded shadow-md min-w-[150px] z-50"
style={{ top: pos.y, left: pos.x }}
>
{menu.map((item, idx) => (
<li
key={idx}
className="px-4 py-2 hover:bg-blue-500 hover:text-white cursor-pointer"
onClick={() => {
alert(`Acción: ${item}`);
setVisible(false);
}}
>
{item}
</li>
))}
</ul>
)}
</div>
);
}
🔹 ¿Qué hace este código?
Usa React state para manejar:
menu: las opciones actuales.pos: coordenadas del clic.visible: si el menú está abierto o no.
Captura el evento
onContextMenuen el SVG.Según el
iddel elemento clickeado (circulo,rectanguloo vacío), carga un menú distinto.Renderiza el menú en la posición del clic usando estilos de TailwindCSS.
Se oculta al hacer clic fuera.
✨ Mejoras posibles
Diferenciar entre clic izquierdo y derecho (
onClickvsonContextMenu).Animaciones con Tailwind (
transition,transform,scale).Menús más complejos con subitems o íconos.
Integración con lógica real (ej. editar/eliminar elementos del SVG).
Así tienes un menú contextual totalmente dinámico y estilizado con Tailwind, que funciona tanto sobre elementos del SVG como en áreas vacías.
¿Quieres que te prepare también una versión con menús distintos para clic izquierdo y clic derecho sobre el SVG?
Perfecto, Gabriel 🙌. Vamos a armar un mini proyecto con Vite + React + TailwindCSS que tenga un menú contextual con submenús sobre un SVG. Así tendrás la estructura clara y el código listo para expandir.
📂 Estructura del proyecto con Vite
mi-proyecto/
├── index.html
├── package.json
├── vite.config.js
├── tailwind.config.js
├── postcss.config.js
├── /src
│ ├── main.jsx
│ ├── App.jsx
│ ├── /components
│ │ ├── ContextMenu.jsx
│ │ └── SubMenu.jsx
│ ├── /styles
│ │ └── index.css
│ └── /assets
│ └── logo.svg
🔹 Configuración inicial
Crear proyecto con Vite:
bashnpm create vite@latest mi-proyecto -- --template react cd mi-proyectoInstalar TailwindCSS:
bashnpm install -D tailwindcss postcss autoprefixer npx tailwindcss init -pConfigurar
tailwind.config.js:jsexport default { content: ["./index.html", "./src/**/*.{js,jsx}"], theme: { extend: {}, }, plugins: [], };Importar Tailwind en
src/styles/index.css:css@tailwind base; @tailwind components; @tailwind utilities;
🔹 Componente de Menú con Submenús
ContextMenu.jsx
import { useState } from "react";
import SubMenu from "./SubMenu";
export default function ContextMenu({ items, pos, visible, onClose }) {
if (!visible) return null;
return (
<ul
className="absolute bg-white border border-gray-300 rounded shadow-md min-w-[150px] z-50"
style={{ top: pos.y, left: pos.x }}
>
{items.map((item, idx) => (
<li
key={idx}
className="relative px-4 py-2 hover:bg-blue-500 hover:text-white cursor-pointer group"
onClick={() => {
if (!item.submenu) {
alert(`Acción: ${item.label}`);
onClose();
}
}}
>
{item.label}
{item.submenu && (
<SubMenu items={item.submenu} />
)}
</li>
))}
</ul>
);
}
SubMenu.jsx
export default function SubMenu({ items }) {
return (
<ul className="absolute left-full top-0 bg-white border border-gray-300 rounded shadow-md min-w-[150px] hidden group-hover:block">
{items.map((subItem, idx) => (
<li
key={idx}
className="px-4 py-2 hover:bg-green-500 hover:text-white cursor-pointer"
onClick={() => alert(`Acción: ${subItem.label}`)}
>
{subItem.label}
</li>
))}
</ul>
);
}
App.jsx
import { useState } from "react";
import ContextMenu from "./components/ContextMenu";
export default function App() {
const [menuItems, setMenuItems] = useState([]);
const [pos, setPos] = useState({ x: 0, y: 0 });
const [visible, setVisible] = useState(false);
const menus = {
circulo: [
{ label: "Editar círculo" },
{ label: "Más opciones", submenu: [
{ label: "Cambiar color" },
{ label: "Cambiar tamaño" }
]}
],
rectangulo: [
{ label: "Editar rectángulo" },
{ label: "Más opciones", submenu: [
{ label: "Rotar" },
{ label: "Duplicar" }
]}
],
default: [
{ label: "Agregar elemento" },
{ label: "Propiedades generales" }
]
};
const handleContextMenu = (e) => {
e.preventDefault();
const targetId = e.target.id;
const opciones = menus[targetId] || menus.default;
setMenuItems(opciones);
setPos({ x: e.pageX, y: e.pageY });
setVisible(true);
};
const handleClickOutside = () => setVisible(false);
return (
<div className="w-full h-screen bg-gray-100" onClick={handleClickOutside}>
<svg
id="miSVG"
width="400"
height="300"
className="border border-gray-400 mx-auto mt-10"
onContextMenu={handleContextMenu}
>
<circle id="circulo" cx="100" cy="150" r="40" fill="skyblue" />
<rect id="rectangulo" x="200" y="100" width="80" height="80" fill="tomato" />
</svg>
<ContextMenu
items={menuItems}
pos={pos}
visible={visible}
onClose={handleClickOutside}
/>
</div>
);
}
✨ Resultado
Al hacer clic derecho sobre el círculo o el rectángulo, aparece un menú con opciones específicas.
Algunos ítems tienen submenús que se despliegan al pasar el mouse.
Si haces clic en un área vacía del SVG, aparece el menú por defecto.
Todo estilizado con TailwindCSS y estructurado para un proyecto con Vite.
¿Quieres que te prepare también una versión con menús distintos para clic izquierdo y derecho (ej. menú de edición vs menú de acciones rápidas)?
No hay comentarios:
Publicar un comentario