show-product.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. (function () {
  2. var modal = document.getElementById('showProductVideoModal');
  3. if (modal) {
  4. var player = modal.querySelector('.show-product-video-modal__player');
  5. var openButton = document.querySelector('.js-product-video-open');
  6. var closeTriggers = modal.querySelectorAll('[data-product-modal-close]');
  7. function closeModal() {
  8. modal.hidden = true;
  9. if (player) {
  10. player.pause();
  11. player.removeAttribute('src');
  12. player.load();
  13. }
  14. document.body.classList.remove('is-product-video-open');
  15. }
  16. if (openButton && player) {
  17. openButton.addEventListener('click', function () {
  18. var src = openButton.getAttribute('data-video');
  19. if (!src) return;
  20. player.setAttribute('src', src);
  21. modal.hidden = false;
  22. document.body.classList.add('is-product-video-open');
  23. player.play().catch(function () {});
  24. });
  25. }
  26. closeTriggers.forEach(function (node) {
  27. node.addEventListener('click', closeModal);
  28. });
  29. document.addEventListener('keydown', function (event) {
  30. if (event.key === 'Escape' && !modal.hidden) {
  31. closeModal();
  32. }
  33. });
  34. }
  35. var stageThreeSections = document.querySelectorAll('.js-spx-stage-three');
  36. var stageNineSections = document.querySelectorAll('.js-stage-nine');
  37. var stageSixSliders = document.querySelectorAll('.js-stage-six-slider');
  38. var stageEightTabs = document.querySelectorAll('.js-stage-eight-tabs');
  39. var stageTenSections = document.querySelectorAll('.js-stage-ten');
  40. function clamp(value, min, max) {
  41. return Math.min(Math.max(value, min), max);
  42. }
  43. function updateStageThreeState() {
  44. stageThreeSections.forEach(function (section) {
  45. var rect = section.getBoundingClientRect();
  46. var viewportHeight = window.innerHeight || document.documentElement.clientHeight;
  47. var stickyBlock = section.querySelector('.spx-stage-three-copy-switch');
  48. var titleLayer = section.querySelector('.spx-stage-three-title-layer');
  49. var stickyHeight = stickyBlock ? stickyBlock.offsetHeight : 116;
  50. var titleHeight = titleLayer ? titleLayer.offsetHeight : stickyHeight;
  51. var titleCenterOffset = Math.max((stickyHeight - titleHeight) / 2, 0);
  52. var stickyTop = viewportHeight / 2 - stickyHeight / 2;
  53. var titleCenterY = rect.top + stickyTop + titleCenterOffset + titleHeight / 2;
  54. var startY = viewportHeight * 0.8;
  55. var availableDistance = Math.max(section.offsetHeight - stickyTop - stickyHeight - viewportHeight * 0.12, 1);
  56. var progress = clamp((startY - titleCenterY) / availableDistance, 0, 1);
  57. var fadeProgress = clamp((progress - 0.005) / 0.14, 0, 1);
  58. section.classList.toggle('is-text-visible', fadeProgress > 0.5);
  59. section.style.setProperty('--spx-stage-three-title-opacity', String(1 - fadeProgress));
  60. section.style.setProperty('--spx-stage-three-title-shift', (fadeProgress * -18).toFixed(2) + 'px');
  61. section.style.setProperty('--spx-stage-three-text-opacity', String(fadeProgress));
  62. section.style.setProperty('--spx-stage-three-text-shift', ((1 - fadeProgress) * 18).toFixed(2) + 'px');
  63. });
  64. }
  65. function updateStageNineState() {
  66. stageNineSections.forEach(function (section) {
  67. var viewportHeight = window.innerHeight || document.documentElement.clientHeight;
  68. var nextStage = section.nextElementSibling;
  69. var fadeProgress = 0;
  70. if (nextStage) {
  71. var nextRect = nextStage.getBoundingClientRect();
  72. fadeProgress = clamp((viewportHeight - nextRect.top) / Math.max(viewportHeight * 0.72, 1), 0, 1);
  73. }
  74. section.style.setProperty('--spx-nine-media-opacity', (1 - fadeProgress).toFixed(4));
  75. var media = section.querySelector('.spx-stage-nine-media');
  76. if (!media) return;
  77. var video = media.querySelector('video');
  78. if (!video || video.hasAttribute('data-played')) return;
  79. var rect = section.getBoundingClientRect();
  80. var isInViewport = rect.top < viewportHeight && rect.bottom > 0;
  81. if (!isInViewport) return;
  82. video.play().then(function () {
  83. video.setAttribute('data-played', 'true');
  84. video.addEventListener('ended', function () {
  85. video.pause();
  86. }, { once: true });
  87. }).catch(function () {});
  88. });
  89. }
  90. function initStageTen() {
  91. stageTenSections.forEach(function (section) {
  92. var tabs = section.querySelectorAll('[data-stage-ten-tab]');
  93. var panels = section.querySelectorAll('[data-stage-ten-panel]');
  94. var soundEnabled = false;
  95. if (!tabs.length || !panels.length) return;
  96. function syncSoundButton(video, soundButton) {
  97. if (!video || !soundButton) return;
  98. var active = !video.muted;
  99. soundButton.classList.toggle('is-active', active);
  100. soundButton.setAttribute('aria-label', active ? '关闭声音' : '开启声音');
  101. soundButton.innerHTML = active
  102. ? '<i class="bi bi-volume-up-fill" aria-hidden="true"></i>'
  103. : '<i class="bi bi-volume-mute-fill" aria-hidden="true"></i>';
  104. }
  105. function syncVideo(panel, active) {
  106. var video = panel.querySelector('.spx-stage-ten-video');
  107. var soundButton = panel.querySelector('[data-stage-ten-sound]');
  108. if (!video) return;
  109. if (active) {
  110. video.muted = !soundEnabled;
  111. syncSoundButton(video, soundButton);
  112. video.play().catch(function () {});
  113. } else {
  114. video.pause();
  115. video.currentTime = 0;
  116. video.muted = true;
  117. syncSoundButton(video, soundButton);
  118. }
  119. }
  120. function setActive(index) {
  121. tabs.forEach(function (tab, i) {
  122. var active = i === index;
  123. tab.classList.toggle('is-active', active);
  124. tab.setAttribute('aria-selected', active ? 'true' : 'false');
  125. });
  126. panels.forEach(function (panel, i) {
  127. var active = i === index;
  128. panel.classList.toggle('is-active', active);
  129. syncVideo(panel, active);
  130. });
  131. }
  132. tabs.forEach(function (tab, index) {
  133. tab.addEventListener('click', function () {
  134. setActive(index);
  135. });
  136. });
  137. panels.forEach(function (panel) {
  138. var soundButton = panel.querySelector('[data-stage-ten-sound]');
  139. var video = panel.querySelector('.spx-stage-ten-video');
  140. if (!soundButton || !video) return;
  141. syncSoundButton(video, soundButton);
  142. soundButton.addEventListener('click', function () {
  143. soundEnabled = video.muted;
  144. video.muted = !soundEnabled;
  145. syncSoundButton(video, soundButton);
  146. if (video.paused) {
  147. video.play().catch(function () {});
  148. }
  149. });
  150. });
  151. setActive(0);
  152. });
  153. }
  154. function initRevealAnimations() {
  155. var revealNodes = document.querySelectorAll('.spx-reveal');
  156. if (!revealNodes.length) return;
  157. function revealInViewNodes() {
  158. var viewportHeight = window.innerHeight || document.documentElement.clientHeight;
  159. revealNodes.forEach(function (node) {
  160. if (node.classList.contains('is-visible')) return;
  161. var rect = node.getBoundingClientRect();
  162. if (rect.top <= viewportHeight * 0.92 && rect.bottom >= viewportHeight * 0.08) {
  163. node.classList.add('is-visible');
  164. }
  165. });
  166. }
  167. if (!('IntersectionObserver' in window)) {
  168. revealNodes.forEach(function (node) {
  169. node.classList.add('is-visible');
  170. });
  171. return;
  172. }
  173. var observer = new IntersectionObserver(function (entries) {
  174. entries.forEach(function (entry) {
  175. if (!entry.isIntersecting) return;
  176. entry.target.classList.add('is-visible');
  177. observer.unobserve(entry.target);
  178. });
  179. }, {
  180. threshold: 0.12,
  181. rootMargin: '0px 0px -6% 0px'
  182. });
  183. revealNodes.forEach(function (node) {
  184. observer.observe(node);
  185. });
  186. revealInViewNodes();
  187. window.addEventListener('load', revealInViewNodes, { once: true });
  188. window.addEventListener('resize', revealInViewNodes);
  189. window.addEventListener('scroll', revealInViewNodes, { passive: true });
  190. }
  191. function initStageEighteenModal() {
  192. var modal = document.getElementById('stageEighteenModal');
  193. if (!modal) return;
  194. var triggers = document.querySelectorAll('[data-stage-eighteen-trigger]');
  195. var panels = modal.querySelectorAll('[data-stage-eighteen-panel]');
  196. var closeTriggers = modal.querySelectorAll('[data-stage-eighteen-close]');
  197. if (!triggers.length || !panels.length) return;
  198. function setActivePanel(id) {
  199. panels.forEach(function (panel) {
  200. panel.classList.toggle('is-active', panel.getAttribute('data-stage-eighteen-panel') === id);
  201. });
  202. }
  203. function openModal(id) {
  204. setActivePanel(id);
  205. modal.hidden = false;
  206. document.body.classList.add('is-product-video-open');
  207. }
  208. function closeModal() {
  209. modal.hidden = true;
  210. document.body.classList.remove('is-product-video-open');
  211. }
  212. triggers.forEach(function (trigger) {
  213. trigger.addEventListener('click', function () {
  214. var id = trigger.getAttribute('data-stage-eighteen-trigger');
  215. if (!id) return;
  216. openModal(id);
  217. });
  218. });
  219. closeTriggers.forEach(function (node) {
  220. node.addEventListener('click', closeModal);
  221. });
  222. document.addEventListener('keydown', function (event) {
  223. if (event.key === 'Escape' && !modal.hidden) {
  224. closeModal();
  225. }
  226. });
  227. }
  228. if (stageThreeSections.length) {
  229. updateStageThreeState();
  230. }
  231. updateStageNineState();
  232. initStageTen();
  233. initRevealAnimations();
  234. initStageEighteenModal();
  235. window.addEventListener('scroll', function () {
  236. if (stageThreeSections.length) {
  237. updateStageThreeState();
  238. }
  239. updateStageNineState();
  240. }, { passive: true });
  241. window.addEventListener('resize', function () {
  242. if (stageThreeSections.length) {
  243. updateStageThreeState();
  244. }
  245. updateStageNineState();
  246. });
  247. stageSixSliders.forEach(function (slider) {
  248. var slides = slider.querySelectorAll('.spx-stage-six-slide');
  249. var dots = slider.querySelectorAll('[data-stage-six-dot]');
  250. var prevButton = slider.querySelector('[data-stage-six-prev]');
  251. var nextButton = slider.querySelector('[data-stage-six-next]');
  252. if (!slides.length) return;
  253. var activeIndex = 0;
  254. var timer = null;
  255. function setActive(index) {
  256. activeIndex = (index + slides.length) % slides.length;
  257. slides.forEach(function (slide, i) {
  258. slide.classList.toggle('is-active', i === activeIndex);
  259. slide.setAttribute('aria-hidden', i === activeIndex ? 'false' : 'true');
  260. });
  261. dots.forEach(function (dot, i) {
  262. var active = i === activeIndex;
  263. dot.classList.toggle('is-active', active);
  264. dot.setAttribute('aria-current', active ? 'true' : 'false');
  265. });
  266. }
  267. function start() {
  268. if (slides.length < 2) return;
  269. stop();
  270. timer = window.setInterval(function () {
  271. setActive(activeIndex + 1);
  272. }, 4200);
  273. }
  274. function stop() {
  275. if (timer) {
  276. window.clearInterval(timer);
  277. timer = null;
  278. }
  279. }
  280. dots.forEach(function (dot) {
  281. dot.addEventListener('click', function () {
  282. var index = Number(dot.getAttribute('data-stage-six-dot') || 0);
  283. setActive(index);
  284. start();
  285. });
  286. });
  287. if (prevButton) {
  288. prevButton.addEventListener('click', function () {
  289. setActive(activeIndex - 1);
  290. start();
  291. });
  292. }
  293. if (nextButton) {
  294. nextButton.addEventListener('click', function () {
  295. setActive(activeIndex + 1);
  296. start();
  297. });
  298. }
  299. slider.addEventListener('mouseenter', stop);
  300. slider.addEventListener('mouseleave', start);
  301. slider.addEventListener('focusin', stop);
  302. slider.addEventListener('focusout', start);
  303. setActive(0);
  304. start();
  305. });
  306. stageEightTabs.forEach(function (block) {
  307. var tabs = block.querySelectorAll('[data-stage-eight-tab]');
  308. var panels = block.querySelectorAll('[data-stage-eight-panel]');
  309. var contents = block.querySelectorAll('[data-stage-eight-content]');
  310. if (!tabs.length) return;
  311. function setActive(index) {
  312. tabs.forEach(function (tab, i) {
  313. var active = i === index;
  314. tab.classList.toggle('is-active', active);
  315. tab.setAttribute('aria-selected', active ? 'true' : 'false');
  316. });
  317. panels.forEach(function (panel, i) {
  318. panel.classList.toggle('is-active', i === index);
  319. });
  320. contents.forEach(function (content, i) {
  321. content.classList.toggle('is-active', i === index);
  322. });
  323. }
  324. tabs.forEach(function (tab, index) {
  325. tab.addEventListener('click', function () {
  326. setActive(index);
  327. });
  328. });
  329. setActive(0);
  330. });
  331. document.querySelectorAll('.js-stage-eleven').forEach(function (section) {
  332. var tabs = section.querySelectorAll('[data-stage-eleven-tab]');
  333. var panels = section.querySelectorAll('[data-stage-eleven-panel]');
  334. if (!tabs.length || !panels.length) return;
  335. function syncPanelMedia(panel, active) {
  336. var video = panel.querySelector('.spx-stage-eleven-video');
  337. if (!video) return;
  338. if (active) {
  339. video.play().catch(function () {});
  340. } else {
  341. video.pause();
  342. video.currentTime = 0;
  343. }
  344. }
  345. function setActive(index) {
  346. tabs.forEach(function (tab, i) {
  347. var active = i === index;
  348. tab.classList.toggle('is-active', active);
  349. tab.setAttribute('aria-selected', active ? 'true' : 'false');
  350. });
  351. panels.forEach(function (panel, i) {
  352. var active = i === index;
  353. panel.classList.toggle('is-active', active);
  354. syncPanelMedia(panel, active);
  355. });
  356. }
  357. tabs.forEach(function (tab, index) {
  358. tab.addEventListener('click', function () {
  359. setActive(index);
  360. });
  361. });
  362. setActive(0);
  363. });
  364. })();