当前位置: 首页 > news >正文

vue实现根据点击或滑动展示对应高亮

页面需求:

  • 点击左侧版本号,右侧展示对应版本内容并置于顶部
  • 右侧某一内容滚动到顶部时,左侧需要展示高亮

实现效果:

请添加图片描述

实现代码:

<template>
  <div>
    <div class="historyBox pd-20 bg-white">
      <div class="w100 flex h100" v-if="versionList.length > 0">
        <div class="left size-14">
          <div
            v-for="(item, index) in versionList"
            :key="index"
            class="leftItem pd-10 pointer"
            :class="index == activeIndex ? 'isActive' : ''"
            @click="gotoTarget(index)"
          >
            <div>{{ item.versionNumber }}</div>
            <div>{{ item.releaseTime }}</div>
          </div>
        </div>
        <div class="right">
          <div
            v-for="(item, index) in versionList"
            :key="index"
            class="rightItem pd-20 center"
            :class="index == activeIndex ? 'isActive' : ''"
          >
            <div v-html="item.versionDescription" class="ql-editor w60"></div>
          </div>
        </div>
      </div>
      <div class="w100 h100 center size-16 gray-2" v-else>暂无版本记录</div>
    </div>
  </div>
</template>

<script>
import { listAllVersion } from "./components/api";

export default {
  name: "VersionHistory",
  data() {
    return {
      versionList: [], // 表格数据
      activeIndex: 0, // 高亮下标
      clickIndex: 0, // 点击下标
      scrollIndex: 0, // 滚动下标
      scrollStopTimer: null,
    };
  },
  created() {
    this.getList();
  },
  mounted() {},
  methods: {
    // 监听鼠标移入元素 、右侧父元素滚动
    checkItemsHover() {
      const rightBox = document.querySelector(".right");

      // 监听父容器的滚动事件
      rightBox.addEventListener("scroll", this.checkItemsAtTop);

      // 页面加载时也检查一次
      window.addEventListener("load", this.checkItemsAtTop);
    },
    // 监听元素 rightItem 触顶
    checkItemsAtTop() {
      const rightBox = document.querySelector(".right");
      const rightItems = Array.from(rightBox.querySelectorAll(".rightItem"));
      rightItems.forEach((item, index) => {
        // 使用 getBoundingClientRect 来获取元素相对于视口的位置
        const rect = item.getBoundingClientRect();
        const containerRect = rightBox.getBoundingClientRect();

        // 判断元素是否触顶父容器
        if (
          rect.top - containerRect.top <= 0 &&
          rect.bottom - containerRect.top >= 0
        ) {
          this.scrollIndex = index;
          // this.activeIndex = index;
        }

        // 清除之前的定时器,防止重复触发
        if (this.scrollStopTimer) {
          clearTimeout(this.scrollStopTimer);
        }
        // 设置新的定时器,在滚动停止后延迟指定时间触发
        this.scrollStopTimer = setTimeout(this.onScrollStopped, 150); // 150ms 的延迟可以根据需要调整
      });
    },
    // 滚动停止后的回调函数
    onScrollStopped() {
      // console.log("滚动停止了", this.scrollIndex, this.clickIndex);
      // 在这里放置你希望在滚动停止后执行的代码
      if (this.scrollIndex < this.clickIndex) {
        this.activeIndex = this.clickIndex;
        this.clickIndex = 0;
      } else {
        this.activeIndex = this.scrollIndex;
      }
      const leftItems = document.querySelectorAll(".leftItem");
      const leftBox = document.querySelector(".left");
      const targetLeftItem = leftItems[this.activeIndex];
      // 计算目标元素距离父容器顶部的距离
      const offsetTop = targetLeftItem.offsetTop - leftBox.offsetTop;

      // 使用 scrollTo 方法让父容器滚动到目标元素的位置
      leftBox.scrollTo({
        top: offsetTop,
        behavior: "smooth", // 如果需要平滑滚动,请确保父容器设置了 scroll-behavior: smooth;
      });
    },
    // 定义当鼠标移入时触发的函数
    gotoTarget(index) {
      // this.scrollIndex = index;
      this.clickIndex = index;
      this.activeIndex = index;

      const rightItems = document.querySelectorAll(".rightItem");
      const rightBox = document.querySelector(".right");
      const targetRightItem = rightItems[index];
      /* // 使用 scrollIntoView 方法让目标元素滚动到视图顶部
      targetRightItem.scrollIntoView({ behavior: "smooth", block: "start" }); */

      // 计算目标元素距离父容器顶部的距离
      const offsetTop = targetRightItem.offsetTop - rightBox.offsetTop + 1;

      // 使用 scrollTo 方法让父容器滚动到目标元素的位置
      rightBox.scrollTo({
        top: offsetTop,
        behavior: "smooth", // 如果需要平滑滚动,请确保父容器设置了 scroll-behavior: smooth;
      });
    },
    /** 查询列表 */
    getList() {
      listAllVersion().then((response) => {
        this.versionList = response.data;
        // .concat(response.data)
        // .concat(response.data)
        // .concat(response.data)
        // .concat(response.data);
        this.$nextTick(() => {
          this.checkItemsHover();
        });
      });
    },
  },
};
</script>
<style lang="scss" scoped>
@import "./components/quill.snow.css";
.historyBox {
  height: calc(100vh - 90px);
  ::-webkit-scrollbar {
    width: 6px;
    // height: 24px;
  }

  /* 滚动槽的样式设置 */
  ::-webkit-scrollbar-track {
    background: #eee;
  }

  /* 滚动条滑块的样式设置 */
  ::-webkit-scrollbar-thumb {
    background: rgba(0, 0, 0, 0.1);
    background: rgb(158, 203, 255);
    border-radius: 12px;

    &:hover {
      background: rgb(95, 169, 253);
    }
  }
  .left {
    width: 260px;
    min-width: 260px;
    height: 100%;
    // height: calc(100vh - 117px);
    overflow-y: auto;

    .leftItem {
      display: flex;
      justify-content: space-evenly;
      align-items: center;
      border: 1px solid #eee;
    }
    .isActive {
      border: 1px solid #3f8cff;
      border-left: 4px solid #3f8cff;
      color: #3f8cff;
      background: rgba(63, 140, 255, 0.1);
      font-weight: bold;
    }
  }
  .right {
    box-sizing: border-box;
    width: calc(100% - 260px);
    height: 100%;
    // height: calc(100vh - 117px);
    overflow-y: auto;
    background: #eef6ff;

    .rightItem {
      border: 1px dotted #eef6ff;
      &:hover {
        border: 1px dotted #ddd;
      }
      .w60 {
        width: 60%;
      }
    }
    .isActive {
      // border: 1px dotted #ddd;
      border: 1px dotted #3f8cff;
      // box-shadow: 0px 0px 20px #3f8cff;
      // box-shadow: 0px 5.04px 10.08px rgba(55, 114, 233, 0.22),
      //   inset 0px 5.04px 10.08px rgba(211, 221, 242, 1);
    }
  }
}
</style>

相关文章:

  • 【Rust中级教程】2.10. API设计原则之受约束性(constrained) Pt.1:对类型进行修改、`#[non_exhaustive]`注解
  • QT中的事件
  • 基于Java+SpringBoot+Vue的前后端分离的租房网站
  • Shell基础
  • 2011-2019年各省人口数数据
  • vue3动态引入图片
  • 前端依赖nrm镜像管理工具
  • 软考程序员考试内容和备考策略
  • 补充:文件上传、下载传输给前端之直接传递图片二进制数据:网络中的图片、音频、视频等非字符数据的传输
  • 港科大提出开放全曲音乐生成基础模型YuE:可将歌词转换成完整歌曲
  • 每日Attention学习24——Strip Convolution Block
  • 嵌入式开发工程师笔试面试指南-Linux系统移植
  • 计算机组成与接口10
  • 深入探索C语言中的sscanf和sprintf函数
  • 【C++笔记】C++11智能指针的使用及其原理
  • 2025年SCI1区TOP:真菌生长优化算法FGO,深度解析+性能实测
  • java23种设计模式-观察者模式
  • Spring MVC框架六:Ajax技术
  • 用 DeepSeek 打样!KubeSphere LuBan 用 3 天/3 分钟“干掉”大模型部署焦虑
  • 第五六七章
  • 学有质量、查有力度、改有成效,广大党员干部落实中央八项规定精神
  • 4月人民币对美元即期汇率微跌,今年以来升值0.48%
  • “80后”蒋美华任辽宁阜新市副市长
  • 年轻人的事业!6家上海人工智能企业畅想“模范生”新征程
  • 浙商银行外部监事高强无法履职:已被查,曾任建行浙江省分行行长
  • “五一”假期,又有多地将向社会开放政府机关食堂