Як прослуховувати події клацання, що знаходяться поза компонентом


89

Я хочу закрити спадне меню, коли клацання відбувається поза випадаючим компонентом.

Як це зробити?

Відповіді:


59

В елементі, який я додав mousedownі mouseupподобається так:

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

Потім у батьків я роблю це:

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;
}

Це showCalлогічне значення, яке, коли trueв моєму випадку показує календар і falseприховує його.


однак це прив'язує клацання до миші. Події натискання також можуть генеруватися подіями дотику та клавішами введення, на що це рішення не зможе реагувати, роблячи його непридатним для мобільних пристроїв та цілей доступності = (
Mike 'Pomax' Kamermans

@ Mike'Pomax'Kamermans Тепер ви можете використовувати onTouchStart та onTouchEnd для мобільних пристроїв. facebook.github.io/react/docs/events.html#touch-events
naoufal

3
Вони існують вже давно, але не будуть грати добре з андроїдом, тому що вам потрібно негайно викликати preventDefault()події, або ввімкнеться рідна поведінка Android, якій заважає попередня обробка React. Я писав npmjs.com/package/react-onclickoutside з.
Майк 'Помакс' Камерманс

Я це люблю! З похвалою. Видалення прослуховувача подій у mousedown буде корисним. componentWillUnmount = () => window.removeEventListener ('mousedown', this.pageClick, false);
Juni Brosas

73

За допомогою методів життєвого циклу додавання та видалення прослуховувачів подій до документа.

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);
    }
});

Перегляньте рядки 48-54 цього компонента: https://github.com/i-like-robots/react-tube-tracker/blob/91dc0129a1f6077bef57ea4ad9a860be0c600e9d/app/component/tube-tracker.jsx#L48-54


Це додає документ до документа, але це означає, що будь-які події клацання на компоненті також запускають подію документа. Це спричиняє власні проблеми, оскільки ціллю прослуховувача документа завжди є якийсь низ внизу в кінці компонента, а не сам компонент.
Аллан Гортл,

Я не зовсім впевнений, що слідую за вашим коментарем, але якщо ви прив'язуєте слухачів подій до документа, ви можете відфільтрувати будь-які події кліків, які з'являються до нього, або шляхом відповідності селектора (стандартне делегування події), або за будь-якими іншими довільними вимогами (наприклад, цільовий елемент не входить до іншого елемента).
i_like_robots

3
Це справді викликає проблеми, як зазначає @AllanHortle. stopPropagation події реакції не завадить обробникам подій документа отримати подію.
Matt Crinklaw-Vogt

4
Для всіх, хто цікавиться, у мене виникали проблеми із stopPropagation при використанні документа. Якщо ви приєднаєте прослуховувач подій до вікна, здається, вирішили цю проблему?
Тит

10
Як вже згадувалося, this.getDOMNode () застарілий. використовуйте ReactDOM замість цього: ReactDOM.findDOMNode (this) .contens (e.target)
Arne H. Bitubekk

17

Подивіться на ціль події, якщо подія була безпосередньо на компоненті або дочірні елементи цього компонента, тоді клацання було всередині. Інакше це було надворі.

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> 
        )
    }
});

Якщо це буде використано у багатьох компонентах, то з міксином буде приємніше:

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);
    },
}

Див. Приклад тут: https://jsfiddle.net/0Lshs7mg/1/


@ Mike'Pomax'Kamermans, яку було виправлено, я думаю, що ця відповідь додає корисну інформацію, можливо, ваш коментар тепер може бути видалено.
сі

Ви скасували мої зміни з неправильної причини. this.refs.componentпосилається на елемент DOM станом на 0,14 facebook.github.io/react/blog/2015/07/03/…
Гаджус

@GajusKuizinas - чудово зробити це, коли зміниться раз 0,14 - це останній випуск (тепер це бета-версія).
JA

Що таке долар?
pronebird

2
Я ненавиджу тикати jQuery з React, оскільки вони є двома фреймворками.
Абденнур ТУМІ

11

Для Вашого конкретного випадку використання, прийнята в даний час відповідь є трохи надмірно розробленою. Якщо ви хочете прослухати, коли користувач клацне зі спадного списку, просто використовуйте <select>компонент як батьківський елемент і приєднайте onBlurдо нього обробник.

Єдиними недоліками цього підходу є те, що він передбачає, що користувач вже зберіг фокус на елементі, і він покладається на контроль форми (який може бути чи не бути тим, що ви хочете, якщо взяти до уваги, що tabключ також фокусує і розмиває елементи ) - але ці недоліки насправді є лише межею для більш складних випадків використання, і в цьому випадку може знадобитися більш складне рішення.

 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 також працює з div, якщо він має tabIndexатрибут
gorpacrate

Для мене це було рішення, яке я знайшов найпростішим і найпростішим у використанні, я використовую його на кнопковому елементі і працює як шарм. Дякую!
Amir5000

5

Я написав загальний обробник подій для подій, що виникають поза компонентом, response-outside-event .

Сама реалізація проста:

  • Коли компонент змонтований, обробник події приєднується до windowоб'єкта.
  • Коли відбувається подія, компонент перевіряє, чи відбувається подія всередині компонента. Якщо цього не відбувається, це спрацьовує onOutsideEventна цільовому компоненті.
  • Коли компонент демонтується, обробник подій від'єднується.
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} />;
        }
    }
};

Щоб використовувати компонент, потрібно обернути декларацію класу цільового компонента, використовуючи компонент вищого порядку, та визначити події, які ви хочете обробити:

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

Я проголосував за одну з відповідей, хоча це не спрацювало для мене. Врешті-решт це привело мене до цього рішення. Я трохи змінив порядок операцій. Я слухаю mouseDown на цілі та mouseUp на цілі. Якщо хтось із цих поверне ІСТИНУ, ми не закриваємо модаль. Як тільки в будь-якому місці буде зареєстровано клацання, ці два логічні значення {mouseDownOnModal, mouseUpOnModal} повертаються у значення 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>
    );
}

Це має ту перевагу, що весь код покладається на дочірній компонент, а не на батьківський. Це означає, що при повторному використанні цього компонента не можна скопіювати шаблонний код.


3
  1. Створіть фіксований шар, який охоплює весь екран ( .backdrop).
  2. Розмістіть цільовий елемент ( .target) поза .backdropелементом та з більшим індексом стекування ( z-index).

Тоді будь-який клік на .backdropелементі буде вважатися "поза .targetелементом".

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

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

2

Ви можете використовувати refs для досягнення цього, щось на зразок наступного повинно працювати.

Додайте refдо свого елемента:

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

Потім можна додати функцію для обробки клацання поза елементом так:

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

Потім, нарешті, додайте та видаліть прослуховувачі подій, які будуть змонтовані та відключені.

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
}

Модифікована свою відповідь, він мав можливість помилки в засланні на виклик handleClickOutsideв document.addEventListener()шляхом додавання thisпосилання. В іншому випадку це дає Uncaught ReferenceError: handleClickOutside не визначено вcomponentWillMount()
Мухаммад Ханнан

1

Дуже пізно на вечірку, але я мав успіх із встановленням події розмиття на батьківському елементі випадаючого списку з відповідним кодом, щоб закрити випадаюче меню, а також підключення слухача мишею до батьківського елементу, який перевіряє, чи відкрито випадаюче меню чи ні, і зупинить розповсюдження події, якщо воно відкрите, щоб подія розмиття не спрацьовувала.

Оскільки подія "мишоуда" спливає, це не дозволить будь-якому мишоуданню на дітей спричинити розмиття на батьків.

/* 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 () не повинен викликатись, наскільки я можу міркувати, але firefox не працює без нього з будь-якої причини. Працює з Chrome, Firefox та Safari.


0

Я знайшов простіший спосіб про це.

Вам просто потрібно додати onHide(this.closeFunction)модальний

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

Припускаючи, що у вас є функція закрити модальний.


-1

Використовуйте чудовий суміш реакції на клік :

npm install --save react-onclickoutside

І потім

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

4
Зараз суміші FYI застаріли в React.
machineghost
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.