วิธีฟังเหตุการณ์คลิกที่อยู่นอกคอมโพเนนต์


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
สิ่งเหล่านี้มีอยู่เป็นเวลานาน แต่จะเล่นได้ไม่ดีกับ Android เพราะคุณต้องโทรหาpreventDefault()เหตุการณ์ทันทีหรือพฤติกรรมดั้งเดิมของ Android เริ่มต้นซึ่งการประมวลผลล่วงหน้าของ React ขัดขวาง ฉันเขียนnpmjs.com/package/react-onclickoutsideตั้งแต่
Mike 'Pomax' Kamermans

ฉันรักมัน! ยกย่อง การลบตัวฟังเหตุการณ์ในมูสดาวน์จะเป็นประโยชน์ 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


สิ่งนี้จะเพิ่มหนึ่งรายการลงในเอกสาร แต่นั่นหมายความว่าเหตุการณ์การคลิกใด ๆ บนคอมโพเนนต์จะทริกเกอร์เหตุการณ์ในเอกสารด้วย สิ่งนี้ทำให้เกิดปัญหาขึ้นเองเนื่องจากเป้าหมายของผู้ฟังเอกสารมักจะเป็น div ที่ต่ำลงที่ส่วนท้ายของส่วนประกอบไม่ใช่ส่วนประกอบเอง
Allan Hortle

ฉันไม่ค่อยแน่ใจว่าฉันทำตามความคิดเห็นของคุณ แต่ถ้าคุณผูกผู้ฟังเหตุการณ์เข้ากับเอกสารคุณสามารถกรองเหตุการณ์การคลิกใด ๆ ที่เกิดขึ้นได้ไม่ว่าจะโดยการจับคู่ตัวเลือก (การมอบหมายเหตุการณ์มาตรฐาน) หรือตามข้อกำหนดของอนุญาโตตุลาการอื่น ๆ (เช่น องค์ประกอบเป้าหมายไม่ได้อยู่ในองค์ประกอบอื่น)
i_like_robots

3
สิ่งนี้ทำให้เกิดปัญหาตามที่ @AllanHortle ชี้ให้เห็น stopPropagation ในเหตุการณ์ react จะไม่ป้องกันไม่ให้ตัวจัดการเหตุการณ์เอกสารได้รับเหตุการณ์
Matt Crinklaw-Vogt

4
สำหรับทุกคนที่สนใจฉันมีปัญหาเกี่ยวกับ stopPropagation เมื่อใช้เอกสาร หากคุณแนบตัวฟังเหตุการณ์เข้ากับหน้าต่างดูเหมือนว่าจะแก้ไขปัญหานี้ได้หรือไม่?
Titus

10
ดังที่ได้กล่าวไว้นี้ getDOMNode () เป็นล้าสมัย ใช้ ReactDOM แทนดังนี้: ReactDOM.findDOMNode (this) .contains (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> 
        )
    }
});

ถ้าจะใช้ในหลาย ๆ ส่วนประกอบก็จะดีกว่าด้วย 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);
    },
}

ดูตัวอย่างที่นี่: https://jsfiddle.net/0Lshs7mg/1/


@ Mike'Pomax'Kamermans ที่ได้รับการแก้ไขแล้วฉันคิดว่าคำตอบนี้เพิ่มข้อมูลที่เป็นประโยชน์บางทีความคิดเห็นของคุณอาจถูกลบออกไปแล้ว
ja

คุณเปลี่ยนกลับการเปลี่ยนแปลงของฉันด้วยเหตุผลที่ไม่ถูกต้อง this.refs.componentหมายถึงองค์ประกอบ DOM ณ 0.14 facebook.github.io/react/blog/2015/07/03/…
Gajus

@GajusKuizinas - สามารถทำการเปลี่ยนแปลงได้เมื่อ 0.14 เป็นรุ่นล่าสุด (ตอนนี้เป็นเบต้า)
ja

ดอลลาร์คืออะไร?
pronebird

2
ฉันเกลียดการกระตุ้น jQuery ด้วย React เนื่องจากเป็นกรอบของสองมุมมอง
Abdennour TOUMI

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

ฉันได้เขียนตัวจัดการเหตุการณ์ทั่วไปสำหรับเหตุการณ์ที่เกิดขึ้นนอกองค์ประกอบการตอบสนองภายนอกเหตุการณ์ตอบสนองนอกเหตุการณ์

การใช้งานนั้นง่ายมาก:

  • เมื่อติดตั้งคอมโพเนนต์ตัวจัดการเหตุการณ์จะถูกแนบกับไฟล์ 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 บนเป้าหมาย ถ้าอย่างใดอย่างหนึ่งส่งคืน TRUE เราจะไม่ปิดโมดอล ทันทีที่มีการลงทะเบียนคลิกที่ใดก็ตามบูลีนสองตัวนั้น {mouseDownOnModal, mouseUpOnModal} จะถูกตั้งค่ากลับเป็นเท็จ

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

คุณสามารถใช้ ref s เพื่อบรรลุสิ่งนี้สิ่งต่อไปนี้ควรได้ผล

เพิ่ม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()
Muhammad Hannan

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.