示例

在这里插入图片描述

功能描述

  • 点击左侧导航栏,右侧能滚动到指定的位置
  • 右侧滚动,左侧能自动选中

存在问题

  • 多次监听
  • 直接绑定滚动到了body

优化版本链接

使用

这儿是结合element-ui 一起使用的,同学们使用的时候要注意引入对应组件

<template>
  <div class="container pr flex-wrap">
    <el-menu
      :default-active="state.defaultActive"
      class="el-menu-vertical-demo width-120"
    >
      <el-menu-item
        :index="item.id"
        v-for="item in state.menuList"
        :key="item.id"
        @click="changenavigationBar(item.id)"
      >
        <span>{{ item.title }}</span>
      </el-menu-item>
    </el-menu>
    <div class="content">
      <h2>我是右侧的顶部内容</h2>

      <!-- 必传项为 id id必须和el-menu-item的index一致,activeId函数用于接收滚动到那个id区域了-->
      <kl-anchor
        class="content-item"
        :id="item.id"
        v-for="item in state.menuList"
        :key="item.id"
        @activeId="activeId"
      >
        {{ item.id }}
      </kl-anchor>
    </div>
  </div>
</template>
<script lang="ts" setup>
import { reactive } from "vue-demi";

const state = reactive({
  defaultActive: "nav1",
  menuList: [
    {
      id: "nav1",
      title: "nav11",
    },
    {
      id: "nav2",
      title: "nav22",
    },
    {
      id: "nav3",
      title: "nav33",
    },
    {
      id: "nav4",
      title: "nav44",
    },
    {
      id: "nav5",
      title: "nav55",
    },
    {
      id: "nav6",
      title: "nav66",
    },
  ],
});

const activeId = (id: string) => {
  if (id !== state.defaultActive) {
    state.defaultActive = id;
  }
  // console.log(id);
};

// 平滑滚动方式
const changenavigationBar = (id: String) => {
  var PageId = document.querySelector("#" + id) as HTMLElement;

  window.scrollTo({
    top: PageId.offsetTop,
    behavior: "smooth",
  });
};
</script>
<style lang="scss" scoped>
.width-120 {
  width: 120px;
}

.content-item {
  height: 500px;
}

.el-menu-vertical-demo {
  position: fixed;
  top: 0;
  left: 0;
}

.container {
  padding-left: 120px;
}
</style>

实现

kl-anchor

<template>
  <div class="kl-anchor" :id="id">
    <slot> </slot>
  </div>
</template>
<script lang="ts" setup>
import {
  onBeforeUnmount,
  onMounted,
  reactive,
  onBeforeUpdate,
  nextTick,
} from "vue-demi";

const props = defineProps({
  // 这儿适用于监听滚动的id 一定要注意唯一性
  id: {
    type: String,
    required: true,
  },
  // 精度 建议实际每项高度越小,这儿也传入越小
  accuracy: {
    type: Number,
    default: 150,
  },
});

const emits = defineEmits(["activeId"]);

interface i_state {
  el: HTMLElement | null;
  offsetTop: number;
  clientHeight: number;
  myHeight: number;
}

const state: i_state = reactive({
  el: null, // 节点对象
  offsetTop: 0, // 当前节点到body的顶部的距离
  clientHeight: 0, // 页面可视区高度
  myHeight: 0, // 当前节点的高度
});

function throttle(fn: () => void, delay: number) {
  let time1 = 0;
  return function () {
    let time2 = Date.now();
    if (time2 - time1 >= delay) {
      fn();
      time1 = time2;
    }
  };
}

const eventScroll = throttle(() => {
  let bodyScroll: number = document.documentElement.scrollTop;

  if (
    bodyScroll - state.offsetTop >= 0 &&
    state.offsetTop + state.myHeight > bodyScroll
  ) {
    // console.log(props.id);
    emits("activeId", props.id);
  }
}, props.accuracy);

function handleEvent() {
  state.el = document.querySelector("#" + props.id) as HTMLElement;
  state.myHeight = state.el.offsetHeight; //如果是异步获取,将无法获取到没有加载进来的内容的高度
  state.offsetTop = state.el.offsetTop; //如果是异步获取,将无法获取到没有加载进来的内容的高度
  state.clientHeight = document.body.clientHeight;
  // 监听事件 当前是处于哪个nav对应的内容
  window.document.addEventListener("scroll", eventScroll);
}
onMounted(() => {
  handleEvent();
});

// 当数据更新时的业务
onBeforeUpdate(() => {
  nextTick(() => {
    handleEvent();
  });
});

onBeforeUnmount(() => {
  window.document.removeEventListener("scroll", eventScroll);
});
</script>
<style lang="scss" scoped></style>
Logo

前往低代码交流专区

更多推荐