【问题标题】:Disable all pointer events except scroll on overlaying div禁用所有指针事件,除了覆盖 div 上的滚动
【发布时间】:2018-05-13 10:53:43
【问题描述】:

问题:

我有两个容器,其中包含溢出的文本内容,如下所示:

蓝色的<div>s 有overflow:hidden。现在我想以自定义的同步*方式滚动这些 div,无论我在白色容器 <div> 滚动的位置。我的想法是,我可以创建一个绝对定位的透明 <div> 作为白色容器的直接子代,并给它一个溢出的子代:

蓝色容器的 z-index 比原来的两个文本容器高:

.container {
  width: 100vw;
  height: 100vh;
  z-index: 10;
  position: absolute;
  overflow-y: scroll;
}

所以最终的结果看起来像这样:

现在我希望能够滚动覆盖容器,但在底层元素中捕获其他鼠标事件(如文本选择)

我的目标是在滚动覆盖容器时使用 JavaScript 手动滚动底层容器。

问题:

鉴于no way 可以通过css 属性pointer-events 选择性地禁用指针事件,有没有其他方法可以仅启用覆盖元素的滚动事件,同时将其他指针事件传递给底层元素?

背景:

*我正在尝试实现的目标类似于 Perforce P4Merge 使用他们的差异工具所做的。它们有一个用于 2 个代码块的垂直滚动条,我假设滚动高度大于两个代码块中的任何一个。在某些情况下,滚动事件会滚动两个代码块,有时只是其中一个,而在其他情况下,它们会以不同的速度滚动(取决于添加和删除的内容)。

更新: 原始实现是用 react 编写的,在该代码中,我不必在 scrollable-container 上使用 margin-left: -18px; 来显示滚动条。不知道为什么。此外,如果您愿意,这里有一个 codepen:codepen snippet

body {
  overflow-y: hidden;
}

.app {
  overflow-y: hidden;
  position: relative;
  display: flex;
  flex-direction: row;
  z-index: 0;
}

.scrollable-container {
  width: 100vw;
  height: 100vh;
  z-index: 10;
  margin-left: -18px;
  position: absolute;
  overflow-y: scroll;
}

.scrollable-content {
  width: 500px;
  height: 1600px;
}

.non-scrollable-container {
  flex: 1;
  height: 100vh;
  overflow-y: hidden;
}

.bridge {
  width: 40px;
  background: linear-gradient(white, black);
  cursor: ew-resize;
  height: 100vh;
}

#original {
  background: linear-gradient(red, yellow);
  height: 2100px;
}

#modified {
  background: linear-gradient(blue, green);
  height: 1600px;
}
<div class="app">
  <div class="scrollable-container">
    <div class="scrollable-content"></div>
  </div>
  <div class="non-scrollable-container">
    <div id="original" class="codeBlock">
      Content I want to select
    </div>
  </div>
  <div class="bridge"></div>
  <div class="non-scrollable-container">
    <div id="modified" class="codeBlock">
      Content I want to select
    </div>
  </div>
</div>

【问题讨论】:

  • 叠加的div会有背景吗?我问是因为我的第一个想法是:如果没有,那么只需将其放在 Z 顺序的顶部并在其上处理滚动,它应该只是工作™。
  • 请用minimal reproducible example 更新您的问题,显示问题显示五个 div(蓝色、绿色、额外的绿色叠加层),理想情况下是 可运行 一个使用 Stack Snippets( [&lt;&gt;] 工具栏按钮;here's how to do one)。
  • 抱歉,应该在发帖前做一个codepen,目前正在处理。覆盖的可滚动 div(具有更高的 z-index)是透明的,但它仍然不起作用。 编辑:或堆栈sn-p!

标签: javascript html css


【解决方案1】:

这个问题已经很老了,但如果有人在这里寻找类似的东西,我会找到解决方案。 我使用 java 脚本事件侦听器暂时禁用 mousedown 上的指针事件并重新启用其父鼠标上的指针事件

function addlistener() {
  var scrollable = document.getElementsByClassName("scrollable-container")[0];
  scrollable.addEventListener('mousedown', function() {
    this.style.pointerEvents = "none";
    document.elementFromPoint(event.clientX, event.clientY).click();
  }, false);

  document.getElementsByClassName("app")[0].addEventListener('mouseup', function(e) {
    scrollable.style.pointerEvents = "all";
  }, false);
}
body {
  overflow-y: hidden;
}

.app {
  overflow-y: hidden;
  position: relative;
  display: flex;
  flex-direction: row;
  z-index: 0;
}

.scrollable-container {
  width: 100vw;
  height: 100vh;
  margin-left: -18px;
  position: absolute;
  overflow-y: scroll;
  z-index: 10;
}

.scrollable-content {
  width: 500px;
  height: 1600px;
}

.non-scrollable-container {
  flex: 1;
  height: 100vh;
  overflow-y: hidden;
}

.bridge {
  width: 40px;
  background: linear-gradient(white, black);
  cursor: ew-resize;
  height: 100vh;
}

#original {
  background: linear-gradient(red, yellow);
  height: 2100px;
}

#modified {
  background: linear-gradient(blue, green);
  height: 1600px;
}
<body onload="addlistener()">
  <div class="app">
    <div class="scrollable-container">
      <div class="scrollable-content"></div>
    </div>
    <div class="non-scrollable-container">
      <div id="original" class="codeBlock">
        Content I want to select
      </div>
    </div>
    <div class="bridge"></div>
    <div class="non-scrollable-container">
      <div id="modified" class="codeBlock">
        Content I want to select
      </div>
    </div>
  </div>
</body>

【讨论】:

    【解决方案2】:

    现在已经过去了几天,根据我的研究,似乎不可能以这种方式实现我想要的。无法选择性地禁用指针事件,我找不到任何解决方法。

    相反,我能想到的最好方法是实现我自己的“假”滚动条。这个滚动条实现订阅了容器的wheel 事件,然后我将子滚动容器同步到相同的位置。我暂时不回答这个问题,以防有人为我提出的问题提出更好的解决方案。

    对于任何感兴趣的人,您可以在下面找到我的解决方案。 注意:选择整页视图以获得更好的体验。

    let appStyles = {
    	original: {
    		background: 'linear-gradient(red, yellow)',
    		height: '1600px',
    	},
    	modified: {
    		background: 'linear-gradient(blue, green)',
    		height: '2100px',
    	},
    };
    
    
    let Pane = React.forwardRef((props, ref) => {
    	return <PaneComponent {...props} forwardedRef={ref} />;
    });
    let PaneWithScrollSync = withScrollSync(Pane);
    
    class App extends React.Component {
    
      render() {
    		return (
    			<div className="app">
    				<FakeScrollBar scrollHeight={2100}>
    					<Splitter>
    						<PaneWithScrollSync>
    							<pre className="code" style={appStyles.original}>
    								<code>Content with height: 1600px</code>
    							</pre>
    						</PaneWithScrollSync>
    						<PaneWithScrollSync>
    							<pre className="code" style={appStyles.modified}>
    								<code>Ccontent with height: 2100px</code>
    							</pre>
    						</PaneWithScrollSync>
    					</Splitter>
    				</FakeScrollBar>
    			</div>
    		);
    	}
    }
    
    let scrollStyles = {
    	container: {
    		display: 'flex',
    		flexDirection: 'row',
    		flex: 1,
    	},
    	scrollTrack: {
    		width: 30,
    		borderLeft: '1px solid',
    		borderLeftColor: '#000',
    		background: '#212121',
    		position: 'relative',
    	},
    	scrollThumb: {
    		position: 'absolute',
    		background: 'red',
    		width: '100%',
    	},
    	scrollThumbHover: {
    		background: 'blue',
    	},
    };
    
    const ScrollContext = React.createContext();
    class FakeScrollBar extends React.Component {
    	state = {
    		scrollTopRelative: 0,
    		thumbRelativeHeight: 0,
    		thumbMouseOver: false,
    	};
    
    	constructor(props) {
    		super(props);
    		this.scrollTrack = React.createRef();
    	}
    
    	get trackPosition() {
    		if (!this.scrollTrack.current) {
    			return 0;
    		}
    		return (this.scrollTop / this.props.scrollHeight) * this.scrollTrack.current.clientHeight;
    	}
    
    	get scrollTop() {
    		return this.state.scrollTopRelative * this.scrollTopMax;
    	}
    
    	get scrollTopMax() {
    		return this.props.scrollHeight - this.scrollTrack.current.clientHeight;
    	}
    
    	get thumbHeight() {
    		if (!this.scrollTrack.current) {
    			return 0;
    		}
    		return this.state.thumbRelativeHeight * this.scrollTrack.current.clientHeight;
    	}
    
    	handleWheel = e => {
    		if (e.deltaMode !== 0) {
    			console.error('The scrolling is not in pixel mode!');
    			return false;
    		}
    
    		let deltaYPercentage = e.deltaY / this.scrollTopMax;
    		let scrollTopRelative = Math.min(
    			Math.max(this.state.scrollTopRelative + deltaYPercentage, 0),
    			1
    		);
    		this.setState({
    			scrollTopRelative,
    		});
    	};
    
    	handleMouseEnterThumb = e => {
    		this.setState({ thumbMouseOver: true });
    	};
    
    	handleMouseLeaveThumb = e => {
    		this.setState({ thumbMouseOver: false });
    	};
    
    	getSyncedPosition = container => {};
    
    	componentDidMount() {
    		this.updateScrollThumbHeight();
    		window.addEventListener('resize', this.updateScrollThumbHeight);
    	}
    
    	componentWillUnmount() {
    		window.removeEventListener('resize', this.updateScrollThumbHeight);
    	}
    
    	updateScrollThumbHeight = e => {
    		this.setState({
    			thumbRelativeHeight: this.scrollTrack.current.clientHeight / this.props.scrollHeight,
    		});
    	};
    
    	render() {
    		let { thumbMouseOver } = this.state;
    
    		return (
    			<ScrollContext.Provider value={this.state}>
    				<div style={scrollStyles.container} onWheel={this.handleWheel}>
    					{this.props.children}
    					<div ref={this.scrollTrack} style={scrollStyles.scrollTrack}>
    						<div
    							onMouseEnter={this.handleMouseEnterThumb}
    							onMouseLeave={this.handleMouseLeaveThumb}
    							style={Object.assign(
    								{ top: this.trackPosition },
    								{ height: this.thumbHeight },
    								scrollStyles.scrollThumb,
    								thumbMouseOver && scrollStyles.scrollThumbHover
    							)}
    						/>
    					</div>
    				</div>
    			</ScrollContext.Provider>
    		);
    	}
    }
    
    let splitterStyles = {
    	container: {
    		display: 'flex',
    		flexDirection: 'row',
    		flex: 1,
    	},
    	bridge: {
    		width: '40px',
    		height: '100vh',
    		position: 'relative',
    		background: 'linear-gradient(white, black)',
    		cursor: 'ew-resize',
    	},
    };
    
    class Splitter extends React.Component {
    	state = {
    		dragging: false,
    		leftPaneFlex: 0.5,
    		rightPaneFlex: 0.5,
    	};
    
    	componentDidMount() {
    		if (this.props.children.length !== 2) {
    			console.error('The splitter needs to `Pane` children to work');
    		}
    	}
    
    	handleMouseUp = e => {
    		this.setState({ dragging: false });
    		this.bridge.removeEventListener('mouseup', this.handleMouseUp);
    	};
    
    	handleMouseMove = e => {
    		if (!this.state.dragging) {
    			return;
    		}
    
    		let splitterPosition = this.getRelativeContainerX(e.clientX);
    		console.log(splitterPosition);
    		this.setState({
    			leftPaneFlex: splitterPosition,
    			rightPaneFlex: 1 - splitterPosition,
    		});
    	};
    
    	handleMouseDown = e => {
    		this.setState({ dragging: true });
    		document.addEventListener('mouseup', this.handleMouseUp);
    		document.addEventListener('mousemove', this.handleMouseMove);
    	};
    
    	getRelativeContainerX(x) {
    		var rect = this.container.getBoundingClientRect();
    		return (x - rect.left) / rect.width;
    	}
    
    	render() {
    		const { children } = this.props;
    		let commonProps = {
    			dragging: this.state.dragging,
    		};
    		const leftPane = React.cloneElement(children[0], {
    			...commonProps,
    			flex: this.state.leftPaneFlex,
    		});
    		const rightPane = React.cloneElement(children[1], {
    			...commonProps,
    			flex: this.state.rightPaneFlex,
    		});
    
    		return (
    			<div style={splitterStyles.container} ref={container => (this.container = container)}>
    				{leftPane}
    				<div
    					style={{ ...splitterStyles.bridge }}
    					ref={bridge => (this.bridge = bridge)}
    					onDrag={this.handleDrag}
    					onMouseDown={this.handleMouseDown}
    				/>
    				{rightPane}
    			</div>
    		);
    	}
    }
    
    let paneStyles = {
    	scrollContainer: {
    		height: '100vh',
    		overflow: 'hidden',
    	},
    	pane: {
    		flex: 1,
    		minWidth: 'fit-content',
        border: '5px solid', // remove
    		borderColor: 'cyan', // remove
    	},
    };
    
    class PaneComponent extends React.Component {
    	render() {
    		const { forwardedRef, dragging, ...rest } = this.props;
    		return (
    			<div
    				ref={forwardedRef}
    				style={{ flex: this.props.flex, ...paneStyles.scrollContainer }}
    				{...rest}
    			>
    				<div
    					style={{
    						userSelect: dragging ? 'none' : 'auto',
    						...paneStyles.pane,
    					}}
    				>
    					{this.props.children}
    				</div>
    			</div>
    		);
    	}
    }
    
    
    
    function withScrollSync(WrappedComponent) {
    	class ScrollSynced extends React.Component {
    		constructor(props) {
    			super(props);
    			this.wrappedComponent = React.createRef();
    		}
    
    		componentDidUpdate() {
    			let { scrollTopRelative } = this.props;
    			if (!this.wrappedComponent) {
    				return;
    			}
    
    			let { scrollHeight, clientHeight } = this.wrappedComponent.current;
    			this.wrappedComponent.current.scrollTop = (scrollHeight - clientHeight) * scrollTopRelative;
    		}
    
    		render() {
    			let { scrollTopRelative, forwardedRef, ...rest } = this.props;
    			return <WrappedComponent ref={this.wrappedComponent} {...rest} />;
    		}
    	}
    	ScrollSynced.propTypes = WrappedComponent.propTypes;
    	return React.forwardRef((props, ref) => (
    		<ScrollContext.Consumer>
    			{state => (
    				<ScrollSynced
    					{...props}
    					forwardedRef={ref}
    					scrollTopRelative={state.scrollTopRelative}
    				/>
    			)}
    		</ScrollContext.Consumer>
    	));
    }
    
    
    ReactDOM.render(
      <App />,
      document.getElementById('root')
    )
    body {
      margin: 0;
    	overflow-y: hidden;
    }
    
    .app {
      display: flex;
      flex-direction: row;
    }
    
    .code {
      margin: 0;
    }
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <div id="root">
      
    </div>

    【讨论】:

      猜你喜欢
      • 2012-09-28
      • 2021-02-15
      • 2015-05-18
      • 1970-01-01
      • 2017-04-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多