js生成目录并实现目录节点跟随滚动高亮

项目场景:

当网页有文章,文章中有很多标题。我们有时会需要生成目录大纲,以便他人查阅。生成目录到是不难,但是怎么实现目录跟随着页面滚动而改变目录的高亮标题?


获取所有标题,并生成大纲

// 获取所有标题
let tocList = document.querySelectorAll("h1, h2, h3, h4, h5, h6");
let HList = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'];

let str = `<div class="dir"><ul id="toc">`;
// 生成目录(你也可以更具自己需要生成一个对象也可以,我这里生成 html字符串)
Array.from(tocList,v => {
	const H = HList.indexOf(v.nodeName) + 1 || 1;  // 标题等级 1,2,3,4,5,6
    str += `<li class="li li-${H}">
    			<a href="javascript:void(0);" id="${v.id}" >${v.textContent}</a>
    		</li>\n`;
})
str += `</ul>`;
str += `<div class="sider"><span class="siderbar"><span></div></div>`;

// 添加 html 字符串到 页面
let toc = document.querySelector(".toc");
toc.insertAdjacentHTML("beforeend", str);

生成节点大概如下:(如果是vue或react可以生成对象,在页面渲染成大概这样子就行)

在这里插入图片描述

给每个目录节点绑定点击事件

为什么不用瞄点定位?a 标签 #id 确实可以定位到该标题,但这个我个人觉得有点不好用,如果顶部有导航栏,瞄点定位的标题会被顶部导航栏遮挡住,为了不被遮挡,可以给每个标题都添加一个 padding-top 或 margin-top 隔开顶部导航栏,但我不想那么做。

Array.from(tocList,v => {
   const btn = document.querySelector(`#toc #${v.id}`);
   const ele = document.querySelector(`.container #${v.id}`);
   if (!btn || !ele) return;
  
	btn.addEventListener("click", () => {
      window.scrollTo({ top: ele.offsetTop - 80, behavior: "smooth" });
    })
})

监听滚动时目录高亮节点

这里不适用 scolll 来监听滚动,我这里用 observe 来监听

const visibleChnage = (obs) => {
  var sider = document.querySelector(".siderbar");
  var toc = document.querySelectorAll("#toc .li a");
  item.forEach(observe => {
    // 找到对应的节点
    const id = observe.target.getAttribute('id'), anchor = document.querySelector(`#toc .li #${id}`);

    if (!anchor) return false;
    
    // 如果节点出现在可视视窗
    if (observe.isIntersecting) {
      // 排他(这个为了清除所有a标签中的类名 li-active)
      removeClass(); 
      // 目录 a 标签的类名为 li-active 时高亮
      anchor.classList.add("li-active");
       const index = Array.from(toc, v => v.getAttribute('id')).indexOf(id);
      // 左边高亮目录条
      sider.style.transform = `translateY(${index * 30}px)`;
    }
  });
}


 const observer = new IntersectionObserver(visibleChnage);
 // 监听滚动
 Array.from(tocList).map(item => observer.observe(item));

排他: removeClass()

// 移除所有的 li-active 排他
const removeClass = () => {
  const list = document.querySelectorAll("#toc .li a");
  Array.from(list,v => v.classList.remove("li-active"));
}

最终效果

在这里插入图片描述

线上demo

Gitee地址

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要在 Canvas 中实现拖拽生成新的节点,可以通过监听鼠标事件来实现。 具体实现步骤如下: 1. 监听 `mousedown` 事件,记录鼠标点击的位置。 ```javascript var isDragging = false; // 是否正在拖拽 var startX, startY; // 鼠标点击的起始位置 canvas.addEventListener('mousedown', function(e) { startX = e.clientX; startY = e.clientY; isDragging = true; }); ``` 2. 监听 `mousemove` 事件,当正在拖拽,绘制新的节点。 ```javascript canvas.addEventListener('mousemove', function(e) { if (isDragging) { var x = e.clientX; var y = e.clientY; var width = x - startX; // 新节点的宽度 var height = y - startY; // 新节点的高度 // 绘制新节点 ctx.beginPath(); ctx.rect(startX, startY, width, height); ctx.stroke(); } }); ``` 3. 监听 `mouseup` 事件,完成拖拽并生成节点。 ```javascript canvas.addEventListener('mouseup', function(e) { if (isDragging) { isDragging = false; var x = e.clientX; var y = e.clientY; var width = x - startX; // 新节点的宽度 var height = y - startY; // 新节点的高度 // 绘制新节点 ctx.beginPath(); ctx.rect(startX, startY, width, height); ctx.stroke(); } }); ``` 完整的实现代码如下: ```javascript var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d'); var isDragging = false; // 是否正在拖拽 var startX, startY; // 鼠标点击的起始位置 canvas.addEventListener('mousedown', function(e) { startX = e.clientX; startY = e.clientY; isDragging = true; }); canvas.addEventListener('mousemove', function(e) { if (isDragging) { var x = e.clientX; var y = e.clientY; var width = x - startX; // 新节点的宽度 var height = y - startY; // 新节点的高度 // 绘制新节点 ctx.beginPath(); ctx.rect(startX, startY, width, height); ctx.stroke(); } }); canvas.addEventListener('mouseup', function(e) { if (isDragging) { isDragging = false; var x = e.clientX; var y = e.clientY; var width = x - startX; // 新节点的宽度 var height = y - startY; // 新节点的高度 // 绘制新节点 ctx.beginPath(); ctx.rect(startX, startY, width, height); ctx.stroke(); } }); ``` 在这个基础上,可以通过添加节点属性、拖拽节点等操作,实现更复杂的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值