Awesome Context Menu
Создание контекстного меню по клику на правую кнопку мыши.
Сначала структура.
Наше меню, по структуре, это обычный список <ul>
.
<ul class="context">
<li class="context__header">Заголовок</li>
<li class="context__item"><a href="#">Пункт меню</a></li>
<li class="context__item context__item--nope"><a href="#">Неактивный пункт меню</a></li>
<li class="context__item"><a href="#">Добавить в Pin</a></li>
<li class="context__divider"></li>
<li class="context__item context__item--email"><a href="#">Пункт меню с подменю</a><i class="context-right-open"></i>
<ul class="context context--sub">
<li class="context__item"><a href="#">Пункт меню подменю</a></li>
<li class="context__item"><a href="#">Пункт меню подменю</a></li>
</ul>
</li>
</ul>
<li class="context__divider"></li>
разделитель между пунктами меню.
<i class="context-right-open"></i>
иконка, показывающая что меню содержит подменю.
Классом context__item--nope
можно отметить временно неактивный пункт меню.
Теперь перейдём с стилям.
Разбирать каждый пункт не буду, просто приведу код целиком:
.context {
font-size: 14px;
color: #D0C7FF;
list-style: none;
margin: 0;
padding: 0.05em 0.25em;
border: 1px solid transparent;
border-right-color: #5062c2;
border-bottom-color: #5062c2;
border-radius: 3px;
position: absolute;
min-width: 16em;
z-index: 1;
background: linear-gradient(145deg, #673AB7, #3F51B5);
box-shadow: 0 4px 14px -5px #141321;
will-change: transform, opacity;
transition: transform, opacity, visibility;
transition-duration: 0.35s, 0.2s, 0s;
transition-delay: 0.08s, 0s, 0.4s;
transition-timing-function: ease;
transform: rotate3d(-1, -1, 0, 25deg) scale(1);
transform-origin: 0 0;
opacity: 0;
visibility: hidden;
}
.context, .context * {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: default;
}
.context.is-visible {
opacity: 1;
transform: none;
transition-delay: 0s, 0s, 0s;
visibility: visible;
}
.context--sub {
background: #4b4ab6;
width: auto;
min-width: 10em;
left: 100%;
top: -0.4em;
transform: translateX(-0.7em);
transition: transform, opacity, width, min-width, visibility;
transition-timing-function: ease;
transition-duration: 0.3s, 0.25s, 0.15s, 0.15s, 0.01s;
transition-delay: 0.3s, 0.25s, 0.3s, 0.3s, 0.35s;
overflow: hidden;
}
.context--sub.oppositeX {
right: 100%;
left: auto;
transform: translateX(0.7em);
}
.context--sub.oppositeY {
top: auto;
bottom: -0.4em;
}
.context__header, .context__item {
padding-left: 1.5em;
padding-right: 1.5em;
padding-top: 0.3em;
padding-bottom: 0.35em;
}
.context__header, .context__divider {
margin-top: 0.25em;
margin-bottom: 0.25em;
border-bottom: 1px solid rgba(208, 199, 255, 0.3);
}
.context__header {
font-weight: 700;
padding-bottom: 0.5em;
}
.context__item {
border-radius: 3px;
position: relative;
}
.context__item:not(.context__item--nope):hover {
background-color: rgba(255, 255, 255, 0.09);
color: white;
}
.context__item:not(.context__item--nope):hover .context--sub {
opacity: 1;
transform: translateX(0);
transition-delay: 0.2s, 0.25s, 0.2s, 0.2s, 0s;
border-radius: 0 3px 3px 3px;
visibility: visible;
}
.context__item:last-child {
margin-bottom: 0.25em;
}
.context__item:first-child {
margin-top: 0.25em;
}
.context__item--nope {
color: rgba(255, 255, 255, 0.3);
pointer-events: none;
}
.context__item--active {
-webkit-animation: flash 0.5s ease 1;
animation: flash 0.5s ease 1;
}
.context a {
cursor: default;
color: inherit;
text-decoration: none;
display: block;
}
.context-right-open {
background: url('data:image/svg+xml,%3Csvg xmlns="http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 15 15"%3E%3Cpath fill="rgba(255, 255, 255, 0.3)" d="M10.207 7.5L6 3.293v8.414L10.207 7.5Z"%2F%3E%3C%2Fsvg%3E') no-repeat 0 0%;
position: absolute;
width: 16px;
height: 16px;
transform: translateY(-55%);
right: 0;
top: 50%;
}
@-webkit-keyframes flash {
0% {
background: rgba(255, 255, 255, 0);
}
20% {
background: rgba(255, 255, 255, 0.4);
}
}
@keyframes flash {
0% {
background: rgba(255, 255, 255, 0);
}
20% {
background: rgba(255, 255, 255, 0.4);
}
}
Теперь скрипт jquery:
var $doc = $(document),
$context = $(".context:not(.context--sub)");
$doc.on("contextmenu", function (e) {
var $window = $(window),
$sub = $context.find(".context--sub");
$sub.removeClass("oppositeX oppositeY");
e.preventDefault();
var w = $context.width();
var h = $context.height();
var x = e.clientX;
var y = e.clientY;
var ww = $window.width();
var wh = $window.height();
var padx = 30;
var pady = 20;
var fx = x;
var fy = y;
var hitsRight = x + w >= ww - padx;
var hitsBottom = y + h >= wh - pady;
if (hitsRight) {
fx = ww - w - padx;
}
if (hitsBottom) {
fy = wh - h - pady;
}
$context.
css({
left: fx - 1,
top: fy - 1 });
var sw = $sub.width();
var sh = $sub.height();
var sx = $sub.offset().left;
var sy = $sub.offset().top;
var subHitsRight = sx + sw - padx >= ww - padx;
var subHitsBottom = sy + sh - pady >= wh - pady;
if (subHitsRight) {
$sub.addClass("oppositeX");
}
if (subHitsBottom) {
$sub.addClass("oppositeY");
}
$context.addClass("is-visible");
$doc.on("mousedown", function (e) {
var $tar = $(e.target);
if (!$tar.is($context) && !$tar.closest(".context").length) {
$context.removeClass("is-visible");
$doc.off(e);
}
});
});
$context.on("mousedown touchstart", ".context__item:not(.context__item--nope)", function (e) {
if (e.which === 1) {
var $item = $(this);
$item.removeClass("context__item--active");
setTimeout(function () {
$item.addClass("context__item--active");
}, 10);
}
});