首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >从不希望我拥有它的组件获取mousemove事件

从不希望我拥有它的组件获取mousemove事件
EN

Stack Overflow用户
提问于 2019-08-29 02:49:51
回答 2查看 1.3K关注 0票数 2

我正在做一个React项目,我正在使用react-player来播放视频。我想在视频播放时获取鼠标移动事件,但react-player似乎捕获了这些事件,而不是传播它们。我可以做些什么来获得这些事件?

首先,我尝试向div添加一个处理程序:

代码语言:javascript
复制
<div onMouseMove={() => console.log("mousemove")}>
 <ReactPlayer url={props.url} />
</div>

然后我尝试使用addEventListener:

代码语言:javascript
复制
document.getElementById("my-div").addEventListener("mousemove", () => console.log("mousemove"), true)

<div id="my-div">
  <ReactPlayer url={props.url} />
</div>

我希望如果我将useCapture代码设置为true,addEventListener就能正常工作,但它没有帮助。

EN

回答 2

Stack Overflow用户

发布于 2019-08-29 03:13:36

我找不到任何可能的方法来强制组件传播事件,如果它有内部逻辑阻止的话。

您可以尝试创建具有足够z-index属性值的不可见<div>,使其覆盖ReactPlayer组件。在此之后,您可以直接将侦听器附加到它。

通过这种方式,您将能够捕获所需的所有鼠标事件。这不是理想的方式,但至少是可行的。

票数 1
EN

Stack Overflow用户

发布于 2019-08-29 19:14:58

严格地说,没有办法以您想要的方式提供事件传播。

原因是ReactPlayer是在一个单独的<iframe...>元素中呈现的,这意味着您正在处理两个独立的DOM树:一个在主窗口上下文中,另一个在iframe上下文中。然而,DOM事件仅在单个DOM树内传播,并且永远不会在它之外传播。

在您的特定情况下,这意味着在玩家表面上发生的鼠标事件完全由在iframe上下文中执行的代码处理,您的主窗口甚至永远不会知道这些事件。

上面讨论的解决方案是放置一种透明的div,覆盖整个玩家的表面,也不会起作用。在这种情况下,“overlay div”将捕获鼠标事件,并且这些事件将被正确传播,但仅在主窗口DOM树内。然而,在iframe中创建的DOM树永远不会知道这些事件。正如你所说的,你已经意识到了这一点。这种情况与上一段中描述的情况完全相反。

如果您能够完全控制在iframe中运行的代码,那么您将能够使用Window.postMessage()将事件从主窗口调度到iframe,并可能达到预期的结果,但是ReactPlayer对您来说是一个黑匣子,这也不是一个有效的解决方案。

因此,你必须拒绝你的鼠标事件捕获的想法,或者如果你非常需要知道鼠标指针正在玩家表面上移动,你必须发明其他解决方案,而不是基于不同DOM树之间不存在的鼠标事件传播。

我已经起草了一个名为SmartPlayer的小POC组件,它实现了一种可能的解决方案。

其基本思想是播放器被其他名为SmartOverlay的组件所覆盖,该组件旨在捕获鼠标事件。这个SmartOverlay实际上是一个由更小的“瓦片”组成的网格,每个瓦片都有自己的onMouseMove事件处理程序。诀窍是一旦触发了瓦片的onMouseMove事件处理程序,它实际上就从DOM中移除了瓦片,从而在覆盖图中创建了一个“洞”。通过这个“洞”,鼠标事件对玩家的iframe变得“可见”。我知道这听起来很奇怪,但我希望你能从代码中得到整个画面。如果你设置了“显示叠加”选择器,你实际上可以看到“瓷砖”和移动的“洞”。

请注意,当您在“洞”内移动鼠标时,鼠标移动计数器不会改变。您可以通过使tileHeighttileWidth更小来减小这种“粒度”,但代价是性能降低。

严格地说,这是一种黑客行为,在将其用于生产之前,我会三思而后行。不过,如果你真的需要在ReactPlayer上捕捉鼠标事件,这可能是你能得到的最简单的解决方案。在这个解决方案中有一些性能开销,但我试图将其保持在可接受的较低水平。

祝你好运:)

附注:我在让这段代码作为代码片段运行时遇到了麻烦。希望能以某种方式修复它。同时,我在这个答案中直接包含了整个代码。

要测试该解决方案,您可以使用create-react-app创建一个React应用程序,然后用下面的代码完全替换App.js内容。

我还将运行版本放在这里:http://react-player.2358.com.ua/

代码语言:javascript
复制
import React, { Component } from 'react'
import ReactPlayer from 'react-player'

class SmartOverlay extends Component {

    constructor(props) {
        super(props);

        const {height, width, } = props;
        const tileHeight = props.tileHeight || 64;
        const tileWidth = props.tileWidth || 64;

        //  below we're creating an array of "tiles"
        //  these "tiles" together are intended to cover all the players sufrace
        this.overlayTiles = [];
        for (let top = 0; top < height; top += tileHeight) {
            for (let left = 0; left < width; left += tileWidth) {
                const elementHeight = Math.min(tileHeight, height - top);
                const elementWidth = Math.min(tileWidth, width - left);
                const tile = {top, left, elementHeight, elementWidth }

                //  for each tile its own 'mousmove' event handler is created and assigned as the tile's property
                tile.onMouseMove = () => this.onMouseMove(tile);    
                this.overlayTiles.push(tile);
            }
        }

        //  all the overlay tiles are "active" at the beginning
        this.state = {activeOverlayTiles: this.overlayTiles}
    }

    onMouseMove(currentTile) {
        //  call event handler that is passed to the SmartOvelay via its handleMouseMove property
        //  using setTimeout here isn't strictly necessary but I prefer that "external" handler was executed only after we exit current method
        setTimeout(this.props.handleMouseMove);     

        //  "remove" current tile from the activeOverlayTiles array (that will cause removing the tile's DIV element from DOM eventually)
        //  actually we are not removing the item from the array but creating a new array that contains all the tiles but the current one
        this.setState({activeOverlayTiles: this.overlayTiles.filter(item => item !== currentTile)})

        //  after current tile is removed from DOM the "hole" is left on the overlay "surface"
        //  and the player's iframe can "see" the mouse events that are happening within the "hole"
    }

    render() {
        const showOverlayTileStyle = this.props.showOverlay ? {opacity: 0.5, background: '#fff', border: "1px solid #555",  } : {}
        return (
            this.state.activeOverlayTiles.map(item => (
                <div onMouseMove = {item.onMouseMove} style = {{...showOverlayTileStyle, position: 'absolute', top: item.top, left: item.left, height: item.elementHeight, width: item.elementWidth,}}></div>
            ))
        );
    }

}

const PlayerWithoutOverlay = ({height, width, url}) => (
    <div style = {{position: 'absolute'}}>
        <ReactPlayer height = {height} width = {width} url = {url} />
    </div>
)

const SmartPlayer = ({height, width, url, showOverlay, handleMouseMove}) => (
    <>
        <PlayerWithoutOverlay height = {height} width = {width} url = {url} />
        <SmartOverlay height = {height} width = {width} showOverlay = {showOverlay} handleMouseMove = {handleMouseMove} />
    </>
)

class App extends Component {
    constructor(props) {
        super(props);

        this.state = {
            showOverlay: false,
            mouseMoves: 0
        }
    }

    toggleShowOverlay(e) {
        //  this simply shows/hide the ovelay depending on checkbox state
        this.setState(state => ({showOverlay: !state.showOverlay}))
    }

    handleMouseMove(){
        //  adds 1 to state.mouseMoves counter
        this.setState(state => ({mouseMoves: state.mouseMoves + 1}));
    }

    render() {
        const height = 420;
        const width = 640;
        return (
            <div style = {{margin: 12, position: 'relative'}}>
                <div style = {{height: height }}>
                    <SmartPlayer 
                        height = {height} 
                        width = {width} 
                        showOverlay = {this.state.showOverlay} 
                        url = "https://www.youtube.com/watch?v=A0Z7fQyTb4M" 
                        handleMouseMove = {this.handleMouseMove.bind(this)} />
                </div>
                <div style = {{marginTop: 12}}>
                    <label>Moves detected: </label>
                    <span>{`${this.state.mouseMoves}`}</span>
                    <label onClick = {this.toggleShowOverlay.bind(this)}>&nbsp; <input type = "checkbox" value="1" checked = {this.state.showOverlay} />Show overlay</label>
                </div>

            </div>
        )
    }
}

export default App;
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57698463

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档