Advertisement
nrzmalik

ToolTips for Articulate Storyline Elements

Oct 25th, 2023 (edited)
748
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 8.07 KB | Source Code | 0 0
  1. class Tooltip {
  2.   constructor() {
  3.     this.tooltip = null;
  4.     this.showTimeout = null;
  5.     this.hideTimeout = null;
  6.     this.currentTarget = null;
  7.     this.touchStartX = 0;
  8.     this.touchStartY = 0;
  9.     this.resizeObserver = null;
  10.     this.init();
  11.   }
  12.  
  13.   init() {
  14.     try {
  15.       this.createStyles();
  16.       this.createTooltipElement();
  17.       this.setupResizeObserver();
  18.       this.attachEventListeners();
  19.     } catch (error) {
  20.       console.error('Failed to initialize tooltip:', error);
  21.     }
  22.   }
  23.  
  24.   createStyles() {
  25.     const style = document.createElement('style');
  26.     style.textContent = `
  27.       .custom-tooltip {
  28.         position: fixed;
  29.         background-color: rgba(33, 33, 33, 0.95);
  30.         color: #fff;
  31.         padding: 10px 15px;
  32.         border-radius: 6px;
  33.         font-size: 14px;
  34.         font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  35.         box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);
  36.         pointer-events: none;
  37.         opacity: 0;
  38.         transform: translateY(5px);
  39.         transition: opacity 0.2s ease, transform 0.2s ease;
  40.         z-index: 1000000; /* Increased z-index */
  41.         max-width: 250px;
  42.         word-wrap: break-word;
  43.         line-height: 1.4;
  44.         visibility: hidden; /* Start hidden */
  45.       }
  46.       .custom-tooltip.visible {
  47.         opacity: 1;
  48.         transform: translateY(0);
  49.         visibility: visible;
  50.       }
  51.       .custom-tooltip::after {
  52.         content: '';
  53.         position: absolute;
  54.         border-style: solid;
  55.         border-width: 6px;
  56.       }
  57.       .custom-tooltip.top::after {
  58.         border-color: rgba(33, 33, 33, 0.95) transparent transparent transparent;
  59.         top: 100%;
  60.         left: 50%;
  61.         margin-left: -6px;
  62.       }
  63.       .custom-tooltip.bottom::after {
  64.         border-color: transparent transparent rgba(33, 33, 33, 0.95) transparent;
  65.         bottom: 100%;
  66.         left: 50%;
  67.         margin-left: -6px;
  68.       }
  69.     `;
  70.     document.head.appendChild(style);
  71.   }
  72.  
  73.   setupResizeObserver() {
  74.     this.resizeObserver = new ResizeObserver(entries => {
  75.       if (this.currentTarget && this.tooltip.classList.contains('visible')) {
  76.         this.positionTooltip(this.currentTarget);
  77.       }
  78.     });
  79.     this.resizeObserver.observe(document.body);
  80.   }
  81.  
  82.   createTooltipElement() {
  83.     this.tooltip = document.createElement('div');
  84.     this.tooltip.className = 'custom-tooltip';
  85.     this.tooltip.setAttribute('role', 'tooltip');
  86.     document.body.appendChild(this.tooltip);
  87.   }
  88.  
  89.   attachEventListeners() {
  90.     const handlers = {
  91.       mouseover: this.handleMouseOver.bind(this),
  92.       mouseout: this.handleMouseOut.bind(this),
  93.       focusin: this.handleFocusIn.bind(this),
  94.       focusout: this.handleFocusOut.bind(this),
  95.       touchstart: this.handleTouchStart.bind(this),
  96.       touchend: this.handleTouchEnd.bind(this),
  97.       scroll: this.handleScroll.bind(this),
  98.     };
  99.  
  100.     Object.entries(handlers).forEach(([event, handler]) => {
  101.       document.body.addEventListener(event, handler, { passive: true });
  102.     });
  103.   }
  104.  
  105.   handleScroll() {
  106.     if (this.currentTarget) {
  107.       this.positionTooltip(this.currentTarget);
  108.     }
  109.   }
  110.  
  111.   handleMouseOver(event) {
  112.     const target = event.target.closest('[data-acc-text]');
  113.     if (!target || !this.isValidTarget(target)) return;
  114.    
  115.     this.currentTarget = target;
  116.     clearTimeout(this.showTimeout);
  117.     this.showTimeout = setTimeout(() => this.showTooltip(target), 200);
  118.   }
  119.  
  120.   handleMouseOut(event) {
  121.     if (!event.relatedTarget || !event.relatedTarget.closest('[data-acc-text]')) {
  122.       clearTimeout(this.showTimeout);
  123.       this.hideTooltip();
  124.     }
  125.   }
  126.  
  127.   isValidTarget(target) {
  128.     const tooltipText = target.getAttribute('data-acc-text');
  129.     return tooltipText && tooltipText.trim().length > 0;
  130.   }
  131.  
  132.   positionTooltip(target) {
  133.     if (!target || !this.tooltip) return;
  134.  
  135.     const rect = target.getBoundingClientRect();
  136.     const tooltipRect = this.tooltip.getBoundingClientRect();
  137.     const viewportHeight = window.innerHeight;
  138.     const viewportWidth = window.innerWidth;
  139.     const spacing = 10;
  140.  
  141.     // Calculate positions
  142.     let position = this.calculateOptimalPosition(rect, tooltipRect, viewportHeight, viewportWidth, spacing);
  143.  
  144.     // Apply positions
  145.     this.tooltip.style.top = `${position.top}px`;
  146.     this.tooltip.style.left = `${position.left}px`;
  147.    
  148.     // Update arrow position
  149.     this.tooltip.classList.toggle('top', position.isTop);
  150.     this.tooltip.classList.toggle('bottom', !position.isTop);
  151.   }
  152.  
  153.   calculateOptimalPosition(targetRect, tooltipRect, viewportHeight, viewportWidth, spacing) {
  154.     let top, left, isTop = false;
  155.  
  156.     // Try to position below first
  157.     top = targetRect.bottom + spacing;
  158.     left = targetRect.left + (targetRect.width - tooltipRect.width) / 2;
  159.  
  160.     // Check if tooltip would go below viewport
  161.     if (top + tooltipRect.height > viewportHeight) {
  162.       top = targetRect.top - tooltipRect.height - spacing;
  163.       isTop = true;
  164.     }
  165.  
  166.     // Adjust horizontal position if needed
  167.     if (left + tooltipRect.width > viewportWidth) {
  168.       left = viewportWidth - tooltipRect.width - spacing;
  169.     }
  170.     if (left < spacing) {
  171.       left = spacing;
  172.     }
  173.  
  174.     return { top, left, isTop };
  175.   }
  176.  
  177.   showTooltip(target) {
  178.     if (!target || !this.isValidTarget(target)) return;
  179.  
  180.     const tooltipText = target.getAttribute('data-acc-text');
  181.     this.tooltip.textContent = tooltipText;
  182.     this.tooltip.setAttribute('aria-hidden', 'false');
  183.    
  184.     // Position before showing for smooth animation
  185.     this.positionTooltip(target);
  186.    
  187.     requestAnimationFrame(() => {
  188.       this.tooltip.classList.add('visible');
  189.     });
  190.   }
  191.  
  192.   hideTooltip() {
  193.     clearTimeout(this.hideTimeout);
  194.     this.hideTimeout = setTimeout(() => {
  195.       this.tooltip.classList.remove('visible');
  196.       this.tooltip.setAttribute('aria-hidden', 'true');
  197.       this.currentTarget = null;
  198.     }, 200);
  199.   }
  200.  
  201.   handleFocusIn(event) {
  202.     const target = event.target.closest('[data-acc-text]');
  203.     if (!target || !this.isValidTarget(target)) return;
  204.     this.currentTarget = target;
  205.     this.showTooltip(target);
  206.   }
  207.  
  208.   handleFocusOut() {
  209.     this.hideTooltip();
  210.   }
  211.  
  212.   handleTouchStart(event) {
  213.     const target = event.target.closest('[data-acc-text]');
  214.     if (!target || !this.isValidTarget(target)) return;
  215.    
  216.     this.currentTarget = target;
  217.     this.touchStartX = event.touches[0].clientX;
  218.     this.touchStartY = event.touches[0].clientY;
  219.     this.showTimeout = setTimeout(() => this.showTooltip(target), 200);
  220.   }
  221.  
  222.   handleTouchEnd(event) {
  223.     if (!this.currentTarget) return;
  224.    
  225.     const touchEndX = event.changedTouches[0].clientX;
  226.     const touchEndY = event.changedTouches[0].clientY;
  227.     const distance = Math.sqrt(
  228.       Math.pow(touchEndX - this.touchStartX, 2) +
  229.       Math.pow(touchEndY - this.touchStartY, 2)
  230.     );
  231.  
  232.     if (distance < 10) { // Threshold for considering it a tap, not a scroll
  233.       clearTimeout(this.showTimeout);
  234.       this.toggleTooltip(this.currentTarget);
  235.     } else {
  236.       this.hideTooltip();
  237.     }
  238.   }
  239.  
  240.   toggleTooltip(target) {
  241.     if (this.tooltip.classList.contains('visible')) {
  242.       this.hideTooltip();
  243.     } else {
  244.       this.showTooltip(target);
  245.     }
  246.   }
  247.  
  248.   destroy() {
  249.     if (this.resizeObserver) {
  250.       this.resizeObserver.disconnect();
  251.     }
  252.     clearTimeout(this.showTimeout);
  253.     clearTimeout(this.hideTimeout);
  254.     if (this.tooltip && this.tooltip.parentNode) {
  255.       this.tooltip.parentNode.removeChild(this.tooltip);
  256.     }
  257.   }
  258. }
  259.  
  260. function initializeTooltip() {
  261.   try {
  262.     if (window.tooltipInstance) {
  263.       window.tooltipInstance.destroy();
  264.     }
  265.     window.tooltipInstance = new Tooltip();
  266.   } catch (error) {
  267.     console.error('Failed to initialize tooltip:', error);
  268.   }
  269. }
  270.  
  271. if (document.readyState === 'complete' || document.readyState === 'interactive') {
  272.   initializeTooltip();
  273. } else {
  274.   document.addEventListener('DOMContentLoaded', initializeTooltip);
  275. }
  276.      
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement