data-hover-area.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. // Hover Grid Auto Setup with Edge Detection and Animation Div Creation
  2. // Debug elements - DOM references for visual debugging
  3. const debugInfo = document.getElementById('debugInfo');
  4. const directionSpan = document.getElementById('direction');
  5. const statusSpan = document.getElementById('status');
  6. // Updates the debug information display
  7. function updateDebug(direction, status) {
  8. if (directionSpan && statusSpan) {
  9. directionSpan.textContent = direction || '-';
  10. statusSpan.textContent = status || '-';
  11. }
  12. }
  13. // HoverGridSystem Class
  14. class HoverGridSystem {
  15. constructor() {
  16. this.activeCards = new Map(); // Tracks active cards and animations
  17. this.init();
  18. }
  19. init() {
  20. document.querySelectorAll('[data-hover-area]').forEach(parentGrid => {
  21. const gridSelector = parentGrid.getAttribute('data-hover-area');
  22. const grid = document.querySelector(gridSelector);
  23. if (!grid) {
  24. console.warn(`Grid not found: ${gridSelector}`);
  25. return;
  26. }
  27. const config = this.extractConfig(parentGrid);
  28. this.setupGrid(grid, config);
  29. });
  30. }
  31. extractConfig(element) {
  32. return {
  33. hoverColor: element.getAttribute('data-hover-color') || 'rgba(0, 191, 255, 0.3)',
  34. targetSelector: element.getAttribute('data-target') || null,
  35. targetColor: element.getAttribute('data-target-color') || '#00BFFF',
  36. animationDuration: element.getAttribute('data-duration') || '0.6s',
  37. animationEasing: element.getAttribute('data-easing') || 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'
  38. };
  39. }
  40. setupGrid(grid, config) {
  41. const cards = Array.from(grid.children);
  42. cards.forEach(card => {
  43. this.setupCard(card, config);
  44. });
  45. }
  46. setupCard(card, config) {
  47. if (getComputedStyle(card).position === 'static') {
  48. card.style.position = 'relative';
  49. }
  50. card.style.overflow = 'hidden';
  51. const overlay = document.createElement('div');
  52. overlay.className = 'anim-overlay';
  53. overlay.style.cssText = `
  54. position: absolute;
  55. top: 0;
  56. left: 0;
  57. right: 0;
  58. bottom: 0;
  59. background: ${config.hoverColor};
  60. opacity: 0;
  61. z-index: 0;
  62. pointer-events: none;
  63. `;
  64. card.appendChild(overlay);
  65. // Ensure all content is above the overlay
  66. Array.from(card.children).forEach(child => {
  67. if (!child.classList.contains('anim-overlay')) {
  68. child.style.position = 'relative';
  69. child.style.zIndex = '1';
  70. }
  71. });
  72. if (config.targetSelector) {
  73. const targetElement = card.querySelector(config.targetSelector);
  74. if (targetElement) {
  75. targetElement.style.transition = `color ${config.animationDuration} ${config.animationEasing}`;
  76. }
  77. }
  78. card.addEventListener('mouseenter', (e) => this.handleMouseEnter(card, overlay, e, config));
  79. card.addEventListener('mouseleave', (e) => this.handleMouseLeave(card, overlay, e, config));
  80. }
  81. handleMouseEnter(card, overlay, event, config) {
  82. const direction = this.getDirection(card, event.clientX, event.clientY);
  83. updateDebug(direction, 'ENTER');
  84. this.cancelAnimation(card);
  85. if (config.targetSelector) {
  86. const targetElement = card.querySelector(config.targetSelector);
  87. if (targetElement) {
  88. targetElement.style.color = config.targetColor;
  89. }
  90. }
  91. this.animateEntry(overlay, direction, config);
  92. this.activeCards.set(card, {
  93. overlay: overlay,
  94. direction: direction,
  95. isActive: true,
  96. config: config
  97. });
  98. }
  99. handleMouseLeave(card, overlay, event, config) {
  100. const direction = this.getDirection(card, event.clientX, event.clientY);
  101. updateDebug(direction, 'LEAVE');
  102. if (config.targetSelector) {
  103. const targetElement = card.querySelector(config.targetSelector);
  104. if (targetElement) {
  105. targetElement.style.color = '';
  106. }
  107. }
  108. this.animateExit(overlay, direction, config);
  109. const duration = parseFloat(config.animationDuration) * 1000;
  110. setTimeout(() => {
  111. this.activeCards.delete(card);
  112. }, duration + 100);
  113. }
  114. getDirection(element, mouseX, mouseY) {
  115. const rect = element.getBoundingClientRect();
  116. const centerX = rect.left + rect.width / 2;
  117. const centerY = rect.top + rect.height / 2;
  118. const angle = Math.atan2(mouseY - centerY, mouseX - centerX);
  119. const degree = angle * (180 / Math.PI);
  120. if (degree >= -45 && degree < 45) {
  121. return 'right';
  122. } else if (degree >= 45 && degree < 135) {
  123. return 'bottom';
  124. } else if (degree >= 135 || degree < -135) {
  125. return 'left';
  126. } else {
  127. return 'top';
  128. }
  129. }
  130. animateEntry(overlay, direction, config) {
  131. overlay.style.transition = 'none';
  132. overlay.style.opacity = '1';
  133. overlay.style.transform = this.getInitialSlideTransform(direction);
  134. overlay.offsetHeight;
  135. overlay.style.transition = `transform ${config.animationDuration} ${config.animationEasing}`;
  136. overlay.style.transform = 'translate(0, 0)';
  137. }
  138. animateExit(overlay, direction, config) {
  139. const exitEasing = 'cubic-bezier(0.55, 0.085, 0.68, 0.53)';
  140. overlay.style.transition = `transform ${config.animationDuration} ${exitEasing}`;
  141. overlay.style.transform = this.getExitSlideTransform(direction);
  142. const duration = parseFloat(config.animationDuration) * 1000;
  143. setTimeout(() => {
  144. overlay.style.opacity = '0';
  145. overlay.style.transition = 'none';
  146. }, duration);
  147. }
  148. getInitialSlideTransform(direction) {
  149. switch(direction) {
  150. case 'top':
  151. return 'translateY(-100%)';
  152. case 'right':
  153. return 'translateX(100%)';
  154. case 'bottom':
  155. return 'translateY(100%)';
  156. case 'left':
  157. return 'translateX(-100%)';
  158. default:
  159. return 'translateY(-100%)';
  160. }
  161. }
  162. getExitSlideTransform(direction) {
  163. switch(direction) {
  164. case 'top':
  165. return 'translateY(-100%)';
  166. case 'right':
  167. return 'translateX(100%)';
  168. case 'bottom':
  169. return 'translateY(100%)';
  170. case 'left':
  171. return 'translateX(-100%)';
  172. default:
  173. return 'translateY(-100%)';
  174. }
  175. }
  176. cancelAnimation(card) {
  177. const cardData = this.activeCards.get(card);
  178. if (cardData && cardData.overlay) {
  179. cardData.overlay.style.transition = 'none';
  180. }
  181. }
  182. }
  183. if (document.readyState === 'loading') {
  184. document.addEventListener('DOMContentLoaded', () => {
  185. new HoverGridSystem();
  186. });
  187. } else {
  188. new HoverGridSystem();
  189. }
  190. if (debugInfo) {
  191. setTimeout(() => {
  192. debugInfo.style.opacity = '0.3';
  193. debugInfo.style.transition = 'opacity 0.5s ease';
  194. }, 5000);
  195. }