fx-runtime.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. /* html-ppt :: fx-runtime.js
  2. * Canvas FX autoloader + lifecycle manager.
  3. * - Dynamically loads all fx modules listed in FX_LIST
  4. * - Initializes [data-fx] elements when their slide becomes active
  5. * - Calls handle.stop() when the slide leaves
  6. */
  7. (function(){
  8. 'use strict';
  9. const FX_LIST = [
  10. '_util',
  11. 'particle-burst','confetti-cannon','firework','starfield','matrix-rain',
  12. 'knowledge-graph','neural-net','constellation','orbit-ring','galaxy-swirl',
  13. 'word-cascade','letter-explode','chain-react','magnetic-field','data-stream',
  14. 'gradient-blob','sparkle-trail','shockwave','typewriter-multi','counter-explosion'
  15. ];
  16. // Resolve base path of this script so it works from any page location.
  17. const myScript = document.currentScript || (function(){
  18. const all = document.getElementsByTagName('script');
  19. for (const s of all){ if (s.src && s.src.indexOf('fx-runtime.js')>-1) return s; }
  20. return null;
  21. })();
  22. const base = myScript ? myScript.src.replace(/fx-runtime\.js.*$/, 'fx/') : 'assets/animations/fx/';
  23. let loaded = 0;
  24. const total = FX_LIST.length;
  25. const ready = new Promise((resolve) => {
  26. if (!total) return resolve();
  27. FX_LIST.forEach((name) => {
  28. const s = document.createElement('script');
  29. s.src = base + name + '.js';
  30. s.async = false;
  31. s.onload = s.onerror = () => { if (++loaded >= total) resolve(); };
  32. document.head.appendChild(s);
  33. });
  34. });
  35. window.__hpxActive = window.__hpxActive || new Map();
  36. function initFxIn(root){
  37. if (!window.HPX) return;
  38. const els = root.querySelectorAll('[data-fx]');
  39. els.forEach((el) => {
  40. if (window.__hpxActive.has(el)) return;
  41. const name = el.getAttribute('data-fx');
  42. const fn = window.HPX[name];
  43. if (typeof fn !== 'function') return;
  44. try {
  45. const handle = fn(el, {}) || { stop(){} };
  46. window.__hpxActive.set(el, handle);
  47. } catch(e){ console.warn('[hpx-fx]', name, e); }
  48. });
  49. }
  50. function stopFxIn(root){
  51. const els = root.querySelectorAll('[data-fx]');
  52. els.forEach((el) => {
  53. const h = window.__hpxActive.get(el);
  54. if (h && typeof h.stop === 'function'){
  55. try{ h.stop(); }catch(e){}
  56. }
  57. window.__hpxActive.delete(el);
  58. });
  59. }
  60. function reinitFxIn(root){
  61. stopFxIn(root);
  62. initFxIn(root);
  63. }
  64. window.__hpxReinit = reinitFxIn;
  65. function boot(){
  66. ready.then(() => {
  67. const active = document.querySelector('.slide.is-active') || document.querySelector('.slide');
  68. if (active) initFxIn(active);
  69. // Watch all slides for class changes
  70. const slides = document.querySelectorAll('.slide');
  71. slides.forEach((sl) => {
  72. const mo = new MutationObserver((muts) => {
  73. for (const m of muts){
  74. if (m.attributeName === 'class'){
  75. if (sl.classList.contains('is-active')) initFxIn(sl);
  76. else stopFxIn(sl);
  77. }
  78. }
  79. });
  80. mo.observe(sl, { attributes: true, attributeFilter: ['class'] });
  81. });
  82. });
  83. }
  84. if (document.readyState === 'loading'){
  85. document.addEventListener('DOMContentLoaded', boot);
  86. } else {
  87. boot();
  88. }
  89. })();