效果:红色位置代码
实现功能描述:
左侧内容滚动的时候,右侧首字母会变换选中,右侧选中指定首字母,左侧内容滚到指定位置。
组件game_list
<template>
<div>
<div class="game_list">
<div class="left_list_box" ref="scrollBox" @scroll="contentScroll">
<div
v-for="(key, index) in Object.keys(gamesObject).sort()"
:key="index"
:id="key"
:ref="key"
>
<div class="title_name">{{ key }}</div>
<div class="left_list_game">
<div
class="game_item"
v-for="(item, _index) in gamesObject[key]"
:key="_index"
:class="$attrs['letter-hidden'] ? 'letter_hidden' : ''"
>
<van-image :src="item.Icon" width="1.12rem" height="1.12rem"></van-image>
<p :class="$attrs['letter-hidden'] ? 'show_oneline' : ''">{{ item.Name }}</p>
</div>
</div>
</div>
</div>
<div class="right_word_box" v-if="!$attrs['letter-hidden']" @scroll="menuScroll">
<div ref="rightBox" :style="{ top: rightScrollTop + 'px' }">
<div
class="letter add_margin_top"
:class="isChoose == '热' ? 'is_choose' : ''"
@click="toLetter('热')"
>
热
</div>
<div
class="letter"
v-for="(letter, index) in firstLetter.split('')"
:key="index"
:class="isChoose == letter ? 'is_choose' : ''"
@click="toLetter(letter)"
:ref="'menu_' + letter"
>
{{ letter }}
</div>
</div>
</div>
</div>
<!-- <empty-page></empty-page> -->
</div>
</template>
<script>
import { nextTick } from 'vue'
import EmptyPage from '../../components/Empty/EmptyPage.vue'
export default {
name: '',
props: {
gameList: {
type: Array
}
},
data() {
return {
// 处理后的游戏列表
gamesObject: {},
firstLetter: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#',
isChoose: '热',
clickMenu: false, //点击菜单时候
rightScrollTop: 200 //右侧菜单的滚动距离
}
},
components: { EmptyPage },
watch: {
gameList(newVal) {
let arr = newVal.concat()
arr.reverse()
let tempName = ''
//重构列表结构
arr.forEach((game, index) => {
if (index == 0) {
this.$set(this.gamesObject, game.NameInitials.toUpperCase(), [])
tempName = game.NameInitials.toUpperCase()
}
if (tempName != game.NameInitials.toUpperCase()) {
this.$set(this.gamesObject, game.NameInitials.toUpperCase(), [])
}
this.gamesObject[game.NameInitials.toUpperCase()].push(game)
})
},
// 选中的首字母监听用于右侧首字母栏的滚动
isChoose(newVal) {
// 获取内容盒子到头部的距离
let headerHeight = this.$refs['rightBox'].offsetTop
// 当前点击字母的距离右侧菜单框的距离
let chooseTop = this.$refs['menu_' + newVal][0].offsetTop - headerHeight
// 获取右侧菜单盒子可见高度
let rightMenuHeight = this.$refs['scrollBox'].offsetHeight
// 右侧菜单内容实际高度
let rightRealHeight = this.$refs['rightBox'].offsetHeight
// 如果当前点击的字母的位置占据了rightMenuHeight高后四分之一的位置。就让右侧菜单网上滚一个二分之一rightMenuHeight的高度
if (chooseTop >= rightMenuHeight * (3 / 4)) {
//让右侧菜单往上滚动1/2rightMenuHeight后没有到达右侧菜单的实际高度
if (this.rightScrollTop + rightMenuHeight * (3 / 2) < rightRealHeight) {
console.log('执行')
// 继续向上滚动
nextTick(() => {
this.rightScrollTop = this.rightScrollTop + rightMenuHeight * (3 / 2)
})
} else {
//让右侧菜单往上滚动1/2rightMenuHeight过程中会打到达右侧菜单的实际高度,为最后一下的滚动
// debugger
nextTick(() => {
this.rightScrollTop =
this.rightScrollTop + rightRealHeight - this.rightScrollTop - rightMenuHeight
})
}
}
}
},
created() {},
mounted() {},
methods: {
toLetter(letter) {
this.isChoose = letter
this.clickMenu = true
const point = document.getElementById(letter)
point &&
point.scrollIntoView({
block: 'start',
behavior: 'smooth'
})
// 设置滚动之后过一段时间恢复clickMenu字段,避免自定义滚动操作和点击后滚动同时出现
setTimeout(() => {
this.clickMenu = false
}, 1000)
},
// 监听内容盒子的滚动事件
contentScroll(e) {
// 判断是不是鼠标点击菜单导致滚动
if (!this.clickMenu) {
// 记录内容滚动距离
let scrollTop = e.srcElement.scrollTop
// 获取内容盒子到头部的距离
let headerHeight = this.$refs['scrollBox'].offsetTop
// 根据滚动值,查询在哪两个字母的区间内
for (let i = 0; i < this.firstLetter.length - 1; i++) {
// 首个字母出现的位置
let letter_before = 0
// 第二个字母出现的位置
let letter_after = null
if (this.$refs[this.firstLetter.split('')[i]]) {
letter_before = this.$refs[this.firstLetter.split('')[i]][0].offsetTop - headerHeight
}
if (i != this.firstLetter.length - 1 && this.$refs[this.firstLetter.split('')[i + 1]]) {
letter_after = this.$refs[this.firstLetter.split('')[i + 1]][0].offsetTop - headerHeight
}
// 鼠标滚动一次是100
if (scrollTop + 100 >= letter_before && scrollTop + 100 < letter_after) {
this.isChoose = this.firstLetter.split('')[i]
break
}
}
}
},
// 监听右侧菜单盒子滚动事件
menuScroll(e) {
this.rightScrollTop = e.srcElement.scrollTop
}
}
}
</script>
<style lang="scss" scoped>
.game_list {
width: 100%;
display: flex;
justify-content: space-between;
}
// 左侧游戏列表
.left_list_box {
flex: 1;
height: calc(100vh - 0.92rem);
overflow-y: scroll;
padding: 0 0.14rem;
padding-bottom: 1.08rem;
box-sizing: border-box;
.title_name {
font-size: 0.28rem;
color: #111;
font-weight: bold;
padding: 0.32rem 0.3rem;
}
.left_list_game {
overflow: hidden;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-content: flex-start;
border-bottom: 0.02rem solid #edeef1;
padding-bottom: 0.34rem;
.game_item {
width: 1.48rem;
display: flex;
flex-direction: column;
align-items: center;
margin: 0 0.06rem 0.2rem;
height: max-content;
p {
width: 1.48rem;
font-size: 0.24rem;
color: #555555;
margin-top: 0.22rem;
white-space: pre-wrap;
text-align: center;
}
// 只显示一行
.show_oneline {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
// 当右侧首字母列隐藏时候
.letter_hidden {
margin: 0 0.16rem 0.2rem;
}
}
}
/* 右侧首字母选择 */
.right_word_box {
width: 0.8rem;
box-sizing: border-box;
height: calc(100vh - 0.92rem);
overflow-y: scroll;
padding-bottom: 1.08rem;
padding-top: 0.5rem;
.letter {
height: 0.7rem;
line-height: 0.7rem;
text-align: center;
font-size: 0.28rem;
color: #111;
}
.is_choose {
width: 0.8rem;
height: 0.4rem;
line-height: 0.4rem;
text-align: center;
font-size: 0.28rem;
background: linear-gradient(90deg, #ff650d, #ffa40d);
color: #fff;
border-radius: 0.3rem 0 0 0.3rem;
padding: 0.04rem 0;
box-sizing: content-box;
}
}
</style>
使用组件