赞
踩
描述:移动端侧边栏导航和右侧内容双向联动,用户点击左侧侧边栏导航,右侧内容能快速定位到内容区域,右侧内容在滚动时,左侧导航会高亮显示当前内容所对应的导航。
问题难点:右侧内容滚动时,左侧导航能定位到当前内容对应的导航。
解决方案:使用 js 提供的 intersectionObserver api 实现,此 api 可观察目标元素与其祖先元素或顶级文档视口交叉状态,通过交叉状态来实现双向联动。
具体实现如下:
1、侧边栏导航子组件 sideBar.vue
- <template>
- <div class="side-bar-wrap">
- <div class="body">
- <div
- :class="['side-bar-item', `side-bar-item-${i}`]"
- v-for="(item, i) in data"
- :key="i"
- @click="setIndex(i)"
- >
- <p :class="[index === i && 'active']">{{ item.title }}</p>
- </div>
- </div>
- </div>
- </template>
-
- <script>
- export default {
- name: 'sideBar',
- model: {
- prop: 'activeKey'
- },
- props: {
- activeKey: {
- type: Number,
- default: 0
- },
- data: {
- type: Array,
- default: () => []
- }
- },
- data() {
- return {
- index: this.activeKey
- };
- },
- watch: {
- activeKey(val) {
- this.index = val;
- }
- },
- methods: {
- setIndex(index) {
- if (index !== this.index) {
- this.index = index;
- this.$emit('change', index);
- }
- }
- }
- };
- </script>
-
- <style scoped>
- .body {
- width: 0.92rem;
- background: #f7f9f9;
- overflow-y: auto;
- -webkit-overflow-scrolling: touch;
- }
-
- .side-bar-item {
- min-height: 0.62rem;
- border-bottom: 0.01rem #e8edf0 solid;
- display: flex;
- align-items: center;
- }
-
- .side-bar-item:last-child {
- border-bottom: none;
- }
-
- .side-bar-item p {
- width: 0.72rem;
- padding: 0.08rem 0;
- margin-left: 0.13rem;
- font-size: 0.16rem;
- font-weight: 600;
- color: #253440;
- line-height: 0.22rem;
- }
-
- .side-bar-item p.active {
- color: #2c85ff;
- position: relative;
- }
-
- .side-bar-item p.active::before {
- content: '';
- display: block;
- opacity: 0.74;
- border-top: 0.06rem solid transparent;
- border-bottom: 0.06rem solid transparent;
- border-left: 0.05rem solid #0164ec;
- position: absolute;
- left: -0.13rem;
- top: 50%;
- transform: translateY(-50%);
- }
- </style>
2、内容区域子组件 content.vue
- <template>
- <div class="detail-list-wrap">
- <div
- :class="['detail-block', `detail-block-${index}`]"
- v-for="(item, index) in data"
- :key="index"
- v-bind:data-index="index"
- >
- <div class="detail-block-title">
- <span>{{ item.title }}</span>
- </div>
- <div></div>
- <div :class="['detail-block-paragraph']">{{ item.content }}</div>
- </div>
- </div>
- </template>
-
- <script>
- export default {
- name: 'content',
- props: {
- data: Array
- }
- };
- </script>
-
- <style scoped>
- .detail-block {
- margin-top: 0.24rem;
- }
-
- .detail-block-title {
- line-height: 0.25rem;
- text-align: center;
- }
-
- .detail-block-title img {
- width: 0.32rem;
- height: 0.12rem;
- }
-
- .detail-block-title span {
- font-weight: 600;
- margin: 0 0.06rem;
- font-size: 0.18rem;
- color: #253440;
- background: linear-gradient(180deg, #fcefcc 0%, #97693d 100%);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- }
-
- .detail-block-paragraph {
- font-size: 0.16rem;
- color: #253440;
- line-height: 0.3rem;
- margin-top: 0.12rem;
- }
- </style>
3、父组件 index.vue
- <template>
- <div class="finance-morning-tea-detail-page">
- <div class="body-nav-btn" @click="handleNavToggle"></div>
- <div class="detail-body">
- <div class="body-side-bar" v-show="navShow">
- <SideBar
- ref="sideBarRef"
- :data="data"
- v-model="activeKey"
- @change="handleChange"
- />
- </div>
- <div class="body-content">
- <Content :data="data" ref="detailListRef" />
- </div>
- </div>
- </div>
- </template>
-
- <script>
- import Content from './content.vue';
- import SideBar from './sideBar.vue';
-
- export default {
- name: 'financeMorningDetail',
- components: { SideBar, Content },
- data() {
- return {
- data: [
- {
- id: 0,
- title: '龟虽寿',
- content:
- '神龟虽寿,犹有竟时;腾蛇乘雾,终为土灰。老骥伏枥,志在千里;烈士暮年,壮心不已。盈缩之期,不但在天;养怡之福,可得永年。幸甚至哉,歌以咏志。神龟虽寿,犹有竟时;腾蛇乘雾,终为土灰。老骥伏枥,志在千里;烈士暮年,壮心不已。盈缩之期,不但在天;养怡之福,可得永年。幸甚至哉,歌以咏志。神龟虽寿,犹有竟时;腾蛇乘雾,终为土灰。老骥伏枥,志在千里;烈士暮年,壮心不已。盈缩之期,不但在天;养怡之福,可得永年。幸甚至哉,歌以咏志。神龟虽寿,犹有竟时;腾蛇乘雾,终为土灰。老骥伏枥,志在千里;烈士暮年,壮心不已。盈缩之期,不但在天;养怡之福,可得永年。幸甚至哉,歌以咏志。神龟虽寿,犹有竟时;腾蛇乘雾,终为土灰。老骥伏枥,志在千里;烈士暮年,壮心不已。盈缩之期,不但在天;养怡之福,可得永年。幸甚至哉,歌以咏志。神龟虽寿,犹有竟时;腾蛇乘雾,终为土灰。老骥伏枥,志在千里;烈士暮年,壮心不已。盈缩之期,不但在天;养怡之福,可得永年。幸甚至哉,歌以咏志。'
- },
- {
- id: 1,
- title: '观沧海',
- content:
- '东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志'
- },
- {
- id: 2,
- title: '短歌行',
- content:
- '对酒当歌,人生几何!譬如朝露,去日苦多。慨当以慷,忧思难忘。何以解忧?唯有杜康。青青子衿,悠悠我心。但为君故,沉吟至今。呦呦鹿鸣,食野之苹。我有嘉宾,鼓瑟吹笙。明明如月,何时可掇?忧从中来,不可断绝。越陌度阡,枉用相存。契阔谈䜩,心念旧恩。月明星稀,乌鹊南飞。绕树三匝,何枝可依?山不厌高,海不厌深。周公吐哺,天下归心对酒当歌,人生几何!譬如朝露,去日苦多。慨当以慷,忧思难忘。何以解忧?唯有杜康。青青子衿,悠悠我心。但为君故,沉吟至今。呦呦鹿鸣,食野之苹。我有嘉宾,鼓瑟吹笙。明明如月,何时可掇?忧从中来,不可断绝。越陌度阡,枉用相存。契阔谈䜩,心念旧恩。月明星稀,乌鹊南飞。绕树三匝,何枝可依?山不厌高,海不厌深。周公吐哺,天下归心对酒当歌,人生几何!譬如朝露,去日苦多。慨当以慷,忧思难忘。何以解忧?唯有杜康。青青子衿,悠悠我心。但为君故,沉吟至今。呦呦鹿鸣,食野之苹。我有嘉宾,鼓瑟吹笙。明明如月,何时可掇?忧从中来,不可断绝。越陌度阡,枉用相存。契阔谈䜩,心念旧恩。月明星稀,乌鹊南飞。绕树三匝,何枝可依?山不厌高,海不厌深。周公吐哺,天下归心'
- },
- {
- id: 3,
- title: '蒿里行',
- content:
- '关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。'
- },
- {
- id: 4,
- title: '对酒',
- content:
- '对酒歌,太平时,吏不呼门。王者贤且明,宰相股肱皆忠良。咸礼让,民无所争讼。三年耕有九年储,仓谷满盈。斑白不负载。雨泽如此,百谷用成。却走马,以粪其土田。爵公侯伯子男,咸爱其民,以黜陟幽明。子养有若父与兄。犯礼法,轻重随其刑。路无拾遗之私。囹圄空虚,冬节不断。人耄耋,皆得以寿终。恩德广及草木昆虫。对酒歌,太平时,吏不呼门。王者贤且明,宰相股肱皆忠良。咸礼让,民无所争讼。三年耕有九年储,仓谷满盈。斑白不负载。雨泽如此,百谷用成。却走马,以粪其土田。爵公侯伯子男,咸爱其民,以黜陟幽明。子养有若父与兄。犯礼法,轻重随其刑。路无拾遗之私。囹圄空虚,冬节不断。人耄耋,皆得以寿终。恩德广及草木昆虫。对酒歌,太平时,吏不呼门。王者贤且明,宰相股肱皆忠良。咸礼让,民无所争讼。三年耕有九年储,仓谷满盈。斑白不负载。雨泽如此,百谷用成。却走马,以粪其土田。爵公侯伯子男,咸爱其民,以黜陟幽明。子养有若父与兄。犯礼法,轻重随其刑。路无拾遗之私。囹圄空虚,冬节不断。人耄耋,皆得以寿终。恩德广及草木昆虫。对酒歌,太平时,吏不呼门。王者贤且明,宰相股肱皆忠良。咸礼让,民无所争讼。三年耕有九年储,仓谷满盈。斑白不负载。雨泽如此,百谷用成。却走马,以粪其土田。爵公侯伯子男,咸爱其民,以黜陟幽明。子养有若父与兄。犯礼法,轻重随其刑。路无拾遗之私。囹圄空虚,冬节不断。人耄耋,皆得以寿终。恩德广及草木昆虫。'
- }
- ],
- activeKey: 0,
- navShow: false,
- io: null
- };
- },
- // 动态数据的写法:如果数据是接口返回的,则可以通过一下方式来写
- // watch: {
- // data() {
- // this.$nextTick(() => {
- // this.intersectionObserver();
- // });
- // }
- // },
- mounted() {
- // 静态数据的写法
- this.$nextTick(() => {
- this.intersectionObserver();
- });
- },
- methods: {
- intersectionObserver() {
- this.io = new IntersectionObserver((entries) => {
- entries.forEach((entry) => {
- if (entry.intersectionRatio > 0) {
- this.activeKey = parseInt(entry.target.dataset.index);
- this.$refs.sideBarRef.$el
- .querySelector(`.side-bar-item-${this.activeKey}`)
- .scrollIntoView({
- block: 'end'
- });
- }
- });
- });
- const detailBlockElList = this.$refs.detailListRef.$el.querySelectorAll(
- '.detail-block'
- );
- detailBlockElList.forEach((el) => {
- this.io.observe(el);
- });
- },
-
- handleNavToggle() {
- this.navShow = !this.navShow;
- this.activeKey = 0;
- },
-
- handleChange(index) {
- this.$refs.detailListRef.$el
- .querySelector(`.detail-block-${index}`)
- .scrollIntoView();
- }
- }
- };
- </script>
-
- <style scoped>
- .finance-morning-tea-detail-page {
- background-color: #fff;
- overflow: auto;
- height: 100vh;
- }
-
- .detail-title-wrap {
- padding: 0.16rem 0.17rem;
- }
-
- .detail-title {
- font-size: 0.18rem;
- font-weight: 600;
- color: #253440;
- line-height: 0.22rem;
- }
-
- .detail-date {
- font-size: 0.14rem;
- color: #8695a1;
- margin-top: 0.04rem;
- }
-
- .body-nav-btn {
- width: 0.46rem;
- height: 0.4rem;
- box-shadow: 0rem 0rem 0.13rem 0rem rgba(0, 60, 195, 0.25);
- border-radius: 0rem 1rem 1rem 0rem;
- background-color: #2c85ff;
- background: #2c85ff url('../../assets/Icon/icon_nav.png') 0.11rem center
- no-repeat;
- background-size: 0.2rem 0.2rem;
- position: fixed;
- left: 0;
- top: 0;
- z-index: 10;
- }
-
- .detail-body {
- display: flex;
- height: 85vh;
- }
-
- .body-side-bar {
- padding-top: 0.54rem;
- width: 0.92rem;
- overflow-y: scroll;
- background-color: #f7f9f9;
- }
-
- .body-content {
- padding: 0 0.1rem 0 0.13rem;
- flex: 1;
- overflow-y: scroll;
- }
-
- .copyright-notice {
- margin: 0.16rem 0;
- font-size: 0.12rem;
- color: #8695a1;
- line-height: 0.17rem;
- }
- </style>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。