六一的部落格


关关难过关关过,前路漫漫亦灿灿。




说明

  1. 设置高亮样式 css
  2. 使用Intersection Observer实现 JavaScript
  3. 文章目录设置

    config/_default/markup.toml
    -
    startLevel 2
    endLevel 3
  4. 另一个实现思路为Scrollspy
  5. 移植到Doks主题

便签

-
文章目录滚动高亮 1. 为文章目录设置类
2. 高亮样式
3. 提供一种实现
高亮样式 当前标题底部加横线
滚动高亮 设置元素的父元素
滚动高亮 option和intersectionObserver的callback
IntersectionObserver API 1. 添加观察元素: observe
2. intersectionRatio
3. target
querySelectorAll用法 1. 获取有指定属性的元素: “a[target]”
2. 获取指定父元素的元素: “div > p”
3. 获取多个分类元素: “h2[id], h3[id]”
获取元素的方式 1. id: getElementById, 上下文要求是documnet
2. name: getElementsByName, 一组
将NodeList转换为数组 apply
从数组中移除元素 filter
设置数组初始值 fill

文章目录

  • Doks主题通过设置startLevel和endLevel限制ToC显示的标题。endLevel上限为4,startLevel一般从2开始
  • 高亮的是ToC中的链接

高亮样式

  • 设置颜色
  • 加粗
  • 过渡
  • 暗色主题

assets/scss/common/_global.scss

1.my-toc a.active {
2    color: $primary;
3    font-weight: 800;
4    transition: all .25s ease-in-out
5}

assets/scss/common/_dark.scss

1[data-dark-mode] .my-toc a.active {
2    // color: $link-color-dark;
3    color: $zdoc-highlight-dark;
4}

为ToC设置类

layouts/partials/sidebar/docs-toc.html:23

1<nav class="my-toc">
2  {{ .TableOfContents }}
3</nav>

进入判断

1document.addEventListener('DOMContentLoaded', () => {
2  const toc = document.querySelector('.my-toc');
3  if (!toc) return;
4
5  // 后续处理  
6});

文章目录标题

获取有类my-toc的元素的链接

1const tocHeadings = toc.querySelectorAll('a');

正文标题

只关注显示在文章目录的标题, 筛选时, 要求标题的下级链接有类anchor

1const headings = Array.apply(null, document.querySelectorAll('h2[id], h3[id]')).filter(function (value, index, arr) {
2    return arr[index].querySelector('.anchor');
3});

将文章目录标题和正文标题关联

当二者标题数一致时, 认为一一对应, 设置标号; 否则就此结束

 1function addHeadingIdx(list) {
 2  let i = 0;
 3  list.forEach((item) => {
 4    item.setAttribute('headingIdx', i++);
 5  });
 6}
 7
 8if (tocHeadings.length !== headings.length) return;
 9
10addHeadingIdx(tocHeadings);
11addHeadingIdx(headings);

搭建交叉观察框架

  1. 触发条件:当标题的intersectionRatio变为threshold,或者不再为threshold时,对标题进行处理
    1const intersectionOptions = {
    2  threshold: 1.0
    3}
  2. 创建观察器
    1const headingObserver = new IntersectionObserver(headings => {
    2    headings.forEach(heading => {
    3        // 处理标题
    4    })
    5}, intersectionOptions); 
    
  3. 将正文标题添加到观察列表
    1headings.forEach((heading) => {
    2    headingObserver.observe(heading);
    3});

为标题添加/移除高亮

  1. 标题的intersectionRatio满足阈值,isIntersecting为true,intersectionRatio不满足阈值,为false
  2. 根据isIntersecting更新标题高亮状态
  3. 新增高亮标题, 或者有多个标题高亮时移除高亮: 根据高亮状态刷新, 更新高亮标题个数

添加全局数组,保存标题高亮状态,初始值为false

1let HeadingFlag;
2
3HeadingFlag = new Array(headings.length).fill(false);

添加变量,保存高亮标题个数, 初始值为0

1let HeadingCnt;
2
3HeadingCnt = 0;

进入观察器回调函数: 更新标题高亮状态; 新增高亮, 或者有多个标题高亮时移除高亮, 根据高亮状态设置标题,更新高亮个数

 1function refreshHighlight() {
 2    HeadingCnt = 0;
 3    for (let i = 0; i < HeadingFlag.length; ++i) {
 4        if (HeadingFlag[i]) {
 5            ++HeadingCnt;
 6            document.querySelector(`.my-toc a[headingIdx="${i}"]`).classList.add('active');
 7        }
 8        else
 9        {
10            document.querySelector(`.my-toc a[headingIdx="${i}"]`).classList.remove('active');
11        }
12    }
13}
14
15const idx = heading.target.getAttribute('headingIdx');
16if ((HeadingFlag[idx] = heading.isIntersecting) || (HeadingCnt !== 1)) {
17    refreshHighlight();
18}

完整JavaScript代码

assets/js/toc.js

 1let HeadingFlag;
 2let HeadingCnt;
 3
 4function addHeadingIdx(list) {
 5  let i = 0;
 6  list.forEach((item) => {
 7    item.setAttribute('headingIdx', i++);
 8  });
 9}
10
11function refreshHighlight() {
12  HeadingCnt = 0;
13  for (let i = 0; i < HeadingFlag.length; ++i) {
14    if (HeadingFlag[i]) {
15      ++HeadingCnt;
16      document.querySelector(`.my-toc a[headingIdx="${i}"]`).classList.add('active');
17    }
18    else
19    {
20      document.querySelector(`.my-toc a[headingIdx="${i}"]`).classList.remove('active');
21    }
22  }
23}
24
25document.addEventListener('DOMContentLoaded', () => {
26  const toc = document.querySelector('.my-toc');
27  if (!toc) return;
28
29  const tocHeadings = toc.querySelectorAll('a');
30  const headings = Array.apply(null, document.querySelectorAll('h2[id], h3[id]')).filter(function (value, index, arr) {
31    return arr[index].querySelector('.anchor');
32  });
33
34  if (tocHeadings.length !== headings.length) return;
35
36  addHeadingIdx(tocHeadings);
37  addHeadingIdx(headings);
38
39  HeadingFlag = new Array(headings.length).fill(false);
40  HeadingCnt = 0;
41
42  const intersectionOptions = {
43    threshold: 1.0
44  };
45
46  const headingObserver = new IntersectionObserver(headings => {
47    headings.forEach(heading => {
48      // console.log('ratio', heading.target.getAttribute('id'), heading.intersectionRatio, heading.isIntersecting, HeadingCnt);
49      const idx = heading.target.getAttribute('headingIdx');
50      if ((HeadingFlag[idx] = heading.isIntersecting) || (HeadingCnt !== 1)) {
51        refreshHighlight();
52      }
53    });
54  }, intersectionOptions); 
55
56  headings.forEach((heading) => {
57    headingObserver.observe(heading);
58  });
59});

Doks主题执行JavaScript脚本

参照assets/highlight.js的执行

layouts/partials/footer/script-footer.html

  1. 10
    1{{ $toc := resources.Get "js/toc.js" -}}
    2{{ $toc := $toc | js.Build -}}
  2. 86
    1<script src="{{ $toc.RelPermalink }}" defer></script>
  3. 103
    1{{ $toc := $toc | minify | fingerprint "sha512" -}}         
  4. 113
    1<script src="{{ $toc.RelPermalink }}" integrity="{{ $toc.Data.Integrity }}" crossorigin="anonymous" defer></script>

文章目录标题高亮



说明

  1. 设置高亮样式 css
  2. 使用Intersection Observer实现 JavaScript
  3. 文章目录设置

    config/_default/markup.toml
    -
    startLevel 2
    endLevel 3
  4. 另一个实现思路为Scrollspy
  5. 移植到Doks主题

便签

-
文章目录滚动高亮 1. 为文章目录设置类
2. 高亮样式
3. 提供一种实现
高亮样式 当前标题底部加横线
滚动高亮 设置元素的父元素
滚动高亮 option和intersectionObserver的callback
IntersectionObserver API 1. 添加观察元素: observe
2. intersectionRatio
3. target
querySelectorAll用法 1. 获取有指定属性的元素: “a[target]”
2. 获取指定父元素的元素: “div > p”
3. 获取多个分类元素: “h2[id], h3[id]”
获取元素的方式 1. id: getElementById, 上下文要求是documnet
2. name: getElementsByName, 一组
将NodeList转换为数组 apply
从数组中移除元素 filter
设置数组初始值 fill

文章目录

  • Doks主题通过设置startLevel和endLevel限制ToC显示的标题。endLevel上限为4,startLevel一般从2开始
  • 高亮的是ToC中的链接

高亮样式

  • 设置颜色
  • 加粗
  • 过渡
  • 暗色主题

assets/scss/common/_global.scss

1.my-toc a.active {
2    color: $primary;
3    font-weight: 800;
4    transition: all .25s ease-in-out
5}

assets/scss/common/_dark.scss

1[data-dark-mode] .my-toc a.active {
2    // color: $link-color-dark;
3    color: $zdoc-highlight-dark;
4}

为ToC设置类

layouts/partials/sidebar/docs-toc.html:23

1<nav class="my-toc">
2  {{ .TableOfContents }}
3</nav>

进入判断

1document.addEventListener('DOMContentLoaded', () => {
2  const toc = document.querySelector('.my-toc');
3  if (!toc) return;
4
5  // 后续处理  
6});

文章目录标题

获取有类my-toc的元素的链接

1const tocHeadings = toc.querySelectorAll('a');

正文标题

只关注显示在文章目录的标题, 筛选时, 要求标题的下级链接有类anchor

1const headings = Array.apply(null, document.querySelectorAll('h2[id], h3[id]')).filter(function (value, index, arr) {
2    return arr[index].querySelector('.anchor');
3});

将文章目录标题和正文标题关联

当二者标题数一致时, 认为一一对应, 设置标号; 否则就此结束

 1function addHeadingIdx(list) {
 2  let i = 0;
 3  list.forEach((item) => {
 4    item.setAttribute('headingIdx', i++);
 5  });
 6}
 7
 8if (tocHeadings.length !== headings.length) return;
 9
10addHeadingIdx(tocHeadings);
11addHeadingIdx(headings);

搭建交叉观察框架

  1. 触发条件:当标题的intersectionRatio变为threshold,或者不再为threshold时,对标题进行处理
    1const intersectionOptions = {
    2  threshold: 1.0
    3}
  2. 创建观察器
    1const headingObserver = new IntersectionObserver(headings => {
    2    headings.forEach(heading => {
    3        // 处理标题
    4    })
    5}, intersectionOptions); 
    
  3. 将正文标题添加到观察列表
    1headings.forEach((heading) => {
    2    headingObserver.observe(heading);
    3});

为标题添加/移除高亮

  1. 标题的intersectionRatio满足阈值,isIntersecting为true,intersectionRatio不满足阈值,为false
  2. 根据isIntersecting更新标题高亮状态
  3. 新增高亮标题, 或者有多个标题高亮时移除高亮: 根据高亮状态刷新, 更新高亮标题个数

添加全局数组,保存标题高亮状态,初始值为false

1let HeadingFlag;
2
3HeadingFlag = new Array(headings.length).fill(false);

添加变量,保存高亮标题个数, 初始值为0

1let HeadingCnt;
2
3HeadingCnt = 0;

进入观察器回调函数: 更新标题高亮状态; 新增高亮, 或者有多个标题高亮时移除高亮, 根据高亮状态设置标题,更新高亮个数

 1function refreshHighlight() {
 2    HeadingCnt = 0;
 3    for (let i = 0; i < HeadingFlag.length; ++i) {
 4        if (HeadingFlag[i]) {
 5            ++HeadingCnt;
 6            document.querySelector(`.my-toc a[headingIdx="${i}"]`).classList.add('active');
 7        }
 8        else
 9        {
10            document.querySelector(`.my-toc a[headingIdx="${i}"]`).classList.remove('active');
11        }
12    }
13}
14
15const idx = heading.target.getAttribute('headingIdx');
16if ((HeadingFlag[idx] = heading.isIntersecting) || (HeadingCnt !== 1)) {
17    refreshHighlight();
18}

完整JavaScript代码

assets/js/toc.js

 1let HeadingFlag;
 2let HeadingCnt;
 3
 4function addHeadingIdx(list) {
 5  let i = 0;
 6  list.forEach((item) => {
 7    item.setAttribute('headingIdx', i++);
 8  });
 9}
10
11function refreshHighlight() {
12  HeadingCnt = 0;
13  for (let i = 0; i < HeadingFlag.length; ++i) {
14    if (HeadingFlag[i]) {
15      ++HeadingCnt;
16      document.querySelector(`.my-toc a[headingIdx="${i}"]`).classList.add('active');
17    }
18    else
19    {
20      document.querySelector(`.my-toc a[headingIdx="${i}"]`).classList.remove('active');
21    }
22  }
23}
24
25document.addEventListener('DOMContentLoaded', () => {
26  const toc = document.querySelector('.my-toc');
27  if (!toc) return;
28
29  const tocHeadings = toc.querySelectorAll('a');
30  const headings = Array.apply(null, document.querySelectorAll('h2[id], h3[id]')).filter(function (value, index, arr) {
31    return arr[index].querySelector('.anchor');
32  });
33
34  if (tocHeadings.length !== headings.length) return;
35
36  addHeadingIdx(tocHeadings);
37  addHeadingIdx(headings);
38
39  HeadingFlag = new Array(headings.length).fill(false);
40  HeadingCnt = 0;
41
42  const intersectionOptions = {
43    threshold: 1.0
44  };
45
46  const headingObserver = new IntersectionObserver(headings => {
47    headings.forEach(heading => {
48      // console.log('ratio', heading.target.getAttribute('id'), heading.intersectionRatio, heading.isIntersecting, HeadingCnt);
49      const idx = heading.target.getAttribute('headingIdx');
50      if ((HeadingFlag[idx] = heading.isIntersecting) || (HeadingCnt !== 1)) {
51        refreshHighlight();
52      }
53    });
54  }, intersectionOptions); 
55
56  headings.forEach((heading) => {
57    headingObserver.observe(heading);
58  });
59});

Doks主题执行JavaScript脚本

参照assets/highlight.js的执行

layouts/partials/footer/script-footer.html

  1. 10
    1{{ $toc := resources.Get "js/toc.js" -}}
    2{{ $toc := $toc | js.Build -}}
  2. 86
    1<script src="{{ $toc.RelPermalink }}" defer></script>
  3. 103
    1{{ $toc := $toc | minify | fingerprint "sha512" -}}         
  4. 113
    1<script src="{{ $toc.RelPermalink }}" integrity="{{ $toc.Data.Integrity }}" crossorigin="anonymous" defer></script>