手摸手带你们写一个锚点高亮点代码

手摸手带你们写一个锚点高亮点代码

原由

前几天公司要求写一个锚点自动高亮功能,页面滚动到哪里锚点高亮到哪里。javascript

因此这里简单记录一下,由于遇到了自动高亮断定的问题,因此发在这里,若是有更好的方法,但愿能教教我~~css

效果图

点击这里看运行效果

分析一下实现步骤

这是大概的dom结构html

<html> 
  <body>
    <ul>
      <li><a href="#a1">a1</a></li>
      <li><a href="#a2">a2</a></li>
      <li><a href="#a3">a3</a></li>
      <li><a href="#a4">a4</a></li>
      <li><a href="#a5">a5</a></li>
    </ul>
    <div id="container">
      <div id="a1"></div>
      <div id="a2"></div>
      <div id="a3"></div>
      <div id="a4"></div>
      <div id="a5"></div>
    </div>
  </body>
</html>
复制代码

须要处理的元素

  • 页面中须要处理的有 a 标签 即图中 anchor
  • 锚点的位置即各类带 id 的元素,如图中 a1 elementa2 element
  • 包裹这些锚点位置的容器,若是是整个页面那就是 document ,或者就是自定义的滚动容器,这里统一叫container

如何手动高亮 a 标签

高亮效果,这里就是简单的给对应的 a 标签添加一个class便可java

// 获取a全部元素
function getAllAnchorElement(container) {
    const target = container ? container : document
    return target.querySelectorAll('a')
}

// 对应id的添加高亮类名,非对应id移除以前添加的高亮类名
function highLightAnchor(id){
  getAllAnchorElement().forEach(element => {
    element.classList.remove('highLight')
    if (element.hash.slice(1) == id) {
      element.classList.add('highLight')
    }
  });
}

复制代码

如何自动高亮 a 标签

原理很简单,就是监听容器元素的滚动,在对应条件下去自动高亮a标签git

// 这里注意须要用 thorttle 处理一下handleScroll 函数,否则触发次数太多可能卡页面哦 🤪🤪
const throttleFn = throttle(handleScroll, 100)

// 若是你没有阻止滚动事件,能够加上 passive: true 能够提升滚动性能
ScrollContrainer.addEventListener('scroll', throttleFn, {passive: true})
复制代码

可是这个对应条件有点麻烦,我一共想了3种方法,各有缺点和优势,若是你们有完美的方法,望不吝赐教github

我这里方案都是经过 getBoundingClientRect 来判断元素的位置。web

这里贴一点MDN上的解释dom

返回值是一个 DOMRect 对象,这个对象是由该元素的 getClientRects() 方法返回的一组矩形的集合, 即:是与该元素相关的CSS 边框集合 。函数

DOMRect 对象包含了一组用于描述边框的只读属性——left、top、right和bottom,单位为像素。除了 width 和 height 外的属性都是相对于视口的左上角位置而言的。性能

我这里3种方案,先说第一种

方案1:谁冒头就高亮谁

let highligthId;//须要高亮的id
const windowHeight = this.ScrollContrainer.offsetHeight //容器高度
this.anchors.forEach(element => {
  const id = element.hash.slice(1)
  const target = document.getElementById(id)
  if (target) {
    const {
      top
    } = target.getBoundingClientRect()
    // 当元素头部可见时
    if (top < windowHeight) {
      highligthId = id
    }
  }
})
if (highligthId) {
  // 调用高亮方法
  this.highLightAnchor(highligthId)
}
复制代码

优势

  • 简单

缺点

  • 初始状态若是 第一个元素 没有满屏好比 a1 ,屏幕下方漏出一点 a2 元素,那 a1 元素的高亮会里面跳走,这个时候 a1 再也高亮不到了

方案2:谁占据屏幕比例大就高亮谁

let highligthId;
let maxRatio = 0 // 占据屏幕的比例
const windowHeight = this.ScrollContrainer.offsetHeight
this.anchors.forEach(element => {
  const id = element.hash.slice(1)
  const target = document.getElementById(id)
  if (target) {
    let visibleRatio = 0;
    let {
      top,
      height,
      bottom
    } = target.getBoundingClientRect();
    // 当元素所有可见时
    if (top >= 0 && bottom <= windowHeight) {
      visibleRatio = height / windowHeight
    }
    // 当元素就头部可见时
    if (top >= 0 && top < windowHeight && bottom > windowHeight) {
      visibleRatio = (windowHeight - top) / windowHeight
    }
    // 当元素占满屏幕时
    if (top < 0 && bottom > windowHeight) {
      visibleRatio = 1
    }
    // 当元素尾部可见时
    if (top < 0 && bottom > 0 && bottom < windowHeight) {
      visibleRatio = bottom / windowHeight
    }
    if (visibleRatio >= maxRatio) {
      maxRatio = visibleRatio;
      highligthId = id;
    }
  }
});
if (highligthId) {
  this.highLightAnchor(highligthId)
}
复制代码

优势

  • 当每个元素都大于半屏时,效果好

缺点

  • 在页面最顶部 若是 a2 的比例比 a1 大,那 a1 就没法高亮了
  • 在页面最底部,如图中 a5a4 小,那么 a5 就没法高亮,由于 a5 占据屏幕的比例是大不过 a4

方案3:谁显示的自身百分比大就高亮谁

代码判断条件和 谁占据屏幕比例大就高亮谁 的方案同样,就是分母不同

let highligthId;
let maxRatio = 0
const windowHeight = this.ScrollContrainer.offsetHeight
this.anchors.forEach(element => {
  const id = element.hash.slice(1)
  const target = document.getElementById(id)
  if (target) {
    let visibleRatio = 0;
    let {
      top,
      height,
      bottom
    } = target.getBoundingClientRect();
    // 当元素所有可见时
    if (top >= 0 && bottom <= windowHeight) {
      visibleRatio = 1
    }
    // 当元素就头部可见时
    if (top >= 0 && top < windowHeight && bottom > windowHeight) {
      visibleRatio = (windowHeight - top) / height
    }
    // 当元素占满屏幕时
    if (top < 0 && bottom > windowHeight) {
      visibleRatio = windowHeight / height
    }
    // 当元素尾部可见时
    if (top < 0 && bottom > 0 && bottom < windowHeight) {
      visibleRatio = bottom / height
    }
    if (visibleRatio >= maxRatio) {
      maxRatio = visibleRatio;
      highligthId = id;
    }
  }
});
if (highligthId) {
  this.highLightAnchor(highligthId)
}
复制代码

优势

  • 当每个元素都大于半屏时,效果好
  • 在有连续出现小元素的时候效果会比谁占据屏幕比例大就高亮谁的方案好一点

缺点

  • 在页面顶部a1a2 都所有显示,那 a1 的优先级就没有 a2 高,没法高亮

  • 在页面中间 a3 很小,那么 a3 出现后就每次计算比例都是 1 ,a4 就必须等 a3 开始消失,才有可能高亮

最后

经过以上能够看到我能想到的方案在某些状况下都有问题。

在个人工做中由于每个元素都至少有半屏都大小,因此谁占据屏幕比例大就高亮谁这个方案效果是综合起来效果最好的。

其实一开始用的是谁显示的自身百分比大就高亮谁这个方案,可是这个方案在某一个元素特别特别长的时候效果就差点。

若是以上内容对你有帮助,请问能够骗个赞吗 👍👍👍👍

最后的最后附上所有代码

<html>
<body>
  <nav>
    <ul>
      <li><a href="#a1">a1</a></li>
      <li><a href="#a2">a2</a></li>
      <li><a href="#a3">a3</a></li>
      <li><a href="#a4">a4</a></li>
      <li><a href="#a5">a5</a></li>
    </ul>
    <input type="radio" name="strategy" checked value="type1">冒头就高亮<br><br>
    <input type="radio" name="strategy" value="type2">占据屏幕比例大就高亮<br><br>
    <input type="radio" name="strategy" value="type3">显示的自身百分比大就高亮<br><br>
    <p>颜色右下角能够拖动改变对应元素的大小</p>
  </nav>

  <div id="container">
    <div id="a1"></div>
    <div id="a2"></div>
    <div id="a3"></div>
    <div id="a4"></div>
    <div id="a5"></div>
  </div>
</body>
<script> function throttle(fn, interval = 1000) { let timer = null; return function(...args) { if (!timer) { timer = setTimeout(() => { timer = null fn.call(this, ...args) }, interval); } } } class AutoHighLightAnchor { anchors; ScrollContrainer; throttleFn; strategy; constructor(anchorsContainer, ScrollContrainer, strategy = AutoHighLightAnchor.Strategys.type1) { this.anchors = anchorsContainer.querySelectorAll('a') this.ScrollContrainer = ScrollContrainer; this.strategy = strategy; this.init() } init(strategy = this.strategy) { if (this.throttleFn) { this.remove() } this.throttleFn = throttle(this[strategy].bind(this), 100) this.throttleFn() // 初始执行一次更新位置 this.ScrollContrainer.addEventListener('scroll', this.throttleFn, { passive: true }) } remove() { this.ScrollContrainer.removeEventListener('scroll', this.throttleFn, { passive: true }) } highLightAnchor(id) { this.anchors.forEach(element => { element.classList.remove('highLight') if (element.hash.slice(1) == id) { element.classList.add('highLight') } }); } type1(e) { let highligthId; const windowHeight = this.ScrollContrainer.offsetHeight this.anchors.forEach(element => { const id = element.hash.slice(1) const target = document.getElementById(id) if (target) { const { top } = target.getBoundingClientRect() // 当元素头部可见时 if (top < windowHeight) { highligthId = id } } }) if (highligthId) { this.highLightAnchor(highligthId) } } type2(e) { let highligthId; let maxRatio = 0 const windowHeight = this.ScrollContrainer.offsetHeight this.anchors.forEach(element => { const id = element.hash.slice(1) const target = document.getElementById(id) if (target) { let visibleRatio = 0; let { top, height, bottom } = target.getBoundingClientRect(); // 当元素所有可见时 if (top >= 0 && bottom <= windowHeight) { visibleRatio = height / windowHeight } // 当元素就头部可见时 if (top >= 0 && top < windowHeight && bottom > windowHeight) { visibleRatio = (windowHeight - top) / windowHeight } // 当元素占满屏幕时 if (top < 0 && bottom > windowHeight) { visibleRatio = 1 } // 当元素尾部可见时 if (top < 0 && bottom > 0 && bottom < windowHeight) { visibleRatio = bottom / windowHeight } if (visibleRatio >= maxRatio) { maxRatio = visibleRatio; highligthId = id; } } }); if (highligthId) { this.highLightAnchor(highligthId) } } type3(e) { let highligthId; let maxRatio = 0 const windowHeight = this.ScrollContrainer.offsetHeight this.anchors.forEach(element => { const id = element.hash.slice(1) const target = document.getElementById(id) if (target) { let visibleRatio = 0; let { top, height, bottom } = target.getBoundingClientRect(); // 当元素所有可见时 if (top >= 0 && bottom <= windowHeight) { visibleRatio = 1 } // 当元素就头部可见时 if (top >= 0 && top < windowHeight && bottom > windowHeight) { visibleRatio = (windowHeight - top) / height } // 当元素占满屏幕时 if (top < 0 && bottom > windowHeight) { visibleRatio = windowHeight / height } // 当元素尾部可见时 if (top < 0 && bottom > 0 && bottom < windowHeight) { visibleRatio = bottom / height } if (visibleRatio >= maxRatio) { maxRatio = visibleRatio; highligthId = id; } } }); if (highligthId) { this.highLightAnchor(highligthId) } } } AutoHighLightAnchor.Strategys = { type1: 'type1', type2: 'type2', type3: 'type3' } const high = new AutoHighLightAnchor(document.querySelector('ul'), document.querySelector('#container'), AutoHighLightAnchor.Strategys.type1) document.querySelectorAll('input[type=radio]').forEach(element => { element.onchange = e => high.init(e.target.value) }) </script>
<style> body { margin: 0; } a { display: block; width: 100%; height: 100%; color: #898A95; line-height: 30px; border-radius: 0 50px 50px 0; text-decoration: none; text-align: center; } .highLight { color: #ffffff; background: #77b9e1; } nav { width: 120px; float: left; } ul { width: 100px; display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; } #container { height: 100%; overflow: scroll; } #container>div { position: relative; resize: vertical; overflow: scroll; /* color: #ffffff; font-size: 30px; */ } #container>div::after { content: attr(id); display: block; font-size: 100px; color: #fff; text-align: center; width: 100%; position: absolute; top: 50%; transform: translateY(-50%); } #container>div:hover { outline: 1px dashed #09f; } #container>div::-webkit-scrollbar { width: 25px; height: 20px; } #a1 { height: 100vh; background-color: #77b9e1; } #a2 { height: 50vh; background-color: #9fc6e6; } #a3 { height: 33vh; background-color: #73a5d7; } #a4 { height: 33vh; background-color: #1387aa; } #a5 { height: 20vh; background-color: #0c5ea8; } </style>

</html>
复制代码
相关文章
相关标签/搜索