当前位置:   article > 正文

vue.js 移动端侧边栏导航和内容双向联动_顶部导航栏和内容联动

顶部导航栏和内容联动

描述:移动端侧边栏导航和右侧内容双向联动,用户点击左侧侧边栏导航,右侧内容能快速定位到内容区域,右侧内容在滚动时,左侧导航会高亮显示当前内容所对应的导航。

问题难点:右侧内容滚动时,左侧导航能定位到当前内容对应的导航。

解决方案:使用 js 提供的 intersectionObserver api 实现,此 api 可观察目标元素与其祖先元素或顶级文档视口交叉状态,通过交叉状态来实现双向联动。

具体实现如下:

1、侧边栏导航子组件 sideBar.vue

  1. <template>
  2. <div class="side-bar-wrap">
  3. <div class="body">
  4. <div
  5. :class="['side-bar-item', `side-bar-item-${i}`]"
  6. v-for="(item, i) in data"
  7. :key="i"
  8. @click="setIndex(i)"
  9. >
  10. <p :class="[index === i && 'active']">{{ item.title }}</p>
  11. </div>
  12. </div>
  13. </div>
  14. </template>
  15. <script>
  16. export default {
  17. name: 'sideBar',
  18. model: {
  19. prop: 'activeKey'
  20. },
  21. props: {
  22. activeKey: {
  23. type: Number,
  24. default: 0
  25. },
  26. data: {
  27. type: Array,
  28. default: () => []
  29. }
  30. },
  31. data() {
  32. return {
  33. index: this.activeKey
  34. };
  35. },
  36. watch: {
  37. activeKey(val) {
  38. this.index = val;
  39. }
  40. },
  41. methods: {
  42. setIndex(index) {
  43. if (index !== this.index) {
  44. this.index = index;
  45. this.$emit('change', index);
  46. }
  47. }
  48. }
  49. };
  50. </script>
  51. <style scoped>
  52. .body {
  53. width: 0.92rem;
  54. background: #f7f9f9;
  55. overflow-y: auto;
  56. -webkit-overflow-scrolling: touch;
  57. }
  58. .side-bar-item {
  59. min-height: 0.62rem;
  60. border-bottom: 0.01rem #e8edf0 solid;
  61. display: flex;
  62. align-items: center;
  63. }
  64. .side-bar-item:last-child {
  65. border-bottom: none;
  66. }
  67. .side-bar-item p {
  68. width: 0.72rem;
  69. padding: 0.08rem 0;
  70. margin-left: 0.13rem;
  71. font-size: 0.16rem;
  72. font-weight: 600;
  73. color: #253440;
  74. line-height: 0.22rem;
  75. }
  76. .side-bar-item p.active {
  77. color: #2c85ff;
  78. position: relative;
  79. }
  80. .side-bar-item p.active::before {
  81. content: '';
  82. display: block;
  83. opacity: 0.74;
  84. border-top: 0.06rem solid transparent;
  85. border-bottom: 0.06rem solid transparent;
  86. border-left: 0.05rem solid #0164ec;
  87. position: absolute;
  88. left: -0.13rem;
  89. top: 50%;
  90. transform: translateY(-50%);
  91. }
  92. </style>

2、内容区域子组件 content.vue

  1. <template>
  2. <div class="detail-list-wrap">
  3. <div
  4. :class="['detail-block', `detail-block-${index}`]"
  5. v-for="(item, index) in data"
  6. :key="index"
  7. v-bind:data-index="index"
  8. >
  9. <div class="detail-block-title">
  10. <span>{{ item.title }}</span>
  11. </div>
  12. <div></div>
  13. <div :class="['detail-block-paragraph']">{{ item.content }}</div>
  14. </div>
  15. </div>
  16. </template>
  17. <script>
  18. export default {
  19. name: 'content',
  20. props: {
  21. data: Array
  22. }
  23. };
  24. </script>
  25. <style scoped>
  26. .detail-block {
  27. margin-top: 0.24rem;
  28. }
  29. .detail-block-title {
  30. line-height: 0.25rem;
  31. text-align: center;
  32. }
  33. .detail-block-title img {
  34. width: 0.32rem;
  35. height: 0.12rem;
  36. }
  37. .detail-block-title span {
  38. font-weight: 600;
  39. margin: 0 0.06rem;
  40. font-size: 0.18rem;
  41. color: #253440;
  42. background: linear-gradient(180deg, #fcefcc 0%, #97693d 100%);
  43. -webkit-background-clip: text;
  44. -webkit-text-fill-color: transparent;
  45. }
  46. .detail-block-paragraph {
  47. font-size: 0.16rem;
  48. color: #253440;
  49. line-height: 0.3rem;
  50. margin-top: 0.12rem;
  51. }
  52. </style>

3、父组件 index.vue

  1. <template>
  2. <div class="finance-morning-tea-detail-page">
  3. <div class="body-nav-btn" @click="handleNavToggle"></div>
  4. <div class="detail-body">
  5. <div class="body-side-bar" v-show="navShow">
  6. <SideBar
  7. ref="sideBarRef"
  8. :data="data"
  9. v-model="activeKey"
  10. @change="handleChange"
  11. />
  12. </div>
  13. <div class="body-content">
  14. <Content :data="data" ref="detailListRef" />
  15. </div>
  16. </div>
  17. </div>
  18. </template>
  19. <script>
  20. import Content from './content.vue';
  21. import SideBar from './sideBar.vue';
  22. export default {
  23. name: 'financeMorningDetail',
  24. components: { SideBar, Content },
  25. data() {
  26. return {
  27. data: [
  28. {
  29. id: 0,
  30. title: '龟虽寿',
  31. content:
  32. '神龟虽寿,犹有竟时;腾蛇乘雾,终为土灰。老骥伏枥,志在千里;烈士暮年,壮心不已。盈缩之期,不但在天;养怡之福,可得永年。幸甚至哉,歌以咏志。神龟虽寿,犹有竟时;腾蛇乘雾,终为土灰。老骥伏枥,志在千里;烈士暮年,壮心不已。盈缩之期,不但在天;养怡之福,可得永年。幸甚至哉,歌以咏志。神龟虽寿,犹有竟时;腾蛇乘雾,终为土灰。老骥伏枥,志在千里;烈士暮年,壮心不已。盈缩之期,不但在天;养怡之福,可得永年。幸甚至哉,歌以咏志。神龟虽寿,犹有竟时;腾蛇乘雾,终为土灰。老骥伏枥,志在千里;烈士暮年,壮心不已。盈缩之期,不但在天;养怡之福,可得永年。幸甚至哉,歌以咏志。神龟虽寿,犹有竟时;腾蛇乘雾,终为土灰。老骥伏枥,志在千里;烈士暮年,壮心不已。盈缩之期,不但在天;养怡之福,可得永年。幸甚至哉,歌以咏志。神龟虽寿,犹有竟时;腾蛇乘雾,终为土灰。老骥伏枥,志在千里;烈士暮年,壮心不已。盈缩之期,不但在天;养怡之福,可得永年。幸甚至哉,歌以咏志。'
  33. },
  34. {
  35. id: 1,
  36. title: '观沧海',
  37. content:
  38. '东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志东临碣石,以观沧海。水何澹澹,山岛竦峙。树木丛生,百草丰茂。秋风萧瑟,洪波涌起。日月之行,若出其中;星汉灿烂,若出其里。幸甚至哉,歌以咏志'
  39. },
  40. {
  41. id: 2,
  42. title: '短歌行',
  43. content:
  44. '对酒当歌,人生几何!譬如朝露,去日苦多。慨当以慷,忧思难忘。何以解忧?唯有杜康。青青子衿,悠悠我心。但为君故,沉吟至今。呦呦鹿鸣,食野之苹。我有嘉宾,鼓瑟吹笙。明明如月,何时可掇?忧从中来,不可断绝。越陌度阡,枉用相存。契阔谈䜩,心念旧恩。月明星稀,乌鹊南飞。绕树三匝,何枝可依?山不厌高,海不厌深。周公吐哺,天下归心对酒当歌,人生几何!譬如朝露,去日苦多。慨当以慷,忧思难忘。何以解忧?唯有杜康。青青子衿,悠悠我心。但为君故,沉吟至今。呦呦鹿鸣,食野之苹。我有嘉宾,鼓瑟吹笙。明明如月,何时可掇?忧从中来,不可断绝。越陌度阡,枉用相存。契阔谈䜩,心念旧恩。月明星稀,乌鹊南飞。绕树三匝,何枝可依?山不厌高,海不厌深。周公吐哺,天下归心对酒当歌,人生几何!譬如朝露,去日苦多。慨当以慷,忧思难忘。何以解忧?唯有杜康。青青子衿,悠悠我心。但为君故,沉吟至今。呦呦鹿鸣,食野之苹。我有嘉宾,鼓瑟吹笙。明明如月,何时可掇?忧从中来,不可断绝。越陌度阡,枉用相存。契阔谈䜩,心念旧恩。月明星稀,乌鹊南飞。绕树三匝,何枝可依?山不厌高,海不厌深。周公吐哺,天下归心'
  45. },
  46. {
  47. id: 3,
  48. title: '蒿里行',
  49. content:
  50. '关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。关东有义士,兴兵讨群凶。初期会盟津,乃心在咸阳。军合力不齐,踌躇而雁行。势利使人争,嗣还自相戕。淮南弟称号,刻玺于北方。铠甲生虮虱,万姓以死亡。白骨露於野,千里无鸡鸣。生民百遗一,念之断人肠。'
  51. },
  52. {
  53. id: 4,
  54. title: '对酒',
  55. content:
  56. '对酒歌,太平时,吏不呼门。王者贤且明,宰相股肱皆忠良。咸礼让,民无所争讼。三年耕有九年储,仓谷满盈。斑白不负载。雨泽如此,百谷用成。却走马,以粪其土田。爵公侯伯子男,咸爱其民,以黜陟幽明。子养有若父与兄。犯礼法,轻重随其刑。路无拾遗之私。囹圄空虚,冬节不断。人耄耋,皆得以寿终。恩德广及草木昆虫。对酒歌,太平时,吏不呼门。王者贤且明,宰相股肱皆忠良。咸礼让,民无所争讼。三年耕有九年储,仓谷满盈。斑白不负载。雨泽如此,百谷用成。却走马,以粪其土田。爵公侯伯子男,咸爱其民,以黜陟幽明。子养有若父与兄。犯礼法,轻重随其刑。路无拾遗之私。囹圄空虚,冬节不断。人耄耋,皆得以寿终。恩德广及草木昆虫。对酒歌,太平时,吏不呼门。王者贤且明,宰相股肱皆忠良。咸礼让,民无所争讼。三年耕有九年储,仓谷满盈。斑白不负载。雨泽如此,百谷用成。却走马,以粪其土田。爵公侯伯子男,咸爱其民,以黜陟幽明。子养有若父与兄。犯礼法,轻重随其刑。路无拾遗之私。囹圄空虚,冬节不断。人耄耋,皆得以寿终。恩德广及草木昆虫。对酒歌,太平时,吏不呼门。王者贤且明,宰相股肱皆忠良。咸礼让,民无所争讼。三年耕有九年储,仓谷满盈。斑白不负载。雨泽如此,百谷用成。却走马,以粪其土田。爵公侯伯子男,咸爱其民,以黜陟幽明。子养有若父与兄。犯礼法,轻重随其刑。路无拾遗之私。囹圄空虚,冬节不断。人耄耋,皆得以寿终。恩德广及草木昆虫。'
  57. }
  58. ],
  59. activeKey: 0,
  60. navShow: false,
  61. io: null
  62. };
  63. },
  64. // 动态数据的写法:如果数据是接口返回的,则可以通过一下方式来写
  65. // watch: {
  66. // data() {
  67. // this.$nextTick(() => {
  68. // this.intersectionObserver();
  69. // });
  70. // }
  71. // },
  72. mounted() {
  73. // 静态数据的写法
  74. this.$nextTick(() => {
  75. this.intersectionObserver();
  76. });
  77. },
  78. methods: {
  79. intersectionObserver() {
  80. this.io = new IntersectionObserver((entries) => {
  81. entries.forEach((entry) => {
  82. if (entry.intersectionRatio > 0) {
  83. this.activeKey = parseInt(entry.target.dataset.index);
  84. this.$refs.sideBarRef.$el
  85. .querySelector(`.side-bar-item-${this.activeKey}`)
  86. .scrollIntoView({
  87. block: 'end'
  88. });
  89. }
  90. });
  91. });
  92. const detailBlockElList = this.$refs.detailListRef.$el.querySelectorAll(
  93. '.detail-block'
  94. );
  95. detailBlockElList.forEach((el) => {
  96. this.io.observe(el);
  97. });
  98. },
  99. handleNavToggle() {
  100. this.navShow = !this.navShow;
  101. this.activeKey = 0;
  102. },
  103. handleChange(index) {
  104. this.$refs.detailListRef.$el
  105. .querySelector(`.detail-block-${index}`)
  106. .scrollIntoView();
  107. }
  108. }
  109. };
  110. </script>
  111. <style scoped>
  112. .finance-morning-tea-detail-page {
  113. background-color: #fff;
  114. overflow: auto;
  115. height: 100vh;
  116. }
  117. .detail-title-wrap {
  118. padding: 0.16rem 0.17rem;
  119. }
  120. .detail-title {
  121. font-size: 0.18rem;
  122. font-weight: 600;
  123. color: #253440;
  124. line-height: 0.22rem;
  125. }
  126. .detail-date {
  127. font-size: 0.14rem;
  128. color: #8695a1;
  129. margin-top: 0.04rem;
  130. }
  131. .body-nav-btn {
  132. width: 0.46rem;
  133. height: 0.4rem;
  134. box-shadow: 0rem 0rem 0.13rem 0rem rgba(0, 60, 195, 0.25);
  135. border-radius: 0rem 1rem 1rem 0rem;
  136. background-color: #2c85ff;
  137. background: #2c85ff url('../../assets/Icon/icon_nav.png') 0.11rem center
  138. no-repeat;
  139. background-size: 0.2rem 0.2rem;
  140. position: fixed;
  141. left: 0;
  142. top: 0;
  143. z-index: 10;
  144. }
  145. .detail-body {
  146. display: flex;
  147. height: 85vh;
  148. }
  149. .body-side-bar {
  150. padding-top: 0.54rem;
  151. width: 0.92rem;
  152. overflow-y: scroll;
  153. background-color: #f7f9f9;
  154. }
  155. .body-content {
  156. padding: 0 0.1rem 0 0.13rem;
  157. flex: 1;
  158. overflow-y: scroll;
  159. }
  160. .copyright-notice {
  161. margin: 0.16rem 0;
  162. font-size: 0.12rem;
  163. color: #8695a1;
  164. line-height: 0.17rem;
  165. }
  166. </style>

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/76897?site
推荐阅读
相关标签
  

闽ICP备14008679号