Light tomorrow with today.
用現在點亮未來。
今天的任務要做一個 Tab component,
最主要作用為使用者可以在不同標籤間切換來查看不同的內容。
下面為 示意圖:
step1. 先選一個
step2. click 按下向左 scroll
step3. 畫面經過 RWD Resize,自動跳到當初選 Tab
選擇 primevue tabMenu 為基準,做 tab component,
// template
<p-tabMenu :model="items" @tab-change="onTabChange" ref="tabMenu">
<template #item="{ item, props }">
<a
v-ripple
v-bind="props.action"
class="flex align-items-center gap-2"
>
<img
:alt="item.name"
:src="`https://primefaces.org/cdn/primevue/images/avatar/${item.image}`"
style="width: 32px"
/>
<span class="font-bold">{{ item.name }}</span>
</a>
</template>
</p-tabMenu>
// typescript
const items = ref([
{ name: "Amy Elsner", image: "amyelsner.png" },
{ name: "Anna Fali", image: "annafali.png" },
{ name: "Asiya Javayant", image: "asiyajavayant.png" },
{ name: "Bernardo Dominic", image: "bernardodominic.png" },
{ name: "Elwin Sharvill", image: "elwinsharvill.png" },
{ name: "Ioni Bowcher", image: "ionibowcher.png" },
{ name: "Ivan Magalhaes", image: "ivanmagalhaes.png" },
{ name: "Onyama Limba", image: "onyamalimba.png" },
{ name: "Stephen Shaw", image: "stephenshaw.png" },
{ name: "XuXue Feng", image: "xuxuefeng.png" },
]);
// template
<p-button
class="scroll-button left p-button-rounded p-button-text"
@click="scroll('left')"
:disabled="!showLeftScroll"
>
<i class="pi pi-chevron-left"></i>
</p-button>
<p-button
class="scroll-button right p-button-rounded p-button-text"
@click="scroll('right')"
:disabled="!showRightScroll"
>
<i class="pi pi-chevron-right"></i>
</p-button>
// typescript
const showLeftScroll = ref(false);
const showRightScroll = ref(false);
const scroll = (direction) => {
if (tabMenu.value && tabMenu.value.$el) {
const menuElement = tabMenu.value.$el.querySelector(".p-tabmenu-nav");
const scrollAmount = 200;
if (direction === "left") {
menuElement.scrollLeft -= scrollAmount;
} else {
menuElement.scrollLeft += scrollAmount;
}
nextTick(checkScroll);
}
};
如果在實施這些更改後仍然看不到箭頭,請檢查以下幾點:
確保 showLeftScroll 和 showRightScroll 在適當的時候被設置為 true。
確保 checkScroll 函數在適當的時機被調用,比如在窗口大小改變或內容變化時。
:disabled 屬性來控制按鈕的狀態。這樣按鈕始終可見,但在不需要滾動時會被禁用。
調整了 scroll 函數,使其直接操作 TabMenu 的內部元素,而不是外部容器。
修改了 checkScroll 函數,使其檢查 TabMenu 的內部滾動狀態。
在 style 部分:
為 .scroll-container 添加了 padding,為按鈕留出空間。
調整了 .p-tabmenu 和 .p-tabmenu-nav 的樣式,確保正確的滾動行為。
添加了按鈕的樣式,使其在各種狀態下都能正確顯示。
在 onMounted 鉤子中使用 nextTick,確保在 DOM 更新後再進行初始檢查。
一些深度選擇器 (:deep()) 來確保我們的樣式能夠覆蓋 PrimeVue 的默認樣式。
:deep(.p-tabmenu .p-tabmenu-nav .p-tabmenuitem.p-highlight .p-menuitem-link) 選擇器中的相應屬性。
scrollIntoView 選項中,這樣可以確保選中的 tab 總是居中顯示。
<template>
<div class="card">
<div class="scroll-container" ref="scrollContainer">
<p-tabMenu :model="items" @tab-change="onTabChange" ref="tabMenu">
<template #item="{ item, props }">
<a
v-ripple
v-bind="props.action"
class="flex align-items-center gap-2"
>
<img
:alt="item.name"
:src="`https://primefaces.org/cdn/primevue/images/avatar/${item.image}`"
style="width: 32px"
/>
<span class="font-bold">{{ item.name }}</span>
</a>
</template>
</p-tabMenu>
<p-button
class="scroll-button left p-button-rounded p-button-text"
@click="scroll('left')"
:disabled="!showLeftScroll"
>
<i class="pi pi-chevron-left"></i>
</p-button>
<p-button
class="scroll-button right p-button-rounded p-button-text"
@click="scroll('right')"
:disabled="!showRightScroll"
>
<i class="pi pi-chevron-right"></i>
</p-button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick, watchEffect } from "vue";
const items = ref([
{ name: "Amy Elsner", image: "amyelsner.png" },
{ name: "Anna Fali", image: "annafali.png" },
{ name: "Asiya Javayant", image: "asiyajavayant.png" },
{ name: "Bernardo Dominic", image: "bernardodominic.png" },
{ name: "Elwin Sharvill", image: "elwinsharvill.png" },
{ name: "Ioni Bowcher", image: "ionibowcher.png" },
{ name: "Ivan Magalhaes", image: "ivanmagalhaes.png" },
{ name: "Onyama Limba", image: "onyamalimba.png" },
{ name: "Stephen Shaw", image: "stephenshaw.png" },
{ name: "XuXue Feng", image: "xuxuefeng.png" },
]);
const scrollContainer = ref(null);
const tabMenu = ref(null);
const showLeftScroll = ref(false);
const showRightScroll = ref(false);
const checkScroll = () => {
if (tabMenu.value && tabMenu.value.$el) {
const menuElement = tabMenu.value.$el.querySelector(".p-tabmenu-nav");
const { scrollLeft, scrollWidth, clientWidth } = menuElement;
showLeftScroll.value = scrollLeft > 0;
showRightScroll.value = scrollLeft < scrollWidth - clientWidth;
}
};
const scroll = (direction) => {
if (tabMenu.value && tabMenu.value.$el) {
const menuElement = tabMenu.value.$el.querySelector(".p-tabmenu-nav");
const scrollAmount = 200;
if (direction === "left") {
menuElement.scrollLeft -= scrollAmount;
} else {
menuElement.scrollLeft += scrollAmount;
}
nextTick(checkScroll);
}
};
const onTabChange = (event) => {
nextTick(() => {
const tabElement = event.originalEvent.target.closest(".p-tabmenuitem");
if (tabElement) {
tabElement.scrollIntoView({
block: "nearest",
});
}
checkScroll();
});
};
const onResize = () => {
checkScroll();
onTabChange({
originalEvent: { target: tabMenu.value.$el.querySelector(".p-highlight") },
});
};
watchEffect(() => {
if (scrollContainer.value) {
window.addEventListener("resize", onResize);
}
});
onMounted(() => {
nextTick(() => {
checkScroll();
window.addEventListener("resize", checkScroll);
});
});
</script>
<style scoped>
.scroll-container {
position: relative;
overflow: hidden;
padding: 0 40px; /* Make room for buttons */
}
.scroll-button {
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 2;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
.scroll-button.left {
left: 0;
}
.scroll-button.right {
right: 0;
}
:deep(.p-tabmenu) {
overflow-x: hidden;
}
:deep(.p-tabmenu-nav) {
flex-wrap: nowrap;
overflow-x: auto;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* Internet Explorer 10+ */
scroll-behavior: smooth;
padding-bottom: 1px; /* Add padding to show the bottom border */
}
:deep(.p-button.scroll-button) {
color: #333 !important;
}
:deep(.p-highlight .p-menuitem-link) {
border-color: #10b981;
}
/* 防止文字斷行 */
:deep(.p-tabmenu-nav .p-menuitem-link) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
:deep(.p-tabmenu-nav .p-menuitem-link) span {
margin-left: 10px;
}
:deep(.p-button:disabled) {
color: #dfdada !important;
}
</style>