PluginProbe
Bubble Menu – Floating Button Menu with Sticky Navigation / trunk
Bubble Menu – Floating Button Menu with Sticky Navigation vtrunk
trunk 1.3 2.0 2.2 2.2.1 3.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.1 3.1.1 4.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.1 4.1.1
bubble-menu / public / assets / js / script.js

script.js in Bubble Menu – Floating Button Menu with Sticky Navigation trunk, at public/assets/js/script.js

172 lines 6.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * BubbleMenu
3 * A JavaScript class for creating customizable side menus.
4 *
5 * @version 4.0.0
6 * @license MIT License
7 * @author Dmytro Lobov
8 * @url https://wow-estore.com/item/bubble-menu-pro/
9 */
10
11 'use strict';
12
13
14 const BubbleMenu = () => {
15 const menus = document.querySelectorAll('.bubble-menu');
16 if (menus.length === 0) return;
17
18 const ITEM_PADDING = 15;
19 const BASE_ANGLE_NON_POSITIONED = Math.PI;
20 const BASE_ANGLE_POSITIONED = Math.PI / 2;
21
22 menus.forEach(menu => {
23 initializeMenu(menu);
24 });
25
26 function initializeMenu(menu) {
27 menu.classList.add('-show');
28 const toggle = getElement(menu, '.bm-toggle');
29 const items = getElements(menu, '.bm-item');
30 const links = getElements(menu, '.bm-link');
31 const itemWidth = getItemWidth(items);
32 const isPositioned = isPositionedMenu(menu);
33 const angleIncrement = calculateAngleIncrement(items.length, isPositioned);
34 let radius = calculateOptimalRadius(itemWidth, angleIncrement);
35 const minRadius = itemWidth * 1.5;
36
37 if(radius < minRadius) {
38 radius = minRadius;
39 }
40
41 setMenuProperties(menu, items, angleIncrement, radius);
42 updateMenuStatus(menu, links);
43 attachEventListeners(menu, toggle, links);
44 }
45
46 function getElement(parent, selector) {
47 return parent.querySelector(selector);
48 }
49
50 function getElements(parent, selector) {
51 return parent.querySelectorAll(selector);
52 }
53
54 function getItemWidth(items) {
55 return items[0].offsetWidth + ITEM_PADDING;
56 }
57
58 function isPositionedMenu(menu) {
59 const positions = ['-top-left', '-top-right', '-bottom-left', '-bottom-right'];
60 return positions.some(position => menu.classList.contains(position));
61 }
62
63 function calculateAngleIncrement(itemCount, isPositioned) {
64 const baseAngle = isPositioned ? BASE_ANGLE_POSITIONED : BASE_ANGLE_NON_POSITIONED;
65 return baseAngle / (itemCount - 1);
66 }
67
68 function calculateOptimalRadius(itemWidth, angleIncrement) {
69 return (itemWidth / 2) / Math.sin(angleIncrement / 2);
70 }
71
72 function setMenuProperties(menu, items, angleIncrement, radius) {
73 items.forEach((item, index) => {
74 const reverseIndex = items.length - 1 - index;
75 const angle = determineAngleForMenu(menu, angleIncrement, index, reverseIndex);
76 styleMenuItem(item, menu, radius, angle, index);
77 });
78 }
79
80 function determineAngleForMenu(menu, angleIncrement, index, reverseIndex) {
81 if (menu.classList.contains('-top')) return angleIncrement * reverseIndex;
82 if (menu.classList.contains('-top-right')) return angleIncrement * reverseIndex + Math.PI / 2;
83 if (menu.classList.contains('-top-left')) return angleIncrement * index;
84 if (menu.classList.contains('-left')) return angleIncrement * index - Math.PI / 2;
85 if (menu.classList.contains('-right')) return angleIncrement * reverseIndex + Math.PI / 2;
86 if (menu.classList.contains('-bottom')) return angleIncrement * reverseIndex * -1;
87 if (menu.classList.contains('-bottom-right')) return angleIncrement * reverseIndex + Math.PI;
88 if (menu.classList.contains('-bottom-left')) return angleIncrement * index - Math.PI / 2;
89 return angleIncrement * index - Math.PI / 2;
90 }
91
92 function styleMenuItem(item, menu, radius, angle, index) {
93 const labelPosition = determineLabelPosition(menu, angle);
94 const link = item.querySelector('.bm-link');
95 link.classList.add(labelPosition);
96 applyToggleClass(menu);
97 const x = radius * Math.cos(angle);
98 const y = radius * Math.sin(angle);
99 item.style.setProperty('--x', `${x}px`);
100 item.style.setProperty('--y', `${y}px`);
101 if (index > 0) {
102 item.style.setProperty('--delay', `0.${index}s`);
103 }
104 }
105
106 function applyToggleClass(menu) {
107 const toggle = menu.querySelector('.bm-toggle');
108 if (menu.classList.contains('-top-right') || menu.classList.contains('-bottom-right') || menu.classList.contains('-right')) {
109 toggle.classList.add('-left');
110 } else {
111 toggle.classList.add('-right');
112 }
113 }
114
115 function determineLabelPosition(menu, angle) {
116 if (menu.classList.contains('-bottom')) return angle < -1.571 ? '-top-left' : '-top-right';
117 if (menu.classList.contains('-top')) return angle < 1.571 ? '-bottom-right' : '-bottom-left';
118 if (menu.classList.contains('-bottom-left')) return '-top-right';
119 if (menu.classList.contains('-top-left')) return '-bottom-right';
120 if (menu.classList.contains('-bottom-right')) return '-top-left';
121 if (menu.classList.contains('-top-right')) return '-bottom-left';
122 if (menu.classList.contains('-right')) return angle < 2.36 ? '-bottom-left' : angle < 3.92 ? '-left' : '-top-left';
123 if (menu.classList.contains('-left')) return angle < -0.78 ? '-top-right' : angle < 0.78 ? '-right' : '-bottom-right';
124 }
125
126 function updateMenuStatus(menu, links) {
127 const isActive = menu.classList.contains('-active');
128 links.forEach(link => link.setAttribute('tabindex', isActive ? '0' : '-1'));
129 const updateVisibility = (bubble) => {
130 if(!bubble.classList.contains('-active')) {
131 bubble.classList.remove('-show');
132 } else {
133 bubble.classList.add('-show');
134 }
135 };
136
137 if(isActive && menu.hasAttribute('data-bubbles-hide')) {
138 menus.forEach(updateVisibility);
139 } else {
140 menus.forEach(bubble => bubble.classList.add('-show'));
141 }
142
143 document.addEventListener('click', function(event) {
144 if (!event.target.closest('[data-bubble-toggle]')) {
145 menus.forEach((bubble) => {
146 if(bubble.hasAttribute('data-bubble-toggle')) {
147 bubble.classList.remove('-active');
148 }
149 bubble.classList.add('-show');
150 });
151 }
152 });
153 }
154
155 function attachEventListeners(menu, toggle, links) {
156 toggle.addEventListener('click', () => {
157 menu.classList.toggle('-active');
158 updateMenuStatus(menu, links);
159 });
160
161 document.addEventListener('keydown', event => {
162 if ((event.key === 'Enter' || event.key === ' ') && document.activeElement === toggle) {
163 menu.classList.toggle('-active');
164 updateMenuStatus(menu, links);
165 event.preventDefault();
166 }
167 });
168 }
169 };
170
171 BubbleMenu();
172