何使用JavaScript代碼解決父級選項卡鼠標懸停時子級菜單無法操作的問題。解決方案包括在子級導航上添加一個類并使用JavaScript代碼監聽鼠標事件來顯示或隱藏子級菜單。如果無法解決問題,建議檢查CSS樣式是否正確應用、JavaScript代碼是否正確工作、HTML結構是否正確、瀏覽器兼容性是否良好等問題。
這個問題可以通過添加一些JavaScript代碼來解決。以下是一種可能的解決方案:
你需要為你的子級導航添加一個類,比如 "subnav"。
你可以使用JavaScript代碼來監聽鼠標事件,當鼠標移入父級tabs時,添加一個類來顯示子級導航。當鼠標移出tabs元素時,移除這個類來隱藏子級導航。如下所示:
HTML如下:
<div class="tabs">
<a href="#">Tab 1</a>
<a href="#">Tab 2</a>
<a href="#">Tab 3</a>
<div class="subnav">
<a href="#">Subnav 1</a>
<a href="#">Subnav 2</a>
<a href="#">Subnav 3</a>
</div>
</div>
CSS如下:
.tabs {
position: relative;
}
.subnav {
position: absolute;
top: 100%;
left: 0;
display: none;
}
.tabs:hover .subnav {
display: block;
}
JavaScript如下:
var tabs = document.querySelector('.tabs');
var subnav = document.querySelector('.subnav');
tabs.addEventListener('mouseenter', function() {
subnav.classList.add('show');
});
tabs.addEventListener('mouseleave', function() {
subnav.classList.remove('show');
});
在這個代碼中,當鼠標移入父級tabs時,使用 "mouseenter" 事件來添加一個 "show" 類來顯示子級導航。當鼠標移出tabs元素時,使用 "mouseleave" 事件來移除這個類來隱藏子級導航。
如果代碼已經和上述的示例代碼類似,并且仍然無法操作子級菜單,那么你可以嘗試以下幾個步驟來診斷問題:
1、確認CSS樣式是否正確應用
確保CSS樣式被正確地應用于您的HTML代碼中。檢查CSS選擇器是否正確,檢查樣式表中是否存在任何語法錯誤。
2、確認JavaScript代碼是否正確工作
檢查JavaScript代碼是否正確工作。可以在控制臺中打印一些調試信息,例如在 mouseenter 和 mouseleave 事件處理程序中添加 console.log 語句來查看它們是否被正確調用。
3、檢查HTML結構
確保您的HTML結構正確,所有必需的元素都存在。檢查類名和ID是否正確命名,并且沒有拼寫錯誤。
4、檢查瀏覽器兼容性
檢查瀏覽器兼容性。有些瀏覽器可能不支持某些CSS或JavaScript功能。您可以使用瀏覽器的開發者工具來檢查這些問題,并嘗試在其他瀏覽器中測試您的代碼。
加圖片注釋,不超過 140 字(可選)
【成品鎮樓圖】 基礎概念 Tabs組件是一種常見的用戶界面組件,用于在一個界面中展示多個內容區域,并允許用戶通過點擊不同的標簽來切換可見的內容。它的實現原理主要涉及HTML、CSS和JavaScript,以下是一個基本的實現步驟: 1. HTML結構 首先,需要定義一個基本的HTML結構,包括標簽(tabs)和內容(tab content)部分。每個標簽對應一個內容區域。
<div class="tabs">
<div class="tab" data-tab="1">Tab 1</div>
<div class="tab" data-tab="2">Tab 2</div>
<div class="tab" data-tab="3">Tab 3</div>
</div>
<div class="tab-content" data-tab="1">Content 1</div>
<div class="tab-content" data-tab="2">Content 2</div>
<div class="tab-content" data-tab="3">Content 3</div>
接下來,使用CSS來定義標簽和內容區域的樣式,尤其是如何在不同的標簽被選中時顯示或隱藏內容。
.tabs {
display: flex;
}
.tab {
padding: 10px;
cursor: pointer;
background-color: #f1f1f1;
border: 1px solid #ccc;
margin-right: 2px;
}
.tab.active {
background-color: #ddd;
}
.tab-content {
display: none;
padding: 10px;
border: 1px solid #ccc;
margin-top: 10px;
}
.tab-content.active {
display: block;
}
最后,使用JavaScript來處理標簽的點擊事件,并根據點擊的標簽來顯示相應的內容區域。
document.addEventListener('DOMContentLoaded', function() {
const tabs = document.querySelectorAll('.tab');
const contents = document.querySelectorAll('.tab-content');
tabs.forEach(tab => {
tab.addEventListener('click', function() {
const tabId = this.getAttribute('data-tab');
// 移除所有標簽和內容的active類
tabs.forEach(t => t.classList.remove('active'));
contents.forEach(c => c.classList.remove('active'));
// 為當前選中的標簽和相應的內容添加active類
this.classList.add('active');
document.querySelector(`.tab-content[data-tab="${tabId}"]`).classList.add('active');
});
});
});
工作原理
通過這種方式,Tabs組件能夠在用戶點擊不同的標簽時,動態地顯示和隱藏相應的內容區域,從而實現標簽切換的功能。 Vue3 在Vue 3中,可以使用組件化的方式來實現Tabs組件。以下是一個基本的實現步驟: 1. 創建Tabs組件 首先,創建一個Tabs組件,用于容納所有的標簽和內容。
<!-- Tabs.vue -->
<template>
<div>
<div class="tabs">
<div
v-for="(tab, index) in tabs"
:key="index"
:class="['tab', { active: activeTab === index }]"
@click="selectTab(index)"
>
{{ tab.label }}
</div>
</div>
<div class="tab-content">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
data() {
return {
activeTab: 0,
tabs: []
};
},
methods: {
selectTab(index) {
this.activeTab = index;
},
addTab(tab) {
this.tabs.push(tab);
}
},
provide() {
return {
registerTab: this.addTab,
activeTab: () => this.activeTab
};
}
};
</script>
<style>
.tabs {
display: flex;
}
.tab {
padding: 10px;
cursor: pointer;
background-color: #f1f1f1;
border: 1px solid #ccc;
margin-right: 2px;
}
.tab.active {
background-color: #ddd;
}
.tab-content {
padding: 10px;
border: 1px solid #ccc;
margin-top: 10px;
}
</style>
接下來,創建一個Tab組件,用于定義每個標簽和其對應的內容。
<!-- Tab.vue -->
<template>
<div v-show="isActive">
<slot></slot>
</div>
</template>
<script>
export default {
props: {
label: {
type: String,
required: true
}
},
inject: ['registerTab', 'activeTab'],
computed: {
isActive() {
return this.activeTab() === this.index;
}
},
data() {
return {
index: null
};
},
mounted() {
this.index = this.$parent.tabs.length;
this.registerTab(this);
}
};
</script>
最后,在你的主組件中使用Tabs和Tab組件。
<!-- App.vue -->
<template>
<Tabs>
<Tab label="Tab 1">Content 1</Tab>
<Tab label="Tab 2">Content 2</Tab>
<Tab label="Tab 3">Content 3</Tab>
</Tabs>
</template>
<script>
import Tabs from './Tabs.vue';
import Tab from './Tab.vue';
export default {
components: {
Tabs,
Tab
}
};
</script>
工作原理
通過這種方式,可以在Vue 3中實現一個功能完整的Tabs組件。 ColorUI Nav組件源碼
<template>
<view>
<cu-custom bgColor="bg-gradual-pink" :isBack="true"><block slot="backText">返回</block><block slot="content">導航欄</block></cu-custom>
<view v-for="(item,index) in 10" :key="index" v-if="index==TabCur" class="bg-grey padding margin text-center">
Tab{{index}}
</view>
<view class="cu-bar bg-white solid-bottom">
<view class="action">
<text class="cuIcon-title text-orange"></text> 默認
</view>
</view>
<scroll-view scroll-x class="bg-white nav" scroll-with-animation :scroll-left="scrollLeft">
<view class="cu-item" :class="index==TabCur?'text-green cur':''" v-for="(item,index) in 10" :key="index" @tap="tabSelect" :data-id="index">
Tab{{index}}
</view>
</scroll-view>
<view class="cu-bar bg-white margin-top solid-bottom">
<view class="action">
<text class="cuIcon-title text-orange"></text> 居中
</view>
</view>
<scroll-view scroll-x class="bg-white nav text-center">
<view class="cu-item" :class="index==TabCur?'text-blue cur':''" v-for="(item,index) in 3" :key="index" @tap="tabSelect" :data-id="index">
Tab{{index}}
</view>
</scroll-view>
<view class="cu-bar bg-white margin-top solid-bottom">
<view class="action">
<text class="cuIcon-title text-orange"></text> 平分
</view>
</view>
<scroll-view scroll-x class="bg-white nav">
<view class="flex text-center">
<view class="cu-item flex-sub" :class="index==TabCur?'text-orange cur':''" v-for="(item,index) in 4" :key="index" @tap="tabSelect" :data-id="index">
Tab{{index}}
</view>
</view>
</scroll-view>
<view class="cu-bar bg-white margin-top solid-bottom">
<view class="action">
<text class="cuIcon-title text-orange"></text> 背景
</view>
</view>
<scroll-view scroll-x class="bg-red nav text-center">
<view class="cu-item" :class="index==TabCur?'text-white cur':''" v-for="(item,index) in 3" :key="index" @tap="tabSelect" :data-id="index">
Tab{{index}}
</view>
</scroll-view>
<view class="cu-bar bg-white margin-top solid-bottom">
<view class="action">
<text class="cuIcon-title text-orange"></text> 圖標
</view>
</view>
<scroll-view scroll-x class="bg-green nav text-center">
<view class="cu-item" :class="0==TabCur?'text-white cur':''" @tap="tabSelect" data-id="0">
<text class="cuIcon-camerafill"></text> 數碼
</view>
<view class="cu-item" :class="1==TabCur?'text-white cur':''" @tap="tabSelect" data-id="1">
<text class="cuIcon-upstagefill"></text> 排行榜
</view>
<view class="cu-item" :class="2==TabCur?'text-white cur':''" @tap="tabSelect" data-id="2">
<text class="cuIcon-clothesfill"></text> 皮膚
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
TabCur: 0,
scrollLeft: 0
};
},
methods: {
tabSelect(e) {
this.TabCur = e.currentTarget.dataset.id;
this.scrollLeft = (e.currentTarget.dataset.id - 1) * 60
}
}
}
</script>
首先我們先來看一下原作者大佬的組件設計
添加圖片注釋,不超過 140 字(可選)
從原組件改造整理了如下需求
<!-- Tabs.vue -->
<template>
<div :class="isCard !== false ? 'is-card' : ''">
<div
class="nav flex"
:class="[, `bg-${bg}`, `text-${text}`]"
:style="getFlex"
>
<div
class="cu-item"
v-for="(tab, index) in tabs"
:key="index"
@click="selectTab(index, tab)"
:class="[activeTab === index ? 'cur text-blue' : '']"
>
<i
v-if="tab.icon"
:class="`cuIcon-${tab.icon} text-${
activeTab === index ? 'blue' : tab.iconColor
}`"
></i>
{{ tab.label }}
</div>
</div>
<div class="tab-content"><slot :tab="tabs[activeTab]"></slot>·</div>
</div>
</template>
<script setup lang="ts">
import { ref, provide, computed, defineEmits, watch } from "vue";
interface TabItem {
label: string;
icon?: string;
iconColor?: string;
bgColor?: string;
}
const tabs = ref<TabItem[]>([]);
const activeTab = ref(0);
const emit = defineEmits(["update:modelValue", "select"]);
const selectTab = (index: number, tab: TabItem) => {
activeTab.value = index;
emit("update:modelValue", index);
emit("select", tab);
};
const addTab = (tab: TabItem) => {
tabs.value.push(tab);
return tabs.value.length - 1; // 返回新添加的tab的索引
};
provide("registerTab", addTab);
provide("activeTab", activeTab);
const props = withDefaults(
defineProps<{
modelValue: number;
center?: boolean;
bg?: string;
text?: string;
isCard?: boolean;
mode?: "center" | "flex-start" | "space-between";
}>(),
{
center: false,
bg: "white",
isCard: false,
mode: "flex-start",
modelValue: 0,
}
);
const getFlex = computed(() => {
if (props.center !== false) {
return "justify-content:center;";
}
return `justify-content:${props.mode}`;
});
watch(
() => props.modelValue,
(newVal) => {
activeTab.value = newVal;
}
);
</script>
<script lang="ts">
export default {
name: "TTabs",
};
</script>
<style>
.tab-content {
padding: 10px 16px;
background: #fff;
}
</style>
解釋
通過這些改造,我們的組件能夠支持不同的布局和背景樣式,并且標簽項可以包含圖標和自定義背景色。這樣就滿足了原組件的所有需求。 思考 那是不是就滿足了我們日常的開發需求呢,我的回答是我的是基本上滿足了,但還應該增加幾個常用功能 雙向綁定的當前選中變量:組件聯動、動態內容切換、狀態同步; select 事件回調并且傳回的是 tab:日志記錄、業務處理、路由導航; 自定義插槽:自定義樣式、復雜內容、圖標和文本組合。 代碼改進 為了滿足上述需求,我們可以進一步改進組件,增加以下功能:
改進后的 Tabs 組件
<!-- Tabs.vue -->
<template>
<div :class="isCard !== false ? 'is-card' : ''">
<div
class="nav flex"
:class="[, `bg-${bg}`, `text-${text}`]"
:style="getFlex"
>
<div
class="cu-item"
v-for="(tab, index) in tabs"
:key="index"
@click="selectTab(index, tab)"
:class="[modelValue === index ? 'cur text-blue' : '']"
>
<i
v-if="tab.icon"
:class="`cuIcon-${tab.icon} text-${
modelValue === index ? 'blue' : tab.iconColor
}`"
></i>
{{ tab.label }}
</div>
</div>
<div class="tab-content">
<slot :tab="tabs[activeTab]"></slot>·
</div>
</div>
</template>
<script setup lang="ts">
import { ref, provide, computed, defineEmits, watch } from "vue";
interface TabItem {
label: string;
icon?: string;
iconColor?: string;
bgColor?: string;
}
const tabs = ref<TabItem[]>([]);
const activeTab = ref(0);
const emit = defineEmits(["update:modelValue", "select"]);
const selectTab = (index: number, tab: TabItem) => {
activeTab.value = index;
emit("update:modelValue", index);
emit("select", tab);
};
const addTab = (tab: TabItem) => {
tabs.value.push(tab);
return tabs.value.length - 1; // 返回新添加的tab的索引
};
provide("registerTab", addTab);
provide("activeTab", activeTab);
const props = withDefaults(
defineProps<{
modelValue: number;
center?: boolean;
bg?: string;
text?: string;
isCard?: boolean;
mode?: "center" | "flex-start" | "space-between";
}>(),
{
center: false,
bg: "white",
isCard: false,
mode: "flex-start",
}
);
const getFlex = computed(() => {
if (props.center !== false) {
return "justify-content:space-between;";
}
return `justify-content:${props.mode}`;
});
watch(
() => props.modelValue,
(newVal) => {
activeTab.value = newVal;
}
);
</script>
<script lang="ts">
export default {
name: "TTabs",
};
</script>
<style>
.tab-content {
padding: 10px 16px;
background: #fff;
}
</style>
改進后的 TabItem 組件
<!-- Tab.vue -->
<template>
<div v-show="isActive">
<slot></slot>
<slot name="custom" :tab="tabData"></slot>
</div>
</template>
<script setup lang="ts">
import { inject, ref, computed, onMounted, Ref } from "vue";
const props = withDefaults(
defineProps<{
label: string;
icon?: string;
iconColor?: string;
}>(),
{
icon: "",
iconColor: "black",
}
);
const registerTab =
inject<(tab: { label: string; icon?: string; iconColor?: string }) => number>(
"registerTab"
);
const activeTab = inject<Ref<number>>("activeTab");
const index = ref<number | null>(null);
const isActive = computed(() => {
return activeTab?.value === index.value;
});
const tabData = computed(() => ({
label: props.label,
icon: props.icon,
iconColor: props.iconColor,
}));
onMounted(() => {
if (registerTab) {
index.value = registerTab({
label: props.label,
icon: props.icon,
iconColor: props.iconColor,
});
console.log(`Tab ${props.label} registered with index ${index.value}`);
}
});
</script>
<script lang="ts">
export default {
name: "TTab",
};
</script>
解釋一下 我們在創建一個 Tabs 組件系統,其中包括 Tabs 和 Tab 兩個組件:
通過 provide 和 inject 機制,Tab 組件可以注冊到 Tabs 組件中,并且 Tabs 組件可以管理和控制哪些 Tab 組件是激活狀態。 provide 和 inject provide 和 inject 是 Vue 3 中用于跨組件通信的兩個 API,特別適用于祖孫組件之間的數據傳遞。
在我們的例子中,Tabs 組件使用 provide 來提供 registerTab 和 activeTab,而 Tab 組件使用 inject 來接收這些數據。 自定義插槽 自定義插槽允許我們在組件中定義可插入的內容,并且可以傳遞數據給插槽內容。
跨組件自定義插槽傳值 跨組件自定義插槽傳值結合了 provide/inject 和作用域插槽的概念。通過 Tabs 組件提供的數據,Tab 組件可以在自定義插槽中使用這些數據。 使用示例 在使用時,確保你在自定義插槽中正確地接收傳遞的數據:
<template>
<div>
<TTitle>綜合示例:雙向綁定、事件回調、自定義插槽</TTitle>
<TTabs v-model="selectedTab" @select="handleSelect">
<TTab label="Tab 1">
<p>Content for Tab 1</p>
</TTab>
<TTab label="Tab 2">
<p>Content for Tab 2</p>
</TTab>
<TTab label="Tab 3" icon="rank" icon-color="red">
<template #custom="{ tab }">
{{ tab.label }} 自定義插槽
</template>
</TTab>
</TTabs>
<p>當前選中的標簽索引:{{ selectedTab }}</p>
<p>選中的標簽信息:{{ selectedTabInfo }}</p>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const selectedTab = ref(0);
const selectedTabInfo = ref(null);
const handleSelect = (tab) => {
selectedTabInfo.value = tab;
};
</script>
本文詳細介紹了如何實現和改造Tabs組件,涵蓋了從基礎的HTML、CSS和JavaScript實現,到在Vue 3中實現組件化,再到進一步的功能改造。通過逐步完善Tabs組件的功能,使其能夠滿足更多的開發需求。
HTML代碼(wxml)
{{item.name}}
CSS(less):
.nav-bar{ position: relative; z-index: 10; height: 90upx; white-space: nowrap; background-color: #fbfbfb; .nav-item{ display: inline-block; width: 150upx; height: 90upx; text-align: center; line-height: 90upx; font-size: 30upx; color: #a4a4a4; position: relative; } .current{ color: #3f3f3f; font-weight: bold; } }
實現效果大致為這樣的:
PS: 以上為純CSS實現部分,如果項目 tab數量 為通過接口動態獲取的,可以適當加入一些 js 計算。
JS 思路:
大致為(以微信小程序為例):
let width = 0; // 當前選中選項卡及它之前的選項卡之和總寬度 let nowWidth = 0; // 當前選項卡的寬度 //獲取可滑動總寬度 for (let i = 0; i <= index; i++) { let result = await this.getElSize('tab' + i); width += result.width; if(i === index){ nowWidth = result.width; } } // console.log(width, nowWidth, windowWidth) //等待swiper動畫結束再修改tabbar this.$nextTick(() => { if (width - nowWidth/2 > windowWidth / 2) { //如果當前項越過中心點,將其放在屏幕中心 this.scrollLeft = width - nowWidth/2 - windowWidth / 2; console.log(this.scrollLeft) }else{ this.scrollLeft = 0; } if(typeof e === 'object'){ this.tabCurrentIndex = index; } this.tabCurrentIndex = index; })
ps: getElSize() 函數代碼為:
getElSize(id) { return new Promise((res, rej) => { let el = uni.createSelectorQuery().select('#' + id); el.fields({ size: true, scrollOffset: true, rect: true }, (data) => { res(data); }).exec(); }); },
這樣就可以實現動態 tab 切換了:
*請認真填寫需求信息,我們會在24小時內與您取得聯系。