手摸手帶你們寫一個錨點高亮點代碼

手摸手帶你們寫一個錨點高亮點代碼

原由

前幾天公司要求寫一個錨點自動高亮功能,頁面滾動到哪裏錨點高亮到哪裏。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>
複製代碼
相關文章
相關標籤/搜索