内容与目录(标签)关联互动方案

112 篇文章 0 订阅
31 篇文章 0 订阅

1、先上代码:

<template>
  <div>
    <h1>页面内容滚动与右侧导航标签互动关联 效果演示 demo</h1>
    <div class="content" id="content">
      <div class="left">
        <section v-for="(item, i) in list" class="text-node" :key="i" :id="'p' + i">
          <div class="title">
            <div class="title-pre"></div>
            <div v-html="item.title"></div>
          </div>
          <div class="text">
            <div v-html="item.text"></div>
          </div>
        </section>
      </div>
      <div class="right">
        <div class="entry-message">
          <div class="title">内容导航</div>
          <div class="content-anchor">
            <div>
              <el-timeline>
                <el-timeline-item
                  v-for="(item, index) in activities"
                  :key="index"
                  :color="active === index ? '#2b6afb' : ''"
                >
                  <div
                    @click="navToPosition(item, index)"
                    :style="{ color: active === index ? '#2b6afb' : '' }"
                  >
                    <span class="cn-pointer" v-html="item.title"></span>
                  </div>
                </el-timeline-item>
              </el-timeline>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
// 示例样本内容,提前声明赋值,方便后面复用
const text =
  "1、能源资源\n" +
  "能源资源包括煤、石油、天然气、水能等,也包括太阳能、风能、生物质能、地热能、海洋能、核能等新能源。纵观社会发展史,人类经历了柴草能源时期、" +
  "煤炭能源时期和石油、天然气能源时期,正向新能源时期过渡,并且无数学者仍在不懈地为社会进步寻找开发更新更安全的能源。但是," +
  "人们能利用的能源仍以煤炭、石油、天然气为主,在世界一次能源消费结构中,这三者的总和约占93%。\n" +
  "在一定历史时期和科学技术水平下,已被人们广泛应用的能源称为常规能源。那些虽古老但需采用新的先进的科学技术才能加以广泛应用的能源称为新能源。" +
  "凡在自然界中可以不断再生并有规律地得到补充的能源,称为可再生能源。经过亿万年形成的,在短期内无法恢复的能源称为非可再生能源。"


export default {
  name: "ScrollIntoView",
  data() {
    return {
      list: [
        {title: "能源开发与利用1", text: text},
        {title: "能源开发与利用2", text: text},
        {title: "能源开发与利用3", text: text},
        {title: "能源开发与利用4", text: text},
        {title: "能源开发与利用5", text: text},
        {title: "能源开发与利用6", text: text},
      ],
      active: 0,
      timeout: null,
      activities: [],
    };
  },
  mounted() {
    console.dir(document.getElementById("p1").getBoundingClientRect())
    this.init()
  },
  methods: {
    init() {
      const that = this
      // 从内容数据list中,获取段落标题作为导航标题。并为导航节点增加href,以段落的id值作为href的值
      this.list.map((item, i) => {
        this.activities.push({title: item.title, act: false, href: "#p" + i});
      });

      // 监听滚动条
      window.addEventListener("scroll", function (e) {
        // 防抖动处理
        clearTimeout(that.timeout)
        this.timeout = setTimeout(() => {
          that.activeNavNode(e)
        }, 100)
      });
    },
    // dom中定位导航
    navToPosition(item, index) {
      // 激活相应的导航节点,变色
      this.active = index;
      // 根据导航节点的href信息即id信息,获取对应的元素节点,通过 scrollIntoView 滚动该元素到可视区顶部
      document.querySelector(item.href).scrollIntoView(true);
    },
    // 激活左侧对应的导航条
    activeNavNode(e) {
      const nodes = document.getElementsByTagName("section")
      for (let i = 0; i < nodes.length; i++) {
        let node = nodes[i];
        // 获取该元素此时相对于视口的顶部的距离,即是元素顶部距离视口屏幕上边的距离
        let nodeY = node.getBoundingClientRect().y
        // 当元素距离视口顶部的距离在 [0,200] 之间,设置激活该元素对应左侧的导航标题,
        // 这个数字可以按需定义
        // 这里关联内容和导航标签,是巧妙利用了内容在元素集合中的索引序号和导航标签中的一致
        // 即是 list 和 activities 和 nodes 中下标相等的元素,具有对应关联的关系
        if (nodeY <= 200 && nodeY >= 0) {
          this.active = Number(i)
          return
        }
      }
    },
  },
};
</script>
<style lang="scss" scoped>
.cn-pointer {
  cursor: pointer;
}

.content {
  display: flex;
  padding: 20px 0 100px 0;
  width: 1100px;
  min-width: 1100px;
  max-width: 1100px;
  align-content: center;
  margin: 0 auto;

  .left {
    flex-grow: 1;
    margin: 0 20px 500px 0;
    padding: 0 40px 40px;
    background: #ffffff;
    box-shadow: 0 2px 20px -6px rgba(30, 135, 240, 0.1);
    border-radius: 4px;
  }


  section {
    padding: 30px 0;

    .title {
      height: 25px;
      display: flex;
      align-items: center;
      margin-bottom: 20px;
      font-size: 18px;
      color: #262626;
      font-weight: bold;
      margin-top: 20px;

      .title-pre {
        width: 9px;
        height: 18px;
        margin-right: 10px;
        background: #1e87f0;
      }
    }

    .text {
      font-size: 14px;
      color: #262626;
      line-height: 44px;
      font-weight: 400;
      text-align: left;

      span {
        display: block;
        margin-bottom: 20px;
      }
    }
  }

  .right {
    min-width: 300px
  }

  .entry-message {
    box-sizing: content-box;
    padding: 26px 30px;
    background: #ffffff;
    box-shadow: 0 2px 20px -6px rgba(30, 135, 240, 0.1);
    border-radius: 4px;
    position: fixed;
    top: 105px;

    .title {
      margin-bottom: 24px;
      font-size: 18px;
      color: #262626;
      font-weight: 600;
    }

    .content-anchor {
      display: flex;
      flex-direction: row;
      align-items: center;
    }
  }
}
</style>

效果:
在这里插入图片描述

需要储备的知识点1:

技术点1:Element.scrollIntoView()。

Element 接口的scrollIntoView()方法会滚动元素的父容器,使被调用scrollIntoView()的元素对用户可见。

> 语法
> 
element.scrollIntoView(); // 等同于element.scrollIntoView(true)
element.scrollIntoView(alignToTop); // Boolean型参数
element.scrollIntoView(scrollIntoViewOptions); // Object型参数

技术点2:Element.getBoundingClientRect()
Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。返回值是一个 DOMRect 对象,这个对象是由该元素的 getClientRects() 方法返回的一组矩形的集合,就是该元素的 CSS 边框大小。返回的结果是包含完整元素的最小矩形,并且拥有left, top, right, bottom, x, y, width, 和 height这几个以像素为单位的只读属性用于描述整个边框。除了width 和 height 以外的属性是相对于视图窗口的左上角来计算的。
getBoundingClientRectmdn地址

实现难点就是内容变动了,右侧的目录如何激活?

  1. 获取该元素此时相对于视口的顶部的距离,即是元素顶部距离视口屏幕上边的距离。
  2. 巧妙利用了内容在元素集合中的索引序号导航标签中的一致。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值