|
|
@@ -5,7 +5,6 @@ import { onBeforeUnmount, onMounted, ref } from "vue";
|
|
|
import platformCover from "../assets/images/pbg.png?url";
|
|
|
import equipmentCover from "../assets/images/case2.png?url";
|
|
|
import productionCover from "../assets/images/sence2.jpg?url";
|
|
|
-import qhseCover from "../assets/images/sence3.png?url";
|
|
|
|
|
|
const activeTab = ref("platform");
|
|
|
const tabBarRef = ref(null);
|
|
|
@@ -131,7 +130,37 @@ function scrollToTab(key) {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
-let observer = null;
|
|
|
+let scrollRafId = 0;
|
|
|
+let isScrollListenerBound = false;
|
|
|
+
|
|
|
+function updateActiveTabFromScroll() {
|
|
|
+ const offset = headerOffsetPx();
|
|
|
+ let bestKey = pillars[0]?.key || activeTab.value;
|
|
|
+ let bestTop = Number.NEGATIVE_INFINITY;
|
|
|
+
|
|
|
+ for (const p of pillars) {
|
|
|
+ const el = document.getElementById(`product-${p.key}`);
|
|
|
+ if (!el) continue;
|
|
|
+
|
|
|
+ const top = el.getBoundingClientRect().top - offset;
|
|
|
+
|
|
|
+ // Pick the last section whose top has crossed the sticky header+tab bar.
|
|
|
+ if (top <= 1 && top > bestTop) {
|
|
|
+ bestTop = top;
|
|
|
+ bestKey = p.key;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bestKey && activeTab.value !== bestKey) activeTab.value = bestKey;
|
|
|
+}
|
|
|
+
|
|
|
+function onWindowScrollOrResize() {
|
|
|
+ if (scrollRafId) return;
|
|
|
+ scrollRafId = window.requestAnimationFrame(() => {
|
|
|
+ scrollRafId = 0;
|
|
|
+ updateActiveTabFromScroll();
|
|
|
+ });
|
|
|
+}
|
|
|
|
|
|
const currentImageIndex = ref({
|
|
|
platform: 0,
|
|
|
@@ -183,75 +212,37 @@ const goToImageSlide = (key, index, totalItems) => {
|
|
|
startImageCarousel(key, totalItems);
|
|
|
};
|
|
|
|
|
|
-function updateActiveTab() {
|
|
|
- const sections = pillars
|
|
|
- .map((p) => document.getElementById(`product-${p.key}`))
|
|
|
- .filter(Boolean);
|
|
|
- const offset = headerOffsetPx();
|
|
|
-
|
|
|
- const scrollPos = window.scrollY + offset;
|
|
|
-
|
|
|
- let currentSection = null;
|
|
|
- for (const section of sections) {
|
|
|
- const rect = section.getBoundingClientRect();
|
|
|
- if (rect.top <= 100) {
|
|
|
- currentSection = section;
|
|
|
- } else {
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (currentSection) {
|
|
|
- const key = currentSection.id.replace("product-", "");
|
|
|
- if (activeTab.value !== key) {
|
|
|
- activeTab.value = key;
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
onMounted(() => {
|
|
|
- const offset = headerOffsetPx();
|
|
|
- observer = new IntersectionObserver(
|
|
|
- (entries) => {
|
|
|
- const visible = entries
|
|
|
- .filter((e) => e.isIntersecting)
|
|
|
- .sort(
|
|
|
- (a, b) => (b.intersectionRatio || 0) - (a.intersectionRatio || 0),
|
|
|
- )[0];
|
|
|
- if (visible?.target?.id)
|
|
|
- activeTab.value = visible.target.id.replace("product-", "");
|
|
|
- },
|
|
|
- {
|
|
|
- root: null,
|
|
|
- threshold: [0.3, 0.5, 0.7],
|
|
|
- rootMargin: `-${offset}px 0px -40% 0px`,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- for (const p of pillars) {
|
|
|
- const el = document.getElementById(`product-${p.key}`);
|
|
|
- if (el) observer.observe(el);
|
|
|
+ if (!isScrollListenerBound) {
|
|
|
+ window.addEventListener("scroll", onWindowScrollOrResize, { passive: true });
|
|
|
+ window.addEventListener("resize", onWindowScrollOrResize, { passive: true });
|
|
|
+ isScrollListenerBound = true;
|
|
|
}
|
|
|
|
|
|
+ // Ensure initial highlight is correct even before the first scroll event.
|
|
|
+ updateActiveTabFromScroll();
|
|
|
+
|
|
|
for (const p of pillars) {
|
|
|
if (currentImageIndex.value[p.key] === undefined)
|
|
|
currentImageIndex.value[p.key] = 0;
|
|
|
startImageCarousel(p.key, p.items?.length || 0);
|
|
|
}
|
|
|
-
|
|
|
- window.addEventListener("scroll", updateActiveTab, { passive: true });
|
|
|
- updateActiveTab();
|
|
|
});
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
- observer?.disconnect?.();
|
|
|
- observer = null;
|
|
|
+ if (isScrollListenerBound) {
|
|
|
+ window.removeEventListener("scroll", onWindowScrollOrResize);
|
|
|
+ window.removeEventListener("resize", onWindowScrollOrResize);
|
|
|
+ isScrollListenerBound = false;
|
|
|
+ }
|
|
|
+ if (scrollRafId) {
|
|
|
+ window.cancelAnimationFrame(scrollRafId);
|
|
|
+ scrollRafId = 0;
|
|
|
+ }
|
|
|
|
|
|
for (const key of Object.keys(imageTimers.value || {})) {
|
|
|
stopImageCarousel(key);
|
|
|
}
|
|
|
-
|
|
|
- window.removeEventListener("scroll", updateActiveTab);
|
|
|
});
|
|
|
</script>
|
|
|
|
|
|
@@ -1051,23 +1042,6 @@ onBeforeUnmount(() => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-@media (prefers-reduced-motion: reduce) {
|
|
|
- .tabBtn {
|
|
|
- transition: none !important;
|
|
|
- transform: none !important;
|
|
|
- }
|
|
|
-
|
|
|
- .tabText {
|
|
|
- transition: none !important;
|
|
|
- }
|
|
|
-
|
|
|
- .platformFeature,
|
|
|
- .tabFeatureRow {
|
|
|
- transition: none !important;
|
|
|
- transform: none !important;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
.imageCarouselWrapper {
|
|
|
position: relative;
|
|
|
display: flex;
|