题目
京东之类的电商网站的app,左侧的sidebar(通常是分类导航)往往有一个点击效果,就是点击按钮之后按钮会滑到sidebar的中央。
实现原理
利用元素的scrollTo方法,计算top值,结合behavior: 'smooth'
参数,即可实现这个效果。
源码
前提:按钮和容器之间不允许再有中间容器,因为代码只考虑到了父容器必须是overflow: auto。按钮只允许一层元素,也因为代码只考虑按钮是一层元素。
clickItem() {
const containerHeight = event.target.parentNode.offsetHeight
const itemHeight = event.target.offsetHeight
const index = Array.prototype.indexOf.call(event.target.parentNode.children, event.target)
const scrollToTopValue = itemHeight * index - (containerHeight - itemHeight) / 2
event.target.parentNode.scrollTo({
top: scrollToTopValue,
behavior: 'smooth'
})
}
技术点
offsetHeight
:获取一个元素的高度的JS方法。
Array.prototype.indexOf.call()
:这个写法在前端初学者中不常见,具体说一下。
- 先说
Array.prototype
是什么。MDN说:
Array.prototype
属性表示Array
构造函数的原型,并允许您向所有Array对象添加新的属性和方法。
Array.prototype.indexOf
是什么。就是一个叫做“indexOf”的JS原生函数,它返回一个原生函数,所以具体实现是不可见的。你可以console.log()它看一看,得到ƒ indexOf() { [native code] }
这点字,看不到具体内容。.call()
是什么。MDN说:
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
就是说:
首先,call一定会执行一个函数;
执行函数时,先改变this的指向;
this指向谁呢?指向call的第一个参数;
第二个、第三个。。。第N个参数干什么用的呢?向函数传递。
怎么理解
Array.prototype.indexOf.call(event.target.parentNode.children, event.target)
?它就等于event.target.parentNode.children.indexOf(event.target)
+类数组隐式变换
。event.target.parentNode.children
原本是HTMLNodeList,是类数组,但是call有一个能力是隐式变换,可以将类数组变为真正的数组,然后执行indexOf方法。有人将
Array.prototype.indexOf.call(event.target.parentNode.children, event.target)
改写成[].indexOf.call(event.target.parentNode.children, event.target)
一样生效,为什么?因为[].indexOf === Array.prototype.indexOf
。
改写为自定义指令
按照Vue插件写法惯例,分成2个文件,一个负责安装,另一个负责逻辑:
index.js
import sidebarScrollToCenter from './sidebarScrollToCenter'
const install = function(Vue) {
Vue.directive('sidebarScrollToCenter', sidebarScrollToCenter)
}
sidebarScrollToCenter.install = install
export default sidebarScrollToCenter
旁边放一个sidebarScrollToCenter.js:
function handleClick(el, binding) {
el.addEventListener('click', function(e) {
const containerHeight = e.target.parentNode.offsetHeight
const itemHeight = e.target.offsetHeight
const index = [].indexOf.call(e.target.parentNode.children, e.target)
const scrollToTopValue = itemHeight * index - (containerHeight - itemHeight) / 2
e.target.parentNode.scrollTo({
top: scrollToTopValue,
behavior: 'smooth'
})
})
}
export default {
bind(el, binding) {
el.addEventListener('click', handleClick(el, binding), false)
}
}
用法:
v-sidebarScrollToCenter
一定要加在按钮身上,不能加在容器上。