背景
在写 tabs 组件时会遇到内容的长度超出屏幕的情况。参考 antd 的虚拟滚动实现 vue 版本。
原理
橙色为容器(可视区),绿色为内容区(宽度超过容器)。通过设置容器 overflow: hidden 隐藏滚动条,监听内容的 wheel 滚轮事件,设置内容 tranform: translateX 模拟滚动。
源码
import { ref, onMounted } from 'vue'
/**
* 水平方向隐藏滚动条, 实现滚动.
* * 需要将容器设置 overflow-hidden
* @returns container ref
*/
export default function useScrollX() {
const content = ref<HTMLElement | null>(null)
onMounted(() => {
const el = content.value
const clientWidth = el?.clientWidth || 0 // 视图宽度
const scrollWidth = el?.scrollWidth || 0 // 内容展开的宽度
if (scrollWidth > clientWidth && el) { // 是否需要滚动
el.onwheel = wheel
}
})
function wheel(e) {
const el = content.value
const clientWidth = el?.clientWidth || 0
const scrollWidth = el?.scrollWidth || 0
const maxTranslate = scrollWidth - clientWidth // 向右滚的边界
// wheelDelta 正负表示方向, 大小表示速度. + 向下滚动 | - 向上滚动 | 0 不动
const speed = e.wheelDelta > 0 ? e.wheelDelta : -e.wheelDelta
const step = parseInt(`${clientWidth / 3000 * speed}`)
const currTranslate = +(/\d+/.exec(el?.style.transform || '') || 0) // 获取当前的 translate 值
let finalTranslate = currTranslate
if (e.wheelDelta > 0) finalTranslate = currTranslate - step < 0 ? 0 : currTranslate - step
if (e.wheelDelta < 0) finalTranslate = currTranslate + step > maxTranslate ? maxTranslate : currTranslate + step
if (el) el.style.transform = `translateX(-${finalTranslate}px)`
}
return {
content,
}
}
使用
useScrollX 返回值为 content 的 ref, 只需要在 HTML 里将内容属性里添加 ref="content" 即可。