那天产品找到我,提出了一个需求:页面内部能不能做一个文章目录,点击之后跳转到目录文本位置且标题高亮一秒;
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高度多了一个滚动条高度;