小功能——段落链接高亮跳转

323 阅读3分钟

那天产品找到我,提出了一个需求:页面内部能不能做一个文章目录,点击之后跳转到目录文本位置且标题高亮一秒;

emmmm,这不是小功能嘛!!!自信的前端分分钟给你实现!

首先想到的就是用css来实现,毕竟能用css实现的功能就一定用css去实现可是我的基操!嗯,就决定是你了,target伪元素。扒一下MDN上关于:target伪元素的demo,实现代码如下:

<body>    
<h3>Table of Contents</h3>    
<ol>    
<li><a href="#p1">Jump to the first paragraph!</a></li>    
<li><a href="#p2">Jump to the second paragraph!</a></li>   
 <li><a href="#nowhere">This link goes nowhere,    because the target doesn't exist.</a></li>    
</ol>    
<h3>My Fun Article</h3>    
<p id="p1">You can target <i>this paragraph
</i> using a    URL fragment. Click on the link above to try out!</p>    
<p id="p2">This is <i>another paragraph</i>
, also accessible    from the links above. Isn't that delightful?</p>
</body>
<style>
p:target {  
animation: colorFade 2.1s ease;
}
@keyframes colorFade {  
0% {    
background: #fff;    
-webkit-box-shadow: 12px 0 0 4px #fff, -12px 0 0 4px #fff;    
box-shadow: 12px 0 0 4px #fff, -12px 0 0 4px #fff;    
-webkit-filter: brightness(1);    
filter: brightness(1);  
}  
30% {    
background: #fff88f;    
-webkit-box-shadow: 12px 0 0 4px #fff88f, -12px 0 0 4px #fff88f;    
box-shadow: 12px 0 0 4px #fff88f, -12px 0 0 4px #fff88f;    
-webkit-filter: brightness(1.2);    
filter: brightness(1.2);  
}  
100% {    
background: #fff;    
-webkit-box-shadow: 12px 0 0 4px #fff, -12px 0 0 4px #fff;   
 box-shadow: 12px 0 0 4px #fff, -12px 0 0 4px #fff;   
 -webkit-filter: brightness(1);   
 filter: brightness(1); 
}
}
</style>

看一下实现效果:

emmm,有点不对劲,首先是高亮效果是由css的动画控制实现的,点击跳转时,target上的样式会添加到跳转的dom元素上去,也就是同一个点击跳转的元素第二次点击也不会高亮,只有点击到另一个绑定:target伪元素的元素上才会重置css(也就是点击p1元素的跳转后再次点击p1元素的跳转是没有高亮效果的,但是第二次点击p2元素是可以跳转高亮的,此时第三次点击p1元素跳转又会重置css样式的绑定效果);第二个问题是跳转本身是有一个动画时间的,实现的效果严格上来说是先高亮后跳转而不是先跳转后高亮。

     

还是摆脱不了使用js来实现,分析一波js如何实现:可以通过scrollTo、scrollBy实现dom元素的跳转,用scroll事件来监听滚动位置,如果滚动到了指定位置,执行回调方法;代码实现如下:

/**
 * @param {*} element 外部容器的dom元素
 * @param {*} top 滚动需要到达的top位置值
 * @param {*} callback 滚动后的回调函数
 * @returns
 */
function scrollToCallback (element, top, callback) {
  element.scrollTo({
    top: top,
    behavior: 'smooth'
  })

  if (!callback) return
  if (element.scrollY === top) return callback()
  let running = function (event) {
    let currentTop = this.scrollY
    if (currentTop === top) {
      this.removeEventListener('scroll', running)
      return callback()
    }
  }

  element.addEventListener('scroll', running, false)
}

总感觉有点问题,果不其然:问题很快就被我们英明神武的测试大大发现了,如果页面没有滚动条的情况,没有点击跳转效果,但是也没有高亮效果;有滚动条且页面滚动条置底,跳转位置处于如图的位置时(也就是targetTop > scrollHeight - clientHeight时),无法触发高亮效果。

由此我们可以修改scrollToCallback函数,代码如下:

/**
 * @param {*} element 外部容器的dom元素
 * @param {*} top 滚动需要到达的top位置值
 * @param {*} callback 滚动后的回调函数
 * @returns
 */
function scrollToCallback (element, top, callback) {
  element.scrollTo({
    top: top,
    behavior: 'smooth'
  })

  if (!callback) return
  // 添加上无滚动条时直接执行callback事件代码
  let currentScrollTop = element.scrollHeight - element.clientHeight
  if (currentScrollTop === 0) return callback()
  if (element.scrollTop < top || element.scrollTop === top) return callback()
  let running = function (event) {
    let currentTop = this.scrollY
    let currentScrollTop = element.scrollHeight - element.clientHeight
    top = currentScrollTop <= top ? currentScrollTop : top
    if (currentTop === top) {
      this.removeEventListener('scroll', running)
      return callback()
    }
  }

  element.addEventListener('scroll', running, false)
}

ps:回顾一波页面高度有关的知识:

scrollHeight: 就是container内部的总高度,包括由于overflow导致的滚动条页面,如下图左侧的页面高度

clientHeight: 就是container内部可见高度 + 自身padding,如下图右侧所示,只显示当前页面内的高度

offsetHeight: 也是container自己本身的可见高度 + 自身padding + 自身border + 滚动条,offsetHeight高度比clientHeight高度多了一个滚动条高度;