JSFiddle - React, Tailwind, and code Playground
by neonDog
HTML
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxst.icons8.com/vue-static/landings/line-awesome/font-awesome-line-awesome/css/all.min.css">
<div class="neon-page-block">
test
test<br>
test
</div>
SCSS
body {
background: #39D;
}
.pie-menu-container {
display: flex;
justify-content: center;
position: absolute;
bottom:-70px;
left:0;
right:0;
}
.neon-pie-menu-circle {
div {
text-decoration: none;
color: #666;
display: block;
height: 40px;
width: 40px;
line-height: 40px;
margin-left: -20px;
margin-top: -20px;
position: absolute;
text-align: center;
cursor: pointer;
background: white;
border-radius: 50%;
transform: scale(0);
opacity:0;
transition: all 0.3s ease;
&:hover {
transform: scale(1.1)!important;
color: #cef;
background:#ccc;
}
}
&.open div {
opacity: 1;
transform: scale(1);
}
@for $i from 1 through 10 {
div:nth-child(#{$i}) {
transition-delay: $i * 50ms;
}
}
}
.plus-minus-btn {
background: #dde;
width: 50px;
height: 50px;
border: 0;
position: relative;
border-radius:50%;
&:focus {outline:0;}
&:hover {
background: #eef;
}
span {
position: absolute;
transition: 0.3s;
background: #666;
border-radius: 2px;
&:first-of-type {
top: 25%;
bottom: 25%;
width: 10%;
left: 45%;
}
&:last-of-type {
left: 25%;
right: 25%;
height: 10%;
top: 45%;
}
}
&:focus span:first-of-type, &:focus span:last-of-type {
transform: rotate(90deg);
}
&:focus span:last-of-type {
left: 50%;
right: 50%;
}
}
.neon-page-block {
min-height:200px;
outline: 2px red solid;
background:#ccc;
position:relative;
}
JavaScript
class NeonPieMenu {
constructor(element,settings={}) {
this.defaults = {
distance:120,
items:[
{icon:'fa fa-heading fa-2x',action:'home', label:'Heading'},
{icon:'fa fa-font fa-2x',action:'rich-text', label:'Rich-text'},
{icon:'fa fa-images fa-2x',action:'gallery', label:'Gallery'},
{icon:'fa fa-camera fa-2x',action:'image', label:'Image'}
],
onClick:(action,el)=>{}
};
this.options = Object.assign({}, this.defaults, settings);
this.element = element;
this.circle = element.getElementsByClassName('neon-pie-menu-circle')[0];
this.button = element.getElementsByTagName('button')[0];
this.open = false;
this.renderItems();
this._click = this._click.bind(this);
this._focus = this._focus.bind(this);
this._blur = this._blur.bind(this);
this.eventListeners(true);
}
updateData(data={}) {
this.options = Object.assign({}, this.options, settings);
this.renderItems();
}
renderItems() {
const frag = document.createDocumentFragment();
const o = this.options;
for(let i = 0, l = o.items.length; i < l; i++) {
const item = o.items[i];
const itemEl = document.createElement('div');
itemEl.style.left = (50 - o.distance * Math.cos(-0.5 * Math.PI - 2*(1/l)*i*Math.PI)).toFixed(4) + "%";
itemEl.style.top = (50 + o.distance * Math.sin(-0.5 * Math.PI - 2*(1/l)*i*Math.PI)).toFixed(4) + "%";
itemEl.className = 'neon-pie-menu-click ' + item.icon;
itemEl.setAttribute('data-action', item.action);
item.tooltip && itemEl.setAttribute('data-tooltip', item.tooltip);
frag.append(itemEl);
}
this.circle.innerHTML = '';
this.circle.append(frag);
}
_click(e) {
const el = e.target.closest('.neon-pie-menu-click');
if (!el && this.open) {
this.button.blur();
return;
}
const data = el.dataset;
this.options.onClick(data.action, el);
...