Cara mendengarkan peristiwa klik yang berada di luar komponen

89

Saya ingin menutup menu dropdown ketika klik terjadi di luar komponen dropdown.

Bagaimana aku melakukan itu?

Allan Hortle
sumber

Jawaban:

59

Dalam elemen yang telah saya tambahkan mousedowndan mouseupseperti ini:

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

Kemudian pada orang tua saya melakukan ini:

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

Ini showCaladalah boolean yang ketika truemenunjukkan dalam kasus saya kalender dan falsemenyembunyikannya.

Robbert van Elk
sumber
ini mengikat klik secara khusus ke mouse. Peristiwa klik dapat dihasilkan oleh peristiwa sentuh dan masukkan tombol juga, yang tidak dapat
ditanggapi oleh
@ Mike'Pomax'Kamermans Sekarang Anda dapat menggunakan onTouchStart dan onTouchEnd untuk seluler. facebook.github.io/react/docs/events.html#touch-events
naoufal
3
Itu sudah ada sejak lama, tetapi tidak akan berfungsi dengan baik dengan android, karena Anda perlu memanggil preventDefault()acara segera atau perilaku Android asli dimulai, yang mengganggu praproses React. Saya menulis npmjs.com/package/react-onclickoutside sejak itu.
Mike 'Pomax' Kamermans
Aku menyukainya! Dipuji. Menghapus pendengar acara di mousedown akan sangat membantu. componentWillUnmount = () => window.removeEventListener ('mousedown', this.pageClick, false);
Juni Brosas
73

Menggunakan metode siklus hidup menambah dan menghapus event listener ke dokumen.

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

Lihat baris 48-54 dari komponen ini: https://github.com/i-like-robots/react-tube-tracker/blob/91dc0129a1f6077bef57ea4ad9a860be0c600e9d/app/component/tube-tracker.jsx#L48-54

i_like_robots
sumber
Ini menambahkan satu ke dokumen, tetapi itu berarti bahwa kejadian klik pada komponen juga memicu kejadian dokumen. Hal ini menyebabkan masalahnya sendiri karena target pemroses dokumen selalu div rendah di akhir komponen, bukan komponen itu sendiri.
Allan Hortle
Saya tidak yakin saya mengikuti komentar Anda, tetapi jika Anda mengikat pendengar acara ke dokumen, Anda dapat memfilter peristiwa klik apa pun yang menggelembung ke atasnya, baik dengan mencocokkan pemilih (delegasi acara standar) atau dengan persyaratan arbiter lainnya (EG sebelumnya elemen target tidak dalam elemen lain).
i_like_robots
3
Ini memang menyebabkan masalah seperti yang ditunjukkan @AllanHortle. stopPropagation pada kejadian react tidak akan mencegah penangan kejadian dokumen menerima kejadian tersebut.
Matt Crinklaw-Vogt
4
Kepada siapa pun yang tertarik, saya mengalami masalah dengan stopPropagation saat menggunakan dokumen. Jika Anda melampirkan pemroses acara ke jendela, tampaknya masalah ini sudah diperbaiki?
Titus
10
Seperti yang disebutkan, getDOMNode () sudah usang. gunakan ReactDOM sebagai gantinya seperti ini: ReactDOM.findDOMNode (this) .contains (e.target)
Arne H. Bitubekk
17

Lihat target acara, jika acara langsung pada komponen, atau turunan dari komponen itu, klik ada di dalam. Kalau tidak, itu di luar.

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

Jika ini akan digunakan di banyak komponen, lebih bagus dengan mixin:

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

Lihat contoh di sini: https://jsfiddle.net/0Lshs7mg/1/

ja
sumber
@ Mike'Pomax'Kamermans yang telah diperbaiki, saya rasa jawaban ini menambah informasi yang berguna, mungkin komentar Anda sekarang dapat dihapus.
ja
Anda telah mengembalikan perubahan saya karena alasan yang salah. this.refs.componentmengacu pada elemen DOM pada 0.14 facebook.github.io/react/blog/2015/07/03/…
Gajus
@GajusKuizinas - baik untuk membuat perubahan itu setelah 0.14 adalah rilis terbaru (sekarang beta).
ja
Apa itu dolar?
pronebird
2
Saya benci menyodok jQuery dengan React karena mereka adalah kerangka kerja dua tampilan.
Abdennour TOUMI
11

Untuk kasus penggunaan khusus Anda, jawaban yang diterima saat ini agak berlebihan. Jika Anda ingin mendengarkan saat pengguna mengklik daftar drop-down, cukup gunakan <select>komponen sebagai elemen induk dan lampirkan onBlurpenangan padanya.

Satu-satunya kelemahan dari pendekatan ini adalah bahwa ia mengasumsikan pengguna telah mempertahankan fokus pada elemen, dan itu bergantung pada kontrol bentuk (yang mungkin atau mungkin bukan yang Anda inginkan jika Anda memperhitungkan bahwa tabkuncinya juga memfokuskan dan mengaburkan elemen ) - tetapi kekurangan ini hanya benar-benar batas untuk kasus penggunaan yang lebih rumit, dalam hal ini solusi yang lebih rumit mungkin diperlukan.

 var Dropdown = React.createClass({

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

   render: function() {
     return (
       <select onBlur={this.handleBlur}>
         ...
       </select>
     );
   }
 });
razorbeard.dll
sumber
10
onBlurdiv juga bekerja dengan div jika memiliki tabIndexatribut
gorpacrate
Bagi saya ini adalah solusi yang saya temukan paling sederhana dan termudah untuk digunakan, saya menggunakannya pada elemen tombol dan bekerja seperti pesona. Terima kasih!
Amir5000
5

Saya telah menulis event handler generik untuk event yang berasal dari luar komponen, react-outside-event .

Implementasinya sendiri sederhana:

  • Saat komponen dipasang, pengendali kejadian dipasang ke windowobjek.
  • Ketika suatu peristiwa terjadi, komponen memeriksa apakah peristiwa tersebut berasal dari dalam komponen. Jika tidak, maka akan terpicu onOutsideEventpada komponen target.
  • Ketika komponen dilepas, pengendali kejadian didetacth.
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} />;
        }
    }
};

Untuk menggunakan komponen, Anda perlu membungkus deklarasi kelas komponen target menggunakan komponen urutan yang lebih tinggi dan menentukan peristiwa yang ingin Anda tangani:

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']);
Gajus
sumber
4

Saya memilih salah satu jawaban meskipun itu tidak berhasil untuk saya. Itu akhirnya membawa saya ke solusi ini. Saya mengubah urutan operasi sedikit. Saya mendengarkan mouseDown pada target dan mouseUp pada target. Jika salah satu dari mereka mengembalikan TRUE, kami tidak menutup modal. Segera setelah klik terdaftar, di mana saja, kedua boolean itu {mouseDownOnModal, mouseUpOnModal} disetel kembali ke 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>
    );
}

Ini memiliki keuntungan bahwa semua kode terletak pada komponen anak, dan bukan induknya. Artinya tidak ada kode boilerplate untuk disalin saat menggunakan kembali komponen ini.

Steve Zelaznik
sumber
3
  1. Buat lapisan tetap yang membentang di seluruh layar ( .backdrop).
  2. Memiliki elemen target ( .target) di luar .backdropelemen dan dengan indeks susun yang lebih besar ( z-index).

Kemudian klik apa pun pada .backdropelemen tersebut akan dianggap "di luar .targetelemen".

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

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

Anda dapat menggunakan refs untuk mencapai ini, sesuatu seperti berikut ini akan bekerja.

Tambahkan refke elemen Anda:

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

Anda kemudian dapat menambahkan fungsi untuk menangani klik di luar elemen seperti:

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

Kemudian terakhir, tambahkan dan hapus event listener pada will mount dan akan unmount.

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
}
Chris Borrowdale
sumber
Modifikasi jawaban Anda, itu mungkin kesalahan dalam referensi untuk memanggil handleClickOutsidedi document.addEventListener()dengan menambahkan thisreferensi. Jika tidak, ia memberikan Uncaught ReferenceError: handleClickOutside tidak didefinisikan dalamcomponentWillMount()
Muhammad Hannan
1

Sangat terlambat ke pesta, tetapi saya telah berhasil menyetel acara buram pada elemen induk dari dropdown dengan kode terkait untuk menutup dropdown, dan juga melampirkan pendengar mousedown ke elemen induk yang memeriksa apakah dropdown terbuka atau tidak, dan akan menghentikan penyebaran acara jika terbuka sehingga acara blur tidak akan dipicu.

Karena peristiwa mousedown menggelembung, ini akan mencegah mousedown apa pun pada anak-anak yang menyebabkan buram pada induknya.

/* 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 () seharusnya tidak dipanggil sejauh yang saya bisa nalar tetapi firefox tidak berjalan baik tanpanya karena alasan apa pun. Berfungsi di Chrome, Firefox, dan Safari.

Tanner Stults
sumber
0

Saya menemukan cara yang lebih sederhana tentang ini.

Anda hanya perlu menambahkan onHide(this.closeFunction)modal

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

Dengan asumsi Anda memiliki fungsi untuk menutup modal.

MR Murazza
sumber
-1

Gunakan mixin react-onclickoutside yang sangat baik :

npm install --save react-onclickoutside

Lalu

var Component = React.createClass({
  mixins: [
    require('react-onclickoutside')
  ],
  handleClickOutside: function(evt) {
    // ...handling code goes here... 
  }
});
e18r
sumber
4
Campuran FYI sudah tidak digunakan lagi di React sekarang.
machineghost