Cách lắng nghe các sự kiện nhấp chuột nằm ngoài thành phần


89

Tôi muốn đóng menu thả xuống khi nhấp chuột xảy ra bên ngoài thành phần thả xuống.

Làm thế nào để làm điều đó?

Câu trả lời:


58

Trong phần tử tôi đã thêm mousedownmouseupnhư thế này:

onMouseDown={this.props.onMouseDown} onMouseUp={this.props.onMouseUp}

Sau đó, trong cha mẹ, tôi làm điều này:

componentDidMount: function () {
    window.addEventListener('mousedown', this.pageClick, false);
},

pageClick: function (e) {
  if (this.mouseIsDownOnCalendar) {
      return;
  }

  this.setState({
      showCal: false
  });
},

mouseDownHandler: function () {
    this.mouseIsDownOnCalendar = true;
},

mouseUpHandler: function () {
    this.mouseIsDownOnCalendar = false;
}

Đây showCallà một boolean khi truehiển thị trong trường hợp của tôi một lịch và falseẩn nó.


Tuy nhiên, điều này liên kết cụ thể với một con chuột. Các sự kiện nhấp chuột có thể được tạo ra bởi các sự kiện chạm và nhập các phím, mà giải pháp này sẽ không thể phản ứng, làm cho nó không phù hợp cho các mục đích trợ năng và di động = (
Mike 'Pomax' Kamermans 30/12/14

@ Mike'Pomax'Kamermans Giờ đây, bạn có thể sử dụng onTouchStart và onTouchEnd cho điện thoại di động. facebook.github.io/react/docs/events.html#touch-events
naoufal

3
Những thứ đó đã tồn tại trong một thời gian dài, nhưng sẽ không tốt với Android, vì bạn cần phải gọi preventDefault()các sự kiện ngay lập tức hoặc hành vi Android gốc bắt đầu, điều mà quá trình xử lý trước của React can thiệp vào. Tôi đã viết npmjs.com/package/react-onclickoutside kể từ đó.
Mike 'Pomax' Kamermans

Tôi thích nó! Tuyên dương. Xóa trình nghe sự kiện trên mousedown sẽ hữu ích. componentWillUnmount = () => window.removeEventListener ('mousedown', this.pageClick, false);
Juni Brosas 14/02/17

72

Sử dụng các phương pháp vòng đời sẽ thêm và xóa các trình xử lý sự kiện vào tài liệu.

React.createClass({
    handleClick: function (e) {
        if (this.getDOMNode().contains(e.target)) {
            return;
        }
    },

    componentWillMount: function () {
        document.addEventListener('click', this.handleClick, false);
    },

    componentWillUnmount: function () {
        document.removeEventListener('click', this.handleClick, false);
    }
});

Kiểm tra các dòng 48-54 của thành phần này: https://github.com/i-like-robots/react-tube-tracker/blob/91dc0129a1f6077bef57ea4ad9a860be0c600e9d/app/component/tube-tracker.jsx#L48-54


Điều này thêm một vào tài liệu, nhưng điều đó có nghĩa là bất kỳ sự kiện nhấp chuột nào trên thành phần cũng kích hoạt sự kiện tài liệu. Điều này gây ra các vấn đề riêng của nó vì mục tiêu của trình nghe tài liệu luôn là một số div xuống thấp ở cuối một thành phần, không phải chính thành phần đó.
Allan Hortle

Tôi không chắc mình theo dõi nhận xét của bạn nhưng nếu bạn ràng buộc người nghe sự kiện với tài liệu, bạn có thể lọc bất kỳ sự kiện nhấp chuột nào xuất hiện với nó, bằng cách so khớp với bộ chọn (ủy quyền sự kiện tiêu chuẩn) hoặc theo bất kỳ yêu cầu trọng tài nào khác (EG là phần tử đích không nằm trong phần tử khác).
i_like_robots,

3
Điều này gây ra vấn đề như @AllanHortle đã chỉ ra. stopPropagation trên một sự kiện phản ứng sẽ không ngăn trình xử lý sự kiện tài liệu nhận sự kiện.
Matt Crinklaw-Vogt

4
Đối với bất kỳ ai quan tâm, tôi đang gặp sự cố với stopPropagation khi sử dụng tài liệu. Nếu bạn đính kèm trình nghe sự kiện của mình vào cửa sổ, nó có vẻ như khắc phục được sự cố này?
Titus

10
Như đã đề cập this.getDOMNode () đã lỗi thời. sử dụng ReactDOM thay thế như thế này: ReactDOM.findDOMNode (this) .contains (e.target)
Arne H. Bitubekk

17

Nhìn vào mục tiêu của sự kiện, nếu sự kiện nằm trực tiếp trên thành phần hoặc con của thành phần đó, thì nhấp chuột ở bên trong. Nếu không thì nó đã ở bên ngoài.

React.createClass({
    clickDocument: function(e) {
        var component = React.findDOMNode(this.refs.component);
        if (e.target == component || $(component).has(e.target).length) {
            // Inside of the component.
        } else {
            // Outside of the component.
        }

    },
    componentDidMount: function() {
        $(document).bind('click', this.clickDocument);
    },
    componentWillUnmount: function() {
        $(document).unbind('click', this.clickDocument);
    },
    render: function() {
        return (
            <div ref='component'>
                ...
            </div> 
        )
    }
});

Nếu điều này được sử dụng trong nhiều thành phần, nó sẽ đẹp hơn với hỗn hợp:

var ClickMixin = {
    _clickDocument: function (e) {
        var component = React.findDOMNode(this.refs.component);
        if (e.target == component || $(component).has(e.target).length) {
            this.clickInside(e);
        } else {
            this.clickOutside(e);
        }
    },
    componentDidMount: function () {
        $(document).bind('click', this._clickDocument);
    },
    componentWillUnmount: function () {
        $(document).unbind('click', this._clickDocument);
    },
}

Xem ví dụ tại đây: https://jsfiddle.net/0Lshs7mg/1/


@ Mike'Pomax'Kamermans đã được sửa, tôi nghĩ câu trả lời này bổ sung thêm thông tin hữu ích, có lẽ nhận xét của bạn bây giờ có thể bị xóa.
ja

bạn đã hoàn nguyên các thay đổi của tôi vì một lý do sai. this.refs.componentđang đề cập đến phần tử DOM kể từ 0,14 facebook.github.io/react/blog/2015/07/03/…
Gajus

@GajusKuizinas - bạn có thể thực hiện thay đổi đó sau khi 0,14 là bản phát hành mới nhất (bây giờ là bản beta).
ja

Đô la là gì?
Ben Sinclair

2
Tôi ghét chọc ngoáy jQuery với React vì chúng là khung của hai chế độ xem.
Abdennour TOUMI

11

Đối với trường hợp sử dụng cụ thể của bạn, câu trả lời hiện được chấp nhận là một chút được thiết kế quá mức. Nếu bạn muốn lắng nghe khi người dùng nhấp ra khỏi danh sách thả xuống, chỉ cần sử dụng một <select>thành phần làm phần tử mẹ và đính kèm một onBlurtrình xử lý vào nó.

Hạn chế duy nhất của phương pháp này là nó giả định rằng người dùng đã duy trì sự tập trung vào phần tử và nó phụ thuộc vào một điều khiển biểu mẫu (có thể có hoặc không phải những gì bạn muốn nếu bạn tính đến rằng tabkhóa cũng lấy nét và làm mờ phần tử ) - nhưng những hạn chế này chỉ thực sự là một giới hạn cho các trường hợp sử dụng phức tạp hơn, trong trường hợp đó, một giải pháp phức tạp hơn có thể là cần thiết.

 var Dropdown = React.createClass({

   handleBlur: function(e) {
     // do something when user clicks outside of this element
   },

   render: function() {
     return (
       <select onBlur={this.handleBlur}>
         ...
       </select>
     );
   }
 });

10
onBlurdiv cũng làm việc với div nếu nó có tabIndexthuộc tính
gorpacrate

Đối với tôi, đây là giải pháp tôi thấy đơn giản nhất và dễ sử dụng nhất, tôi đang sử dụng nó trên một phần tử nút và hoạt động như một chiếc bùa. Cảm ơn!
Amir5000

5

Tôi đã viết một trình xử lý sự kiện chung cho các sự kiện bắt nguồn bên ngoài thành phần, phản ứng-bên ngoài-sự kiện .

Bản thân việc thực hiện rất đơn giản:

  • Khi thành phần được gắn kết, một trình xử lý sự kiện được gắn vào windowđối tượng.
  • Khi một sự kiện xảy ra, thành phần sẽ kiểm tra xem sự kiện có bắt nguồn từ bên trong thành phần hay không. Nếu không, thì nó sẽ kích hoạt onOutsideEventthành phần đích.
  • Khi thành phần không được gắn kết, trình xử lý sự kiện được giải nén.
import React from 'react';
import ReactDOM from 'react-dom';

/**
 * @param {ReactClass} Target The component that defines `onOutsideEvent` handler.
 * @param {String[]} supportedEvents A list of valid DOM event names. Default: ['mousedown'].
 * @return {ReactClass}
 */
export default (Target, supportedEvents = ['mousedown']) => {
    return class ReactOutsideEvent extends React.Component {
        componentDidMount = () => {
            if (!this.refs.target.onOutsideEvent) {
                throw new Error('Component does not defined "onOutsideEvent" method.');
            }

            supportedEvents.forEach((eventName) => {
                window.addEventListener(eventName, this.handleEvent, false);
            });
        };

        componentWillUnmount = () => {
            supportedEvents.forEach((eventName) => {
                window.removeEventListener(eventName, this.handleEvent, false);
            });
        };

        handleEvent = (event) => {
            let target,
                targetElement,
                isInside,
                isOutside;

            target = this.refs.target;
            targetElement = ReactDOM.findDOMNode(target);
            isInside = targetElement.contains(event.target) || targetElement === event.target;
            isOutside = !isInside;



            if (isOutside) {
                target.onOutsideEvent(event);
            }
        };

        render() {
            return <Target ref='target' {... this.props} />;
        }
    }
};

Để sử dụng thành phần, bạn cần bao bọc khai báo lớp thành phần đích bằng thành phần bậc cao hơn và xác định các sự kiện mà bạn muốn xử lý:

import React from 'react';
import ReactDOM from 'react-dom';
import ReactOutsideEvent from 'react-outside-event';

class Player extends React.Component {
    onOutsideEvent = (event) => {
        if (event.type === 'mousedown') {

        } else if (event.type === 'mouseup') {

        }
    }

    render () {
        return <div>Hello, World!</div>;
    }
}

export default ReactOutsideEvent(Player, ['mousedown', 'mouseup']);

4

Tôi đã bình chọn một trong những câu trả lời mặc dù nó không phù hợp với tôi. Nó đã dẫn tôi đến giải pháp này. Tôi đã thay đổi thứ tự hoạt động một chút. Tôi lắng nghe mouseDown trên mục tiêu và mouseUp trên mục tiêu. Nếu một trong hai điều đó trả về TRUE, chúng tôi không đóng phương thức. Ngay sau khi một nhấp chuột được đăng ký, ở bất kỳ đâu, hai boolean đó {mouseDownOnModal, mouseUpOnModal} được đặt lại thành false.

componentDidMount() {
    document.addEventListener('click', this._handlePageClick);
},

componentWillUnmount() {
    document.removeEventListener('click', this._handlePageClick);
},

_handlePageClick(e) {
    var wasDown = this.mouseDownOnModal;
    var wasUp = this.mouseUpOnModal;
    this.mouseDownOnModal = false;
    this.mouseUpOnModal = false;
    if (!wasDown && !wasUp)
        this.close();
},

_handleMouseDown() {
    this.mouseDownOnModal = true;
},

_handleMouseUp() {
    this.mouseUpOnModal = true;
},

render() {
    return (
        <Modal onMouseDown={this._handleMouseDown} >
               onMouseUp={this._handleMouseUp}
            {/* other_content_here */}
        </Modal>
    );
}

Điều này có lợi thế là tất cả mã nằm trong thành phần con chứ không phải thành phần mẹ. Nó có nghĩa là không có mã soạn sẵn để sao chép khi sử dụng lại thành phần này.


3
  1. Tạo một lớp cố định kéo dài toàn bộ màn hình ( .backdrop).
  2. Có phần tử đích ( .target) bên ngoài .backdropphần tử và có chỉ số xếp chồng lớn hơn ( z-index).

Sau đó, bất kỳ nhấp chuột nào vào .backdropphần tử sẽ được coi là "bên ngoài .targetphần tử".

.click-overlay {
    position: fixed;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    z-index: 1;
}

.target {
    position: relative;
    z-index: 2;
}

2

Bạn có thể sử dụng refs để đạt được điều này, một cái gì đó như sau sẽ hoạt động.

Thêm vào refphần tử của bạn:

<div ref={(element) => { this.myElement = element; }}></div>

Sau đó, bạn có thể thêm một hàm để xử lý nhấp chuột bên ngoài phần tử như sau:

handleClickOutside(e) {
  if (!this.myElement.contains(e)) {
    this.setState({ myElementVisibility: false });
  }
}

Sau đó, cuối cùng, thêm và xóa các trình nghe sự kiện trên sẽ gắn kết và sẽ ngắt kết nối.

componentWillMount() {
  document.addEventListener('click', this.handleClickOutside, false);  // assuming that you already did .bind(this) in constructor
}

componentWillUnmount() {
  document.removeEventListener('click', this.handleClickOutside, false);  // assuming that you already did .bind(this) in constructor
}

Đã sửa đổi câu trả lời của bạn, có thể có lỗi khi tham chiếu đến việc gọi handleClickOutsidevào document.addEventListener()bằng cách thêm thistham chiếu. Nếu không nó mang lại cho ReferenceError chưa gặp: handleClickOutside không được định nghĩa trongcomponentWillMount()
Muhammad Hannan

1

Đến bữa tiệc rất muộn, nhưng tôi đã thành công với việc thiết lập một sự kiện mờ trên phần tử chính của menu thả xuống với mã được liên kết để đóng menu thả xuống và cũng đính kèm trình nghe mousedown vào phần tử mẹ để kiểm tra xem menu thả xuống có mở không hoặc không, và sẽ dừng truyền sự kiện nếu nó đang mở để sự kiện mờ không được kích hoạt.

Vì sự kiện chuyển động xuống bong bóng nên điều này sẽ ngăn không cho bất kỳ thao tác chuyển xuống nào đối với trẻ em gây mờ cho phụ huynh.

/* Some react component */
...

showFoo = () => this.setState({ showFoo: true });

hideFoo = () => this.setState({ showFoo: false });

clicked = e => {
    if (!this.state.showFoo) {
        this.showFoo();
        return;
    }
    e.preventDefault()
    e.stopPropagation()
}

render() {
    return (
        <div 
            onFocus={this.showFoo}
            onBlur={this.hideFoo}
            onMouseDown={this.clicked}
        >
            {this.state.showFoo ? <FooComponent /> : null}
        </div>
    )
}

...

e.preventDefault () không cần phải được gọi như tôi có thể giải thích nhưng firefox không hoạt động tốt nếu không có lý do gì. Hoạt động trên Chrome, Firefox và Safari.


0

Tôi đã tìm thấy một cách đơn giản hơn về điều này.

Bạn chỉ cần thêm onHide(this.closeFunction)vào phương thức

<Modal onHide={this.closeFunction}>
...
</Modal>

Giả sử bạn có một chức năng để đóng phương thức.


-1

Sử dụng mixin phản ứng bên ngoài tuyệt vời :

npm install --save react-onclickoutside

Và sau đó

var Component = React.createClass({
  mixins: [
    require('react-onclickoutside')
  ],
  handleClickOutside: function(evt) {
    // ...handling code goes here... 
  }
});

4
Tính năng kết hợp FYI hiện không được dùng trong React.
machineghost
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.