JavaScript实现跨容器自定义滚动条:解决多滚动条可视问题
作者:青柠时间:2026-10-24 14:25:49浏览:274
本文介绍在上下与左右滚动条分离于不同DIV时,如何实现自定义滚动条以确保两者在同一视口可见。通过隐藏原生滚动条并开发自定义组件,利用事件总线同步滚动状态,提供完整的JS与CSS实现思路及代码示例,同时推荐优先使用成熟的第三方库处理复杂计算。
在实际开发场景中,常遇到上下滚动条与左右滚动条被划分在不同的DIV容器中,导致某些情况下右侧滚动条无法显示。为了满足用户需求,需要在同一视口内同时展示这两个滚动条。
一种可行的解决方案是:采用自定义滚动条机制,并隐藏浏览器原生的滚动条。
自定义滚动条
核心逻辑封装在 scrollbar.js 文件中。
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import '../css/scrollbar.css';
const propTypes = {
eventBus: PropTypes.object.isRequired,
};
class ScrollBar extends Component {
constructor(props) {
super(props);
this.state = {
isDraging: false,
// X: bottom scrollbar offset left, range [0, innerWidth - 100]. When dragging, x is changing
x: null,
// clickX 表示拖动滚动条时,鼠标点击的位置距离滚动条左侧的距离, range [0, 100], When dragging, clickX isn't changing
clickX: 0,
};
}
componentDidMount() {
this.unsubscribeScrollToColumn = this.props.eventBus.subscribe('set-scrollbar-left', this.setScrollBarLeft);
document.addEventListener('mouseup', this.onMouseUp);
}
componentWillUnmount() {
this.unsubscribeScrollToColumn();
document.removeEventListener('mouseup', this.onMouseUp);
}
/**
* 这个函数处理联动(界面滚动时,触发滚动条滚动)这里的100是滚动条的宽度
*/
setScrollBarLeft = (leftRatio) => {
// when bottom scrollbar is dragging, can't set scrollBa left
if (this.state.isDraging) return;
this.setState({
x: (window.innerWidth - 100) * leftRatio,
});
}
/**
* 当鼠标按下,开始拖动,设置当前的位置为初始拖动的位置
*/
handleMouseDown = (e) => {
this.setState({
isDraging: true,
clickX: e.nativeEvent.offsetX,
});
}
/**
* 当鼠标抬起时,停止拖拽,设置当前的点击位置是0(这个有没有必要设置)
*/
onMouseUp = () => {
if (this.state.isDraging) {
setTimeout(() => {
this.setState({ isDraging: false, clickX: 0 });
}, 100);
}
}
/**
* 当拖拽进行时(鼠标按下并开始移动),获取当前的位移,计算新的偏移量
* 注意:可以向右滚动,可以向左滚动
* 当拖拽进行时,应该计算出当前的比例,然后Grid水平滚动
* 现在的问题,如果鼠标拖动时移动到滚动条外部,那么无法触发拖动
* */
onMouseMove = (e) => {
e.persist();
if (this.state.isDraging) {
// 新距离 = 原始距离 + (当前滚动的距离 - 初始滚动的距离)
let newX = this.state.x + e.nativeEvent.offsetX - this.state.clickX;
newX = Math.min(newX, window.innerWidth - 100); // 最大的拖动不能超过右侧边界
this.setState({ x: newX });
const leftRatio = newX / (window.innerWidth - 100);
}
}
renderBottomToolbar = () => {
return (
<div
className="antiscroll-scrollbar antiscroll-scrollbar-horizontal antiscroll-scrollbar-shown"
style={{transform: `translateX(${this.state.x}px)`}}
draggable="true"
onMouseDown={this.handleMouseDown}
onMouseMove={this.onMouseMove}
onMouseUp={this.onMouseUp}
></div>
);
}
// todo: rightToolbar event handle
renderRightToolbar = () => {
return (
<div
className="antiscroll-scrollbar antiscroll-scrollbar-vertical antiscroll-scrollbar-shown"
></div>
);
}
render() {
return (
<div id="scrollOverlay" className="antiscroll-wrap">
{this.renderBottomToolbar()}
{this.renderRightToolbar()}
</div>
);
}
}
ScrollBar.propTypes = propTypes;
export default ScrollBar;
滚动条样式
相关的样式定义位于 scrollbar.css 文件中。
#scrollOverlay {
display: inline-block;
overflow: hidden;
position: fixed;
left: 0;
right: 0;
top: 156px;
bottom: 0;
z-index: 4;
pointer-events: none;
opacity: .7;
}
#scrollOverlay .antiscroll-scrollbar {
pointer-events: auto;
z-index: 2;
background-color: hsla(0,0%,0%,0.28);
box-shadow: inset 0 0 0 1px hsl(0,0%,100%);
border-radius: 5px;
}
#scrollOverlay .antiscroll-scrollbar-horizontal {
height: 12px;
width: 100px;
position: absolute;
bottom: 32px;
}
#scrollOverlay .antiscroll-scrollbar-vertical {
width: 12px;
height: 100px;
position: absolute;
right: 0;
}
/* 隐藏原始滚动对象的滚动条 */
.react-demo::-webkit-scrollbar {
width: 0;
}
滚动条具体使用
在具体使用时,我们将该滚动条组件集成到 Grid 组件中。
import ScrollBar from '../components/scrollbar';
// Grid 原生滚动,触发回调函数
onScroll = () => {
// todo: when clientWidth is smaller than innerWidth, don't show bottom scrollBar
let scrollLeftRatio = this._scrollLeft / (clientWidth - window.innerWidth);
// 当原生DOM左右滚定时,获取当前滚动的比例(偏移量/全部宽度),并设置滚动条进行滚动
this.setScrollLeftRatio(scrollLeftRatio);
}
setScrollLeftRatio = (scrollLeftRatio) => {
this.props.eventBus.dispatch('set-scrollbar-left', scrollLeftRatio);
}
// 在原始滚动元素中,传入eventBus,便于事件传值处理
// <ScrollBar eventBus={this.props.eventBus}/>
此外,市面上存在许多开源的第三方滚动条组件库。鉴于自定义滚动条涉及大量的边界情况和计算逻辑,建议优先选用成熟的第三方库来实现。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
作者最新文章
JS读取本地文件时报错或无响应的常见原因
2026-11-05 16:32:31
let、const与var的区别:JavaScript变量声明最佳实践
2026-11-05 14:16:38
JS中直接定义静态二维数组的字面量写法
2026-11-05 14:16:08
JS 怎么查找字符串中所有匹配项的位置
2026-11-05 14:15:53
JavaScript新手常犯的编码错误与最佳实践指南
2026-11-04 22:12:38
