vue3 双向锚点需求scrollIntoView解决方案

318 阅读3分钟

1.目前ui组件库所提供的锚点组件并不能直接用于我们的项目之中,主要因为传统的href跳转会改变url,以至于让我们页面重新刷新,所以比较靠谱的方案是利用scrollIntoView方案去解决 2.具体思路是将该锚点组件封装成一个独立的子组件让其他的组件可以复用,在父组件传区间的高度下去,别忘记还要往该数组push一个值,不然最后一个索引没法高亮,其次还要把当前的title名字,还有每一区域的class名字传给子组件,具体请看下面的代码(父组件的全部代码)

<template>
  <div>
    <ul>
      <li class="one">
        <p>第一章</p>
        <div>{{ content }}</div>
      </li>
      <li class="two">
        <p>第二章</p>
        <div>{{ content }}</div>
      </li>
      <li class="three">
        <p>第三章</p>
        <div>{{ content }}</div>
      </li>
      <li class="four">
        <p>第四章</p>
        <div>{{ content }}</div>
      </li>
      <li class="five">
        <p>第五章</p>
        <div>{{ content }}</div>
      </li>
    </ul>
    <ElevatorArea
      :allHeightSection="allHeightSection"
      :positionList="positionList"
      class="ElevatorArea_sty"
    />
  </div>
</template>

<script setup>
import { ref, computed, watch, onMounted, reactive } from "vue";
import { useRouter } from "vue-router";
import { useStore } from "vuex";
import ElevatorArea from "@/components/ElevatorArea.vue";

const content = ref("犯得上发生范德萨发达");
const allHeightSection = reactive({ list: [] });
const positionList = [
  {
    className: "one",
    txt: "第一章",
  },
  {
    className: "two",
    txt: "第二章",
  },
  {
    className: "three",
    txt: "第三章",
  },
  {
    className: "four",
    txt: "第四章",
  },
  {
    className: "five",
    txt: "第五章",
  },
];
onMounted(() => {
  const li = document.querySelectorAll("li");
  // console.log(li, "li");
  setTimeout(() => {
    li.forEach((v) => {
      v.style.height = "1000px";
    });
    // 同步
    positionList.forEach((v) => {
      // 求出这组值
      const targetDom = document.querySelector("." + v.className);
      // debugger
      const itemHeight = targetDom.getBoundingClientRect().top;
      allHeightSection.list.push(itemHeight);
    });
    allHeightSection.list.push(5000);
    console.log(allHeightSection, "allHeightSection");
  }, 1000);
});
</script>

<style lang="scss" scoped>
li {
  height: 700px;
}
.ElevatorArea_sty {
  position: fixed;
  right: 0;
  top: 25%;
  z-index: 100;
}
</style>

2.子组件的代码如下(ElevatorArea.vue)

<template>
  <!-- 电梯导航组件 -->
  <div class="containerml">
    <div v-if="currenStatus">
      <div class="top_button_area">
        <a-button
          @click="changeCurrenStatus"
          shape="circle"
          :icon="h(MenuUnfoldOutlined)"
        />
      </div>
      <ul class="list" v-if="currenStatus">
        <li
          :class="{ active: index === currentIndex }"
          @click="jumpToArea(item.className, index)"
          v-for="(item, index) in positionList"
          :key="index"
        >
          {{ item.txt }}
        </li>
      </ul>
    </div>
    <div v-else class="close_sty">
      <a-button
        @click="changeCurrenStatus"
        shape="circle"
        :icon="h(MenuUnfoldOutlined)"
      />
    </div>
  </div>
</template>

<script setup>
import { MenuUnfoldOutlined } from "@ant-design/icons-vue";
import { h, onMounted, ref } from "vue";
const currentIndex = ref(0);
const currenStatus = ref(true);
const props = defineProps(["positionList", "allHeightSection"]);
const jumpToArea = (value, index) => {
  // 目标的类名
  if (value) {
    //  debugger
    const target = document.querySelector("." + value);
    target.scrollIntoView();
  }
  currentIndex.value = index;
  // debugger;
};
// 当前的状态
const changeCurrenStatus = () => {
  const targetDom = window.document.querySelector(".containerml");

  if (currenStatus.value === false) {
    targetDom.style.width = 126 + "px";
    currenStatus.value = true;
  } else {
    targetDom.style.width = 20 + "px";
    currenStatus.value = false;
  }
};

onMounted(() => {
  window.document.addEventListener(
    "scroll",
    function (e) {
      var scrollTop = document.documentElement.scrollTop;
      //  console.log(document.documentElement.scrollTop);
      currentIndex.value = findFirstNumberIndex(
        props.allHeightSection.list,
        scrollTop
      );
      console.log(currentIndex.value, "currentIndex.value");
    },
    true
  );
});

function findFirstNumberIndex(arr, target) {
  let left = 0,
    right = arr.length - 1;
  let result = null;

  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    if (arr[mid] === target) {
      // 找到目标值,但需要检查其前后是否有相同的值来确定是否是一个区间
      if (mid > 0 && arr[mid - 1] === target) {
        // 找到区间,但可能不止一个,所以继续查找下一个区间
        result = mid;
        left = mid + 1; // 跳过已经找到的区间
      } else if (mid < arr.length - 1 && arr[mid + 1] === target) {
        // 如果目标值是区间的最后一个值,返回区间第一个值的索引
        return mid;
      } else {
        return mid; // 返回第一个匹配的索引
      }
    } else if (arr[mid] < target && arr[mid] >= arr[mid + 1]) {
      // 如果目标值不大于区间的第二个值,返回区间第一个值的索引
      return mid;
    } else if (arr[mid] < target && arr[mid] < arr[mid + 1]) {
      left = mid + 1; // 在右半部分继续查找
    } else {
      right = mid - 1; // 在左半部分继续查找
    }
  }
  // 如果没找到目标值或满足条件的区间,返回当前索引(即最后一次迭代的索引)
  return left > 0 ? left - 1 : left;
}
</script>

<style lang="less" scoped>
.containerml {
  // max-height:200.5px;
  // 最后一个数字为1就是不透明
  background: rgba(232, 226, 255, 0.8);
  border: 1px solid;
  border-color: #ffffff;
  border-radius: 12px;
  max-height: 480px;
  // padding: 7px ;
  box-sizing: border-box;
  transition: width 0.8s;
  width: 126px;
  opacity: 1;
  &.open {
    width: 126px;
  }
  &.close {
    width: 26px;
  }
  .top_button_area {
    height: 40px;
    border-bottom: 1px solid;
    border-color: #cfc8eb;
    display: flex;
    justify-content: center;
    align-items: center;
    // align-items: center;
  }
  .list {
    // min-height: 100px;
    // padding: 17px 0;
    box-sizing: border-box;
    display: flex;
    max-height: 300px;
    flex-direction: column;
    //  align-items: center;
    // margin-left: 8px;
    width: 100%;
    justify-content: space-between;
    li {
      padding-left: 8px;
      line-height: 20px;
      font-size: 12px;
      //  color: #6640ff;
      // font-size:14px;
      // line-height: 36px;
      // 这里添加动态样式
      width: 100%;
      //   height: 34px;
      margin-bottom: 10px;
      margin-top: 10px;
      cursor: default;
      &.active {
        border-left: 3px solid #6640ff;
        border-radius: 0px 4px 4px 0px;
        color: #6640ff;
      }
    }
  }
  .close_sty {
    //  background: ;
  }
}
</style>

3.按钮使用了ant-design-vue组件,基本就是这些了,大家项目中遇到可以直接cv使用