vue实现左右联动
实现左右联动的基本思路
在Vue中实现左右联动效果,通常需要两个列表或区域,通过监听滚动事件或点击事件来同步它们的位置。常见的应用场景包括商品分类与商品列表联动、城市选择器等。
监听滚动事件实现联动
创建两个独立的滚动区域,通过计算滚动位置来同步它们的状态。

<template>
<div class="container">
<div class="left" ref="left" @scroll="handleLeftScroll">
<!-- 左侧内容 -->
</div>
<div class="right" ref="right" @scroll="handleRightScroll">
<!-- 右侧内容 -->
</div>
</div>
</template>
<script>
export default {
data() {
return {
isScrolling: false
}
},
methods: {
handleLeftScroll() {
if (this.isScrolling) return
this.isScrolling = true
// 计算并同步右侧滚动位置
this.$nextTick(() => {
this.isScrolling = false
})
},
handleRightScroll() {
if (this.isScrolling) return
this.isScrolling = true
// 计算并同步左侧滚动位置
this.$nextTick(() => {
this.isScrolling = false
})
}
}
}
</script>
<style>
.container {
display: flex;
height: 100vh;
}
.left {
width: 30%;
overflow-y: auto;
}
.right {
width: 70%;
overflow-y: auto;
}
</style>
使用Intersection Observer API
对于更精确的联动效果,可以使用Intersection Observer API来检测元素是否进入视口。
methods: {
initObserver() {
const options = {
root: null,
rootMargin: '0px',
threshold: 0.5
}
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 联动逻辑
}
})
}, options)
// 观察所有需要联动的元素
document.querySelectorAll('.target').forEach(el => {
this.observer.observe(el)
})
}
},
mounted() {
this.initObserver()
},
beforeDestroy() {
this.observer.disconnect()
}
基于锚点的联动实现
对于点击跳转类型的联动,可以使用锚点定位方式。

<template>
<div class="container">
<div class="left">
<ul>
<li v-for="item in items" :key="item.id" @click="scrollTo(item.id)">
{{ item.name }}
</li>
</ul>
</div>
<div class="right">
<div v-for="item in items" :key="item.id" :id="item.id">
{{ item.content }}
</div>
</div>
</div>
</template>
<script>
export default {
methods: {
scrollTo(id) {
const element = document.getElementById(id)
if (element) {
element.scrollIntoView({ behavior: 'smooth' })
}
}
}
}
</script>
性能优化建议
对于大量数据的联动场景,建议使用虚拟滚动技术来提高性能。可以使用第三方库如vue-virtual-scroller来实现。
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
export default {
components: {
RecycleScroller
}
}
完整示例代码
以下是一个商品分类联动的完整示例:
<template>
<div class="container">
<div class="left-menu">
<ul>
<li
v-for="(category, index) in categories"
:key="category.id"
:class="{ active: currentIndex === index }"
@click="selectCategory(index)"
>
{{ category.name }}
</li>
</ul>
</div>
<div class="right-content" ref="rightContent">
<div
v-for="category in categories"
:key="category.id"
:ref="'category-' + category.id"
class="category-section"
>
<h3>{{ category.name }}</h3>
<!-- 商品列表内容 -->
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
currentIndex: 0,
categories: [
{ id: 1, name: '分类1' },
{ id: 2, name: '分类2' },
// 更多分类...
],
observer: null
}
},
methods: {
selectCategory(index) {
this.currentIndex = index
const refName = 'category-' + this.categories[index].id
this.$refs[refName][0].scrollIntoView({ behavior: 'smooth' })
},
initIntersectionObserver() {
const options = {
root: this.$refs.rightContent,
threshold: 0.5
}
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const id = entry.target.getAttribute('data-id')
this.currentIndex = this.categories.findIndex(c => c.id == id)
}
})
}, options)
this.categories.forEach(category => {
const element = this.$refs[`category-${category.id}`][0]
element.setAttribute('data-id', category.id)
this.observer.observe(element)
})
}
},
mounted() {
this.initIntersectionObserver()
},
beforeDestroy() {
if (this.observer) {
this.observer.disconnect()
}
}
}
</script>
<style>
.container {
display: flex;
height: 100vh;
}
.left-menu {
width: 30%;
overflow-y: auto;
}
.right-content {
width: 70%;
overflow-y: auto;
}
.active {
color: red;
font-weight: bold;
}
.category-section {
min-height: 500px;
}
</style>
以上方法可以根据实际需求选择或组合使用,实现不同复杂度的左右联动效果。






