I'm on my way to learn a bit of CSS and today I've found myself wanting to implement a similar context menu (the one it's shown when you the vertical elipsis on youtube cards is clicked) but my attempt didn't make it very far :D . Here's what I've got:
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
.container {
display: inline-block;
position: relative;
}
.dropdown {
position: absolute;
right: 1rem;
top: 1rem;
}
.dropdown-opener {
cursor: pointer;
user-select: none;
position: absolute;
right: 0;
}
.dropdown .dropdown-toggle,
.dropdown .dropdown-menu {
display: none;
style-type: none;
}
.dropdown .dropdown-toggle:checked+ul {
display: block;
}
.style-scope .menu-renderer {
--layout-inline_-_display: inline-flex;
--icon-button-icon-height: 24px;
--icon-button-icon-width: 24px;
--spec-icon-active-other: #606060;
--spec-icon-inactive: #909090;
--spec-text-disabled: #909090;
--spec-text-secondary: #606060;
align-items: var(--layout-center-center_-_align-items);
color: var(--menu-renderer-button-color, var(--spec-icon-inactive));
cursor: pointer;
display: var(--layout-inline_-_display);
fill: var(--iron-icon-fill-color, currentcolor);
width: var(--icon-button-icon-width, 100%);
background: transparent;
}
</style>
</head>
<body>
<div class="container">
<img alt="sample" src="https://via.placeholder.com/200x200">
<nav class="dropdown layer--topright">
<label class="dropdown-opener" for="menu-opener1">...</label>
<input class="dropdown-toggle" id="menu-opener1" type="checkbox">
<ul class="dropdown-menu">
<li>Foo1</li>
<li>Bar1</li>
<li>Baz1</li>
</ul>
</nav>
</div>
<div class="container">
<img alt="sample" src="https://via.placeholder.com/200x200">
<nav class="dropdown layer--topright">
<label class="dropdown-opener" for="menu-opener1">...</label>
<input class="dropdown-toggle" id="menu-opener1" type="checkbox">
<ul class="dropdown-menu">
<li>Foo2</li>
<li>Bar2</li>
<li>Baz2</li>
</ul>
</nav>
</div>
<div class="container">
<img alt="sample" src="https://via.placeholder.com/200x200">
<nav class="dropdown layer--topright">
<icon-button id="button" class="dropdown-opener dropdown-trigger style-scope menu-renderer">
<button id="button" class="style-scope icon-button" aria-label="Action menu">
<icon class="style-scope menu-renderer">
<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" focusable="false" class="style-scope icon" style="pointer-events: none; display: block; width: 100%; height: 100%;">
<g class="style-scope icon">
<path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" class="style-scope icon"></path>
</g>
</svg>
</icon>
</button>
</icon-button>
<input class="dropdown-toggle" id="menu-opener2" type="checkbox">
<ul class="dropdown-menu">
<li>Foo3</li>
<li>Bar3</li>
<li>Baz3</li>
</ul>
</nav>
</div>
</body>
</html>
As you can see the code is dirty and broken... but there are major problems with my attempts I didn't know how to fix:
So if you could explain how to address those ones in order to make something usable out of my attempt that'd be awesome.
Or if my approach is all wrong, unusable and ready to throw to the bin... could you please explain what'd the best to achieve my goal?
Thanks in advance.
Misplaced context menus when clicking the overflow menu button. The context menus are not actually misplaced. The for
attribute in your <label>
element refers to the wrong context menus. For example, the for
value of your <label>
element in the first .container
has a value of menu-opener1
, but the for
value on the <label>
element in the second .container
has the exact same value. Clicking either label causes the dropdown menu in the first container to be opened because both labels cause the hidden checkbox on the first container to be checked.
What can we do? Simply change the id
value so that each dropdown menu has a unique id value. Then, use that id value for the for
value inside your <label>
element.
To hide the bulletins of li
elements inside a ul
, you have to use list-style-type: none
on your CSS and not style-type: none
.
This is a very subjective matter. A design can look clean to one but look unclean to others. Nevertheless, I tried to achieve what I wanted to see. Here are some things that you can change to improve the design aspect.
li
element. Here, I used line-height
. You can also use padding
or margin
on each li
element.box-shadow
to show elevation. Google's Material Design recommends using this technique to show that an element's z-position is higher.
visibility
and display
). Here, I chose to use transform: scale
and opacity
transition.Semantically, your HTML tags are incorrect.
<nav>
(navigation) element is used to navigate between pages. Here, you should probably use <menu>
element instead. However, as it is still experimental, I chose to use <section>
.<icon-button>
and <icon>
). Try consulting here for valid HTML tags and here for valid SVG tags..container
item has a button that will check the hidden checkbox. However, the <button>
element does not work with <label>
element. So, try to make the <label>
element visually look like a button instead. You can use :active
and :hover
CSS pseudoselectors to change the button style when it is pressed and hovered respectively. Furthermore, this reduces nesting..container
to contain the whole page, so I have opted to use the class name .box
instead of .container
.Here's the runnable snippet.
* {
font-family: Helvetica;
box-sizing: border-box;
}
.box {
display: inline-block;
position: relative;
}
.dropdown {
position: absolute;
right: 0;
top: 8px;
}
.dropdown-opener {
cursor: pointer;
user-select: none;
position: absolute;
width: 24px;
height: 24px;
background: url('https://i.imgur.com/Qt3Qwgp.png');
background-repeat: no-repeat;
background-position: right;
right: 0;
}
.dropdown .dropdown-toggle {
display: none;
}
.dropdown .dropdown-menu {
list-style-type: none;
transform: scale(0);
opacity: 0;
transition:
transform 0.25s ease,
opacity 0.25s ease;
position: absolute;
top: 1.5em;
right: 10px;
line-height: 1.75em;
background: white;
box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.5);
border-radius: 5px;
padding: 20px;
margin: 0;
transform-origin: top right;
}
.dropdown .dropdown-toggle:checked + ul {
transform: scale(1);
opacity: 1;
}
.dropdown-opener-button {
position: absolute;
width: 24px;
height: 24px;
right: 8px;
top: 0;
}
.icon-button {
padding: 0;
border: 0;
border-radius: 5px;
cursor: pointer;
transition:
box-shadow .25s ease,
background .25s ease,
transform .25s ease;
background: #ffffffdd;
}
.icon-button:hover {
box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.35);
}
.icon-button:active {
background: #ffffff77;
transform: scale(0.9);
}
.icon-button ~ .dropdown-menu {
top: 1.75em;
}
<div class="box">
<img alt="sample" src="https://via.placeholder.com/200x200">
<section class="dropdown layer--topright">
<label class="dropdown-opener" for="menu-opener1"></label>
<input class="dropdown-toggle" id="menu-opener1" type="checkbox">
<ul class="dropdown-menu">
<li>Foo1</li>
<li>Bar1</li>
<li>Baz1</li>
</ul>
</section>
</div>
<div class="box">
<img alt="sample" src="https://via.placeholder.com/200x200">
<section class="dropdown layer--topright">
<label class="dropdown-opener" for="menu-opener2"></label>
<input class="dropdown-toggle" id="menu-opener2" type="checkbox">
<ul class="dropdown-menu">
<li>Foo2</li>
<li>Bar2</li>
<li>Baz2</li>
</ul>
</section>
</div>
<div class="box">
<img alt="sample" src="https://via.placeholder.com/200x200">
<section class="dropdown layer--topright">
<label class="dropdown-opener-button icon-button" for="menu-opener3">
<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" focusable="false" class="style-scope icon">
<g class="style-scope icon">
<path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" class="style-scope icon"></path>
</g>
</svg>
</label>
<input class="dropdown-toggle" id="menu-opener3" type="checkbox">
<ul class="dropdown-menu">
<li>Foo3</li>
<li>Bar3</li>
<li>Baz3</li>
</ul>
</section>
</div>
Not having a crystal-clear understanding of what the OP meant by how hard would it be to make the menu items change the state when hovering with the mouse, I decided to create an effect on hovered and a different effect (ripple) on click for the <li>
elements. I recommend reading this writing on creating a ripple effect.
Also, as per request, the functionality to hide the menus when a click outside of the menu's box has been added. Here's the runnable snippet.
// Closing menu on outside click
const outsideClickListener = event => {
let checkedToggle = document.querySelector('.dropdown-toggle:checked')
let openedMenu = document.querySelector('.dropdown-toggle:checked + .dropdown-menu')
// If click is performed on checkbox (through label), do nothing
if (event.target.classList.contains('dropdown-toggle')) {
return
}
// If click is performed on label, uncheck all other dropdown-toggle
if (event.target.classList.contains('dropdown-opener') ||
event.target.classList.contains('dropdown-opener-button')) {
let forId = event.target.getAttribute('for')
document.querySelectorAll('.dropdown-toggle').forEach(toggle => {
if (forId !== toggle.getAttribute('id'))
toggle.checked = false
})
return
}
// If click is performed outside opened menu
if (openedMenu && !openedMenu.contains(event.target)) {
checkedToggle.checked = false
}
}
document.addEventListener('click', outsideClickListener)
// Ripple effect on li elements
const createRipple = event => {
let li = event.target
let liBox = li.getBoundingClientRect()
let x = event.pageX - liBox.left
let y = event.pageY - liBox.top
let animDuration = 350
let animationStart, animationFrame
let animationStep = timestamp => {
if (!animationStart) animationStart = timestamp
let frame = timestamp - animationStart
if (frame < animDuration) {
let easing = (frame / animDuration) * (2 - (frame / animDuration))
let circle = `circle at ${x}px ${y}px`
let color = `rgba(0, 0, 0, ${0.2 * (1 - easing)})`
let stop = `${100 * easing}%`
li.style.backgroundImage = `radial-gradient(${circle}, ${color} ${stop}, transparent ${stop})`
animationFrame = window.requestAnimationFrame(animationStep)
}
else {
li.style.backgroundImage = ''
window.cancelAnimationFrame(animationStep)
}
}
animationFrame = window.requestAnimationFrame(animationStep)
}
const listItems = document.querySelectorAll('li')
listItems.forEach(li => {
li.addEventListener('click', createRipple)
})
* {
font-family: Helvetica;
box-sizing: border-box;
}
.box {
display: inline-block;
position: relative;
}
.dropdown {
position: absolute;
right: 0;
top: 8px;
}
.dropdown-opener {
cursor: pointer;
user-select: none;
position: absolute;
width: 24px;
height: 24px;
background: url('https://i.imgur.com/Qt3Qwgp.png');
background-repeat: no-repeat;
background-position: right;
right: 0;
}
.dropdown .dropdown-toggle {
display: none;
}
.dropdown .dropdown-menu {
list-style-type: none;
transform: scale(0);
opacity: 0;
transition:
transform 0.25s ease,
opacity 0.25s ease;
position: absolute;
top: 1.5em;
right: 10px;
background: white;
box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.5);
border-radius: 5px;
margin: 0;
transform-origin: top right;
padding: 7.5px 0 7.5px 0;
}
.dropdown .dropdown-toggle:checked + ul {
transform: scale(1);
opacity: 1;
}
.dropdown-menu li {
padding: 7.5px;
padding-left: 25px;
cursor: pointer;
transition: background .15s ease;
}
.dropdown-menu li:hover {
background: #00000012;
}
.dropdown-opener-button {
position: absolute;
width: 24px;
height: 24px;
right: 8px;
top: 0;
}
.dropdown-opener-button svg {
pointer-events: none;
}
.icon-button {
padding: 0;
border: 0;
border-radius: 5px;
cursor: pointer;
transition:
box-shadow .25s ease,
background .25s ease,
transform .25s ease;
background: #ffffffdd;
}
.icon-button:hover {
box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.35);
}
.icon-button:active {
background: #ffffffaa;
box-shadow: 0px 0px 4px 0px rgba(0,0,0,0.35);
transform: scale(0.9);
}
.icon-button ~ .dropdown-menu {
top: 1.75em;
}
<div class="box">
<img alt="sample" src="https://via.placeholder.com/200x200">
<section class="dropdown layer--topright">
<label class="dropdown-opener" for="menu-opener1"></label>
<input class="dropdown-toggle" id="menu-opener1" type="checkbox">
<ul class="dropdown-menu">
<li>Foo1</li>
<li>Bar1</li>
<li>Baz1</li>
</ul>
</section>
</div>
<div class="box">
<img alt="sample" src="https://via.placeholder.com/200x200">
<section class="dropdown layer--topright">
<label class="dropdown-opener" for="menu-opener2"></label>
<input class="dropdown-toggle" id="menu-opener2" type="checkbox">
<ul class="dropdown-menu">
<li>Foo2</li>
<li>Bar2</li>
<li>Baz2</li>
</ul>
</section>
</div>
<div class="box">
<img alt="sample" src="https://via.placeholder.com/200x200">
<section class="dropdown layer--topright">
<label class="dropdown-opener-button icon-button" for="menu-opener3">
<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" focusable="false" class="style-scope icon">
<g class="style-scope icon">
<path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" class="style-scope icon"></path>
</g>
</svg>
</label>
<input class="dropdown-toggle" id="menu-opener3" type="checkbox">
<ul class="dropdown-menu">
<li>Foo3</li>
<li>Bar3</li>
<li>Baz3</li>
</ul>
</section>
</div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With