Làm cách nào để phát hiện nhấp chuột bên ngoài một yếu tố?


2486

Tôi có một số menu HTML, mà tôi hiển thị hoàn toàn khi người dùng nhấp vào phần đầu của các menu này. Tôi muốn ẩn các yếu tố này khi người dùng nhấp bên ngoài khu vực menu.

Là một cái gì đó như thế này có thể với jQuery?

$("#menuscontainer").clickOutsideThisElement(function() {
    // Hide the menus
});

47
Đây là một ví dụ về chiến lược này: jsfiddle.net/tedp/aL7Xe/1
Ted

18
Như Tom đã đề cập, bạn sẽ muốn đọc css-tricks.com/dangers-stopping-event-propagation trước khi sử dụng phương pháp này. Công cụ jsfiddle đó là khá mát mẻ mặc dù.
Jon Coombs

3
có được một tham chiếu đến phần tử và sau đó là event.target và cuối cùng! = hoặc == cả hai sau đó thực thi mã cho phù hợp ..
Rohit Kumar

Tôi khuyên bạn nên sử dụng github.com/gsantiago/jquery-clickout :)
Rômulo M. Farias

2
Giải pháp Vanilla JSevent.targetkhông có event.stopPropagation .
lowTechsun

Câu trả lời:


1812

LƯU Ý: Sử dụng stopEventPropagation()là điều nên tránh vì nó phá vỡ luồng sự kiện thông thường trong DOM. Xem bài viết này để biết thêm thông tin. Thay vào đó hãy xem xét sử dụng phương pháp này

Đính kèm một sự kiện nhấp chuột vào phần thân tài liệu sẽ đóng cửa sổ. Đính kèm một sự kiện bấm riêng vào thùng chứa dừng lan truyền đến thân tài liệu.

$(window).click(function() {
//Hide the menus if visible
});

$('#menucontainer').click(function(event){
    event.stopPropagation();
});

708
Điều này phá vỡ hành vi tiêu chuẩn của nhiều thứ, bao gồm các nút và liên kết, có trong #menucontainer. Tôi ngạc nhiên câu trả lời này là rất phổ biến.
Nghệ thuật

75
Điều này không phá vỡ hành vi của bất cứ thứ gì bên trong #menucontainer, vì nó ở dưới cùng của chuỗi truyền bá cho bất cứ thứ gì bên trong nó.
Eran Galperin

94
Nó rất đẹp nhưng bạn không nên sử dụng $('html').click()cơ thể. Cơ thể luôn có chiều cao của nội dung của nó. Nó không có nhiều nội dung hoặc màn hình rất cao, nó chỉ hoạt động trên phần được lấp đầy bởi cơ thể.
meo

103
Tôi cũng ngạc nhiên khi giải pháp này nhận được rất nhiều phiếu bầu. Điều này sẽ thất bại đối với bất kỳ phần tử nào bên ngoài có stopPropagation jsfiddle.net/Flandre/vaNFw/3
Andre

140
Philip Walton giải thích rất rõ lý do tại sao câu trả lời này không phải là giải pháp tốt nhất: css-tricks.com/dangers-stopping-event-propagation
Tom

1386

Bạn có thể lắng nghe một sự kiện nhấp chuộtdocument và sau đó đảm bảo #menucontainerkhông phải là tổ tiên hoặc mục tiêu của phần tử được nhấp bằng cách sử dụng .closest().

Nếu không, phần tử được nhấp nằm ngoài #menucontainervà bạn có thể ẩn nó một cách an toàn.

$(document).click(function(event) { 
  $target = $(event.target);
  if(!$target.closest('#menucontainer').length && 
  $('#menucontainer').is(":visible")) {
    $('#menucontainer').hide();
  }        
});

Chỉnh sửa - 2017-06-23

Bạn cũng có thể dọn dẹp sau khi nghe sự kiện nếu bạn có kế hoạch bỏ menu và muốn ngừng nghe các sự kiện. Chức năng này sẽ chỉ dọn sạch trình nghe mới được tạo, duy trì mọi trình nghe nhấp chuột khác document. Với cú pháp ES2015:

export function hideOnClickOutside(selector) {
  const outsideClickListener = (event) => {
    $target = $(event.target);
    if (!$target.closest(selector).length && $(selector).is(':visible')) {
        $(selector).hide();
        removeClickListener();
    }
  }

  const removeClickListener = () => {
    document.removeEventListener('click', outsideClickListener)
  }

  document.addEventListener('click', outsideClickListener)
}

Chỉnh sửa - 2018-03-11

Đối với những người không muốn sử dụng jQuery. Đây là đoạn mã trên trong vanillaJS đơn giản (ECMAScript6).

function hideOnClickOutside(element) {
    const outsideClickListener = event => {
        if (!element.contains(event.target) && isVisible(element)) { // or use: event.target.closest(selector) === null
          element.style.display = 'none'
          removeClickListener()
        }
    }

    const removeClickListener = () => {
        document.removeEventListener('click', outsideClickListener)
    }

    document.addEventListener('click', outsideClickListener)
}

const isVisible = elem => !!elem && !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ) // source (2018-03-11): https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js 

LƯU Ý: Điều này dựa trên nhận xét của Alex để chỉ sử dụng !element.contains(event.target)thay vì phần jQuery.

Nhưng element.closest()hiện tại cũng có sẵn trong tất cả các trình duyệt chính (phiên bản W3C khác một chút so với jQuery). Polyfills có thể được tìm thấy ở đây: Element.closest ()

Chỉnh sửa - 2020-05-21

Trong trường hợp bạn muốn người dùng có thể nhấp và kéo bên trong phần tử, sau đó thả chuột ra bên ngoài phần tử, mà không đóng phần tử:

      ...
      let lastMouseDownX = 0;
      let lastMouseDownY = 0;
      let lastMouseDownWasOutside = false;

      const mouseDownListener = (event: MouseEvent) => {
        lastMouseDownX = event.offsetX
        lastMouseDownY = event.offsetY
        lastMouseDownWasOutside = !$(event.target).closest(element).length
      }
      document.addEventListener('mousedown', mouseDownListener);

Và trong outsideClickListener:

const outsideClickListener = event => {
        const deltaX = event.offsetX - lastMouseDownX
        const deltaY = event.offsetY - lastMouseDownY
        const distSq = (deltaX * deltaX) + (deltaY * deltaY)
        const isDrag = distSq > 3
        const isDragException = isDrag && !lastMouseDownWasOutside

        if (!element.contains(event.target) && isVisible(element) && !isDragException) { // or use: event.target.closest(selector) === null
          element.style.display = 'none'
          removeClickListener()
          document.removeEventListener('mousedown', mouseDownListener); // Or add this line to removeClickListener()
        }
    }

28
Tôi đã thử nhiều câu trả lời khác, nhưng chỉ có câu trả lời này có hiệu quả. Cảm ơn. Mã tôi đã sử dụng là: $ (tài liệu) .click (hàm (sự kiện) {if ($ (event.target) .closest ('. Window'). Length == 0) {$ ('. Window' ) .fadeOut ('nhanh');}});
Pistos

39
Tôi thực sự đã kết thúc với giải pháp này vì nó hỗ trợ tốt hơn cho nhiều menu trên cùng một trang trong đó nhấp vào menu thứ hai trong khi mở đầu tiên sẽ để mở đầu tiên trong giải pháp stopPropagation.
umassthrower

13
Câu trả lời tuyệt vời. Đây là cách để đi khi bạn có nhiều mục mà bạn muốn đóng.
Giăng

5
Đây phải là câu trả lời được chấp nhận vì lỗ hổng mà giải pháp khác có với event.stopPropagation ().
cà chua

20
Không có jQuery - !element.contains(event.target)sử dụng Node.contains ()
Alex Ross

304

Làm thế nào để phát hiện một nhấp chuột bên ngoài một yếu tố?

Lý do mà câu hỏi này rất phổ biến và có rất nhiều câu trả lời là nó phức tạp. Sau gần tám năm và hàng tá câu trả lời, tôi thực sự ngạc nhiên khi thấy sự chăm sóc ít được dành cho khả năng tiếp cận.

Tôi muốn ẩn các yếu tố này khi người dùng nhấp bên ngoài khu vực menu.

Đây là một nguyên nhân cao quý và là vấn đề thực tế . Tiêu đề của câu hỏi, đó là những gì mà hầu hết các câu trả lời xuất hiện để cố gắng giải quyết vấn đề này có chứa một cá trích đỏ không may.

Gợi ý: đó là từ "nhấp chuột" !

Bạn không thực sự muốn ràng buộc xử lý nhấp chuột.

Nếu bạn ràng buộc bấm trình xử lý để đóng hộp thoại, bạn đã thất bại. Lý do bạn thất bại là không phải ai cũng kích hoạt clickcác sự kiện. Người dùng không sử dụng chuột sẽ có thể thoát khỏi hộp thoại của bạn (và menu bật lên của bạn được cho là một loại hộp thoại) bằng cách nhấn Tabvà sau đó họ sẽ không thể đọc nội dung đằng sau hộp thoại mà không kích hoạt clicksự kiện.

Vì vậy, hãy viết lại câu hỏi.

Làm thế nào để đóng một hộp thoại khi người dùng kết thúc với nó?

Đây là mục tiêu. Thật không may, bây giờ chúng ta cần liên kết userisfinishedwiththedialogsự kiện và ràng buộc đó không đơn giản như vậy.

Vậy làm thế nào chúng ta có thể phát hiện ra rằng người dùng đã kết thúc bằng hộp thoại?

focusout biến cố

Một khởi đầu tốt là xác định xem tiêu điểm có rời khỏi hộp thoại không.

Gợi ý: hãy cẩn thận với blursự kiện, blurkhông tuyên truyền nếu sự kiện bị ràng buộc với giai đoạn sủi bọt!

jQuery focusoutsẽ làm tốt. Nếu bạn không thể sử dụng jQuery, thì bạn có thể sử dụng blurtrong giai đoạn chụp:

element.addEventListener('blur', ..., true);
//                       use capture: ^^^^

Ngoài ra, đối với nhiều hộp thoại, bạn sẽ cần cho phép vùng chứa lấy nét. Thêm tabindex="-1"để cho phép hộp thoại nhận tiêu điểm động mà không làm gián đoạn luồng tab.

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on('focusout', function () {
  $(this).removeClass('active');
});
div {
  display: none;
}
.active {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
  Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>


Nếu bạn chơi với bản demo đó trong hơn một phút, bạn sẽ nhanh chóng bắt đầu thấy các vấn đề.

Đầu tiên là liên kết trong hộp thoại không thể nhấp được. Cố gắng nhấp vào nó hoặc tab vào nó sẽ dẫn đến việc đóng hộp thoại trước khi tương tác diễn ra. Điều này là do tập trung vào phần tử bên trong sẽ kích hoạt một focusoutsự kiện trước khi kích hoạt lại một focusinsự kiện.

Cách khắc phục là xếp hàng thay đổi trạng thái trên vòng lặp sự kiện. Điều này có thể được thực hiện bằng cách sử dụng setImmediate(...)hoặc setTimeout(..., 0)cho các trình duyệt không hỗ trợ setImmediate. Sau khi xếp hàng, nó có thể bị hủy bởi sau đó focusin:

$('.submenu').on({
  focusout: function (e) {
    $(this).data('submenuTimer', setTimeout(function () {
      $(this).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function (e) {
    clearTimeout($(this).data('submenuTimer'));
  }
});

Vấn đề thứ hai là hộp thoại sẽ không đóng khi liên kết được nhấn lại. Điều này là do hộp thoại mất tiêu điểm, kích hoạt hành vi đóng, sau đó nhấp vào liên kết sẽ kích hoạt hộp thoại để mở lại.

Tương tự như vấn đề trước, trạng thái tập trung cần phải được quản lý. Cho rằng thay đổi trạng thái đã được xếp hàng, đó chỉ là vấn đề xử lý các sự kiện tập trung vào trình kích hoạt hộp thoại:

Cái này nhìn quen quá
$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});


Esc Chìa khóa

Nếu bạn nghĩ rằng bạn đã hoàn thành bằng cách xử lý các trạng thái tiêu điểm, bạn sẽ có thể làm nhiều hơn để đơn giản hóa trải nghiệm người dùng.

Đây thường là một tính năng "tốt để có", nhưng thông thường là khi bạn có một chế độ hoặc cửa sổ bật lên của bất kỳ loại nào mà Esckhóa sẽ đóng nó.

keydown: function (e) {
  if (e.which === 27) {
    $(this).removeClass('active');
    e.preventDefault();
  }
}


Nếu bạn biết bạn có các yếu tố có thể tập trung trong hộp thoại, bạn sẽ không cần phải tập trung trực tiếp vào hộp thoại. Nếu bạn đang xây dựng một menu, bạn có thể tập trung vào mục menu đầu tiên.

click: function (e) {
  $(this.hash)
    .toggleClass('submenu--active')
    .find('a:first')
    .focus();
  e.preventDefault();
}


Vai trò WAI-ARIA và hỗ trợ tiếp cận khác

Câu trả lời này hy vọng bao gồm những điều cơ bản về hỗ trợ bàn phím và chuột có thể truy cập được cho tính năng này, nhưng vì nó khá dễ hiểu nên tôi sẽ tránh mọi cuộc thảo luận về vai trò và thuộc tính của WAI-ARIA , tuy nhiên tôi rất khuyến nghị người thực hiện tham khảo thông số kỹ thuật để biết chi tiết về vai trò nào họ nên sử dụng và bất kỳ thuộc tính thích hợp nào khác.


29
Đây là câu trả lời đầy đủ nhất, với lời giải thích và khả năng tiếp cận trong tâm trí. Tôi nghĩ rằng đây phải là câu trả lời được chấp nhận vì hầu hết các câu trả lời khác chỉ xử lý nhấp chuột và chỉ là đoạn mã bị bỏ mà không có bất kỳ lời giải thích nào.
Cyrille

4
Tuyệt vời, giải thích tốt. Tôi vừa sử dụng phương pháp này trên Thành phần Phản ứng và hoạt động hoàn hảo nhờ
David Lavieri

2
@zzzzBov cảm ơn vì câu trả lời sâu sắc, tôi đang cố gắng đạt được nó trong vanilla JS, và tôi hơi lạc lõng với tất cả những thứ vớ vẩn. Có điều gì tương tự trong vanilla js?
HendrikEng

2
@zzzzBov không, tôi không tìm kiếm bạn để viết phiên bản không có jQuery, tôi sẽ cố gắng làm điều đó và tôi đoán tốt nhất là hỏi một câu hỏi mới ở đây nếu tôi thực sự gặp khó khăn. Cảm ơn rất nhiều lần nữa.
HendrikEng

7
Mặc dù đây là một phương pháp tuyệt vời để phát hiện nhấp vào thả xuống tùy chỉnh hoặc các đầu vào khác, nhưng nó không bao giờ nên là phương pháp ưa thích cho các phương thức hoặc cửa sổ bật lên vì hai lý do. Phương thức sẽ đóng khi người dùng chuyển sang tab hoặc cửa sổ khác hoặc mở menu ngữ cảnh, điều này thực sự gây phiền nhiễu. Ngoài ra, sự kiện 'nhấp chuột' sẽ kích hoạt chuột lên, trong khi sự kiện 'tiêu điểm' sẽ kích hoạt ngay khi bạn nhấn chuột xuống. Thông thường, một nút chỉ thực hiện một hành động nếu bạn nhấn nút chuột xuống và sau đó buông tay. Cách thích hợp, dễ tiếp cận để làm điều này cho các phương thức là thêm một nút đóng có thể lập bảng.
Kevin

144

Các giải pháp khác ở đây không hiệu quả với tôi nên tôi phải sử dụng:

if(!$(event.target).is('#foo'))
{
    // hide menu
}

Tôi đã đăng một ví dụ thực tế khác về cách sử dụng event.target để tránh kích hoạt tiện ích UI Jquery khác bên ngoài nhấp vào trình xử lý html khi nhúng chúng vào hộp bật lên của riêng bạn: Cách tốt nhất để có được Target Target
Joey T

43
Điều này làm việc cho tôi, ngoại trừ tôi đã thêm vào && !$(event.target).parents("#foo").is("#foo")trong IFcâu lệnh để bất kỳ phần tử con nào sẽ không đóng menu khi nhấp vào.
honyovk

1
Không thể tìm thấy tốt hơn: // Chúng tôi ở ngoài $ (event.target) .parents ('# foo'). Length == 0
AlexG

3
Cải tiến ngắn gọn để xử lý lồng sâu là sử dụng .is('#foo, #foo *'), nhưng tôi không khuyên bạn nên xử lý nhấp chuột ràng buộc để giải quyết vấn đề này .
zzzzBov

1
!$(event.target).closest("#foo").lengthsẽ tốt hơn và làm giảm nhu cầu bổ sung của @ honyovk.
tvanc

127

Tôi có một ứng dụng hoạt động tương tự như ví dụ của Eran, ngoại trừ tôi đính kèm sự kiện nhấp chuột vào phần thân khi tôi mở menu ... Loại như thế này:

$('#menucontainer').click(function(event) {
  $('html').one('click',function() {
    // Hide the menus
  });

  event.stopPropagation();
});

Thêm thông tin về chức năng của jQueryone()


9
nhưng sau đó nếu bạn nhấp vào menu, sau đó ở bên ngoài, nó sẽ không hoạt động :)
vsync

3
Nó giúp đặt event.stopProgagantion () trước khi ràng buộc người nghe nhấp vào cơ thể.
Jasper Kennis

4
Vấn đề với điều này là "một" áp dụng cho phương thức jQuery thêm các sự kiện vào một mảng nhiều lần. Vì vậy, nếu bạn nhấp vào menu để mở nó nhiều lần, sự kiện sẽ bị ràng buộc lại với cơ thể và cố gắng ẩn menu nhiều lần. Một lỗi an toàn nên được áp dụng để khắc phục vấn đề này.
marksyzm

Sau khi ràng buộc sử dụng .one- bên trong $('html')trình xử lý - viết a $('html').off('click').
Cody

4
@Cody Tôi không nghĩ rằng nó giúp. Trình onexử lý sẽ tự động gọi off(như được hiển thị trong các tài liệu jQuery).
Mariano Desanze

44

Sau khi nghiên cứu tôi đã tìm thấy ba giải pháp làm việc (tôi quên các liên kết trang để tham khảo)

Giải pháp đầu tiên

<script>
    //The good thing about this solution is it doesn't stop event propagation.

    var clickFlag = 0;
    $('body').on('click', function () {
        if(clickFlag == 0) {
            console.log('hide element here');
            /* Hide element here */
        }
        else {
            clickFlag=0;
        }
    });
    $('body').on('click','#testDiv', function (event) {
        clickFlag = 1;
        console.log('showed the element');
        /* Show the element */
    });
</script>

Giải pháp thứ hai

<script>
    $('body').on('click', function(e) {
        if($(e.target).closest('#testDiv').length == 0) {
           /* Hide dropdown here */
        }
    });
</script>

Giải pháp thứ ba

<script>
    var specifiedElement = document.getElementById('testDiv');
    document.addEventListener('click', function(event) {
        var isClickInside = specifiedElement.contains(event.target);
        if (isClickInside) {
          console.log('You clicked inside')
        }
        else {
          console.log('You clicked outside')
        }
    });
</script>

8
Giải pháp thứ ba là cách kiểm tra thanh lịch nhất. Nó cũng không liên quan đến bất kỳ chi phí nào của jQuery. Rất đẹp. Nó đã giúp rất nhiều. Cảm ơn.
dbarth

Tôi đang cố gắng để giải pháp thứ ba diễn ra với nhiều yếu tố document.getElementsByClassName, nếu ai đó có manh mối xin vui lòng chia sẻ.
lowTechsun

@lowtechsun Bạn phải lặp lại để kiểm tra từng cái.
Donnie D'Amato

Thực sự thích giải pháp thứ ba, tuy nhiên các kích hoạt nhấp chuột trước khi div của tôi đã bắt đầu hiển thị cái nào ẩn nó một lần nữa, có ý tưởng nào không?
indiehjaerta

Cái thứ ba cho phép console.log, chắc chắn, nhưng nó không cho phép bạn thực sự đóng bằng cách đặt hiển thị thành không - bởi vì nó sẽ làm điều này trước khi menu được hiển thị. Bạn có một giải pháp cho việc này?
RolandiXor

40
$("#menuscontainer").click(function() {
    $(this).focus();
});
$("#menuscontainer").blur(function(){
    $(this).hide();
});

Làm việc cho tôi tốt


3
Đây là cái tôi sẽ sử dụng. Nó có thể không hoàn hảo, nhưng là một lập trình viên sở thích, nó đủ đơn giản để hiểu rõ ràng.
kevtrout

Mờ là một động thái bên ngoài #menucontainercâu hỏi là về một nhấp chuột
borrel

4
@borrel làm mờ không phải là một di chuyển bên ngoài container. Blur là đối lập với trọng tâm, bạn đang nghĩ đến mouseout. Giải pháp này hoạt động đặc biệt tốt đối với tôi khi tôi đang tạo văn bản "nhấp để chỉnh sửa" trong đó tôi hoán đổi qua lại giữa văn bản thuần và trường nhập liệu khi nhấp.
parker.sikand

Tôi đã phải thêm tabindex="-1"vào #menuscontainerđể làm cho nó hoạt động. Có vẻ như nếu bạn đặt một thẻ đầu vào bên trong container và nhấp vào nó, container sẽ bị ẩn.
bạo chúa

sự kiện mouseleave phù hợp hơn cho thực đơn và hộp đựng (ref: w3schools.com/jquery/
mẹo

38

Bây giờ có một plugin cho điều đó: các sự kiện bên ngoài ( bài đăng trên blog )

Sau đây sẽ xảy ra khi một clickoutside handler (wlog) được liên kết với một yếu tố:

  • phần tử được thêm vào một mảng chứa tất cả các phần tử với trình xử lý clickoutside
  • một trình xử lý nhấp chuột (được đặt tên ) được liên kết với tài liệu (nếu chưa có)
  • trên bất kỳ nhấp chuột nào trong tài liệu, sự kiện clickout được kích hoạt cho các thành phần đó trong mảng đó không bằng hoặc là cha mẹ của mục tiêu nhấp chuột
  • ngoài ra, event.target cho sự kiện clickoutide được đặt thành phần tử mà người dùng đã nhấp vào (để bạn thậm chí biết người dùng đã nhấp vào cái gì, không chỉ là anh ta nhấp vào bên ngoài)

Vì vậy, không có sự kiện nào được dừng từ việc truyền bá và các trình xử lý nhấp chuột bổ sung có thể được sử dụng "bên trên" phần tử với trình xử lý bên ngoài.


Plugin đẹp. Liên kết trên "các sự kiện bên ngoài" đã chết, trong khi liên kết bài đăng trên blog vẫn còn tồn tại và cung cấp một plugin rất hữu ích cho các sự kiện loại "clickoutside". Nó cũng được MIT cấp phép.
TechNyquist

Plugin tuyệt vời. Làm việc hoàn hảo. Cách sử dụng là như vậy:$( '#element' ).on( 'clickoutside', function( e ) { .. } );
Gavin

33

Điều này làm việc cho tôi hoàn hảo !!

$('html').click(function (e) {
    if (e.target.id == 'YOUR-DIV-ID') {
        //do something
    } else {
        //do something
    }
});

Giải pháp này hoạt động tốt cho những gì tôi đang làm, nơi tôi vẫn cần sự kiện nhấp để nổi bong bóng, cảm ơn.
Mike

26

Tôi không nghĩ những gì bạn thực sự cần là đóng menu khi người dùng nhấp vào bên ngoài; những gì bạn cần là để menu đóng khi người dùng nhấp vào bất cứ nơi nào trên trang. Nếu bạn bấm vào menu, hoặc tắt menu thì nó sẽ đóng phải không?

Tìm thấy không có câu trả lời thỏa đáng ở trên đã thôi thúc tôi viết bài đăng blog này vào ngày khác. Đối với nhiều phạm vi hơn, có một số vấn đề cần lưu ý:

  1. Nếu bạn đính kèm một trình xử lý sự kiện nhấp vào phần tử cơ thể tại thời điểm nhấp, hãy chắc chắn đợi lần nhấp thứ 2 trước khi đóng menu và hủy liên kết sự kiện. Nếu không, sự kiện nhấp đã mở menu sẽ tạo ra bong bóng cho người nghe phải đóng menu.
  2. Nếu bạn sử dụng event.stopPropogation () trên một sự kiện nhấp chuột, không có yếu tố nào khác trong trang của bạn có thể có tính năng nhấp vào bất cứ nơi nào để đóng.
  3. Đính kèm một trình xử lý sự kiện nhấp vào phần tử cơ thể vô thời hạn không phải là một giải pháp biểu diễn
  4. So sánh mục tiêu của sự kiện và cha mẹ của nó với người tạo của trình xử lý giả định rằng điều bạn muốn là đóng menu khi bạn nhấp vào nó, khi điều bạn thực sự muốn là đóng nó khi bạn nhấp vào bất cứ đâu trên trang.
  5. Lắng nghe các sự kiện trên phần tử cơ thể sẽ làm cho mã của bạn dễ vỡ hơn. Kiểu dáng ngây thơ như thế này sẽ phá vỡ nó:body { margin-left:auto; margin-right: auto; width:960px;}

2
"Nếu bạn nhấp vào menu, hoặc tắt menu thì nó sẽ đóng phải không?" không phải lúc nào Hủy bỏ một nhấp chuột bằng cách kéo ra một yếu tố vẫn sẽ kích hoạt một nhấp chuột cấp độ tài liệu, nhưng mục đích sẽ không là tiếp tục đóng menu. Ngoài ra còn có rất nhiều loại hộp thoại khác có thể sử dụng hành vi "nhấp ra" cho phép nhấp vào bên trong.
zzzzBov

25

Như một poster khác cho biết có rất nhiều vấn đề, đặc biệt là nếu yếu tố bạn đang hiển thị (trong trường hợp này là menu) có các yếu tố tương tác. Tôi đã tìm thấy phương pháp sau khá mạnh mẽ:

$('#menuscontainer').click(function(event) {
    //your code that shows the menus fully

    //now set up an event listener so that clicking anywhere outside will close the menu
    $('html').click(function(event) {
        //check up the tree of the click target to check whether user has clicked outside of menu
        if ($(event.target).parents('#menuscontainer').length==0) {
            // your code to hide menu

            //this event listener has done its job so we can unbind it.
            $(this).unbind(event);
        }

    })
});

25

Một giải pháp đơn giản cho tình huống này là:

$(document).mouseup(function (e)
{
    var container = $("YOUR SELECTOR"); // Give you class or ID

    if (!container.is(e.target) &&            // If the target of the click is not the desired div or section
        container.has(e.target).length === 0) // ... nor a descendant-child of the container
    {
        container.hide();
    }
});

Kịch bản trên sẽ ẩn divnếu bên ngoài divsự kiện nhấp được kích hoạt.

Bạn có thể xem blog sau để biết thêm thông tin: http://www.codecanal.com/detect-click-outside-div-USE-javascript/


22

Giải pháp1

Thay vì sử dụng event.stopPropagation () có thể có một số tác dụng phụ, chỉ cần xác định một biến cờ đơn giản và thêm một ifđiều kiện. Tôi đã thử nghiệm điều này và làm việc đúng cách mà không có bất kỳ tác động phụ nào của stopPropagation:

var flag = "1";
$('#menucontainer').click(function(event){
    flag = "0"; // flag 0 means click happened in the area where we should not do any action
});

$('html').click(function() {
    if(flag != "0"){
        // Hide the menus if visible
    }
    else {
        flag = "1";
    }
});

Giải pháp2

Chỉ với một ifđiều kiện đơn giản :

$(document).on('click', function(event){
    var container = $("#menucontainer");
    if (!container.is(event.target) &&            // If the target of the click isn't the container...
        container.has(event.target).length === 0) // ... nor a descendant of the container
    {
        // Do whatever you want to do when click is outside the element
    }
});

Tôi đã sử dụng giải pháp này với cờ boolean và nó cũng tốt với DOm được khớp nối và nếu bên trong #menucontainer có rất nhiều yếu tố khác
Migio B

Giải pháp 1 hoạt động tốt hơn, bởi vì nó xử lý các trường hợp khi mục tiêu nhấp được xóa khỏi DOM theo thời gian sự kiện lan truyền đến tài liệu.
Alice

21

Kiểm tra mục tiêu sự kiện nhấp vào cửa sổ (nó sẽ truyền đến cửa sổ, miễn là nó không bị bắt ở bất kỳ nơi nào khác) và đảm bảo rằng đó không phải là bất kỳ thành phần nào trong menu. Nếu không, thì bạn ở ngoài menu của bạn.

Hoặc kiểm tra vị trí của nhấp chuột và xem nó có trong khu vực menu không.


18

Tôi đã thành công với những thứ như thế này:

var $menuscontainer = ...;

$('#trigger').click(function() {
  $menuscontainer.show();

  $('body').click(function(event) {
    var $target = $(event.target);

    if ($target.parents('#menuscontainer').length == 0) {
      $menuscontainer.hide();
    }
  });
});

Logic là: khi #menuscontainerđược hiển thị, liên kết một trình xử lý nhấp chuột vào phần thân #menuscontainerchỉ ẩn nếu mục tiêu (của nhấp chuột) không phải là con của nó.


17

Là một biến thể:

var $menu = $('#menucontainer');
$(document).on('click', function (e) {

    // If element is opened and click target is outside it, hide it
    if ($menu.is(':visible') && !$menu.is(e.target) && !$menu.has(e.target).length) {
        $menu.hide();
    }
});

Không có vấn đề gì với việc dừng lan truyền sự kiện và hỗ trợ tốt hơn cho nhiều menu trên cùng một trang trong đó nhấp vào menu thứ hai trong khi mở đầu tiên sẽ để mở đầu tiên trong giải pháp stopPropagation.


16

Sự kiện này có một thuộc tính được gọi là event.path của phần tử là "danh sách thứ tự tĩnh của tất cả tổ tiên của nó theo thứ tự cây" . Để kiểm tra xem một sự kiện có nguồn gốc từ một phần tử DOM cụ thể hoặc một trong những phần tử con của nó, chỉ cần kiểm tra đường dẫn cho phần tử DOM cụ thể đó. Nó cũng có thể được sử dụng để kiểm tra nhiều phần tử bằng cách sử dụng logic ORkiểm tra phần tử trong somehàm.

$("body").click(function() {
  target = document.getElementById("main");
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  })
  if (flag) {
    console.log("Inside")
  } else {
    console.log("Outside")
  }
});
#main {
  display: inline-block;
  background:yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="main">
  <ul>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
  </ul>
</div>
<div id="main2">
  Outside Main
</div>

Vì vậy, đối với trường hợp của bạn.

$("body").click(function() {
  target = $("#menuscontainer")[0];
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  });
  if (!flag) {
    // Hide the menus
  }
});

1
Không hoạt động trong Edge.
Huyền thoại

event.pathkhông phải là một điều.
Andrew

14

Tôi tìm thấy phương pháp này trong một số plugin lịch jQuery.

function ClickOutsideCheck(e)
{
  var el = e.target;
  var popup = $('.popup:visible')[0];
  if (popup==undefined)
    return true;

  while (true){
    if (el == popup ) {
      return true;
    } else if (el == document) {
      $(".popup").hide();
      return false;
    } else {
      el = $(el).parent()[0];
    }
  }
};

$(document).bind('mousedown.popup', ClickOutsideCheck);

13

Đây là giải pháp JavaScript vanilla cho người xem trong tương lai.

Khi nhấp vào bất kỳ phần tử nào trong tài liệu, nếu id của phần tử được nhấp được bật hoặc phần tử ẩn không bị ẩn và phần tử ẩn không chứa phần tử được nhấp, hãy chuyển phần tử.

(function () {
    "use strict";
    var hidden = document.getElementById('hidden');
    document.addEventListener('click', function (e) {
        if (e.target.id == 'toggle' || (hidden.style.display != 'none' && !hidden.contains(e.target))) hidden.style.display = hidden.style.display == 'none' ? 'block' : 'none';
    }, false);
})();

Nếu bạn sắp có nhiều toggles trên cùng một trang, bạn có thể sử dụng cái gì đó như thế này:

  1. Thêm tên lớp hiddenvào mục đóng mở.
  2. Khi nhấp vào tài liệu, hãy đóng tất cả các phần tử ẩn không chứa phần tử được nhấp và không bị ẩn
  3. Nếu phần tử được nhấp là chuyển đổi, hãy chuyển phần tử đã chỉ định.

(function () {
    "use strict";
    var hiddenItems = document.getElementsByClassName('hidden'), hidden;
    document.addEventListener('click', function (e) {
        for (var i = 0; hidden = hiddenItems[i]; i++) {
            if (!hidden.contains(e.target) && hidden.style.display != 'none')
                hidden.style.display = 'none';
        }
        if (e.target.getAttribute('data-toggle')) {
            var toggle = document.querySelector(e.target.getAttribute('data-toggle'));
            toggle.style.display = toggle.style.display == 'none' ? 'block' : 'none';
        }
    }, false);
})();
<a href="javascript:void(0)" data-toggle="#hidden1">Toggle Hidden Div</a>
<div class="hidden" id="hidden1" style="display: none;" data-hidden="true">This content is normally hidden</div>
<a href="javascript:void(0)" data-toggle="#hidden2">Toggle Hidden Div</a>
<div class="hidden" id="hidden2" style="display: none;" data-hidden="true">This content is normally hidden</div>
<a href="javascript:void(0)" data-toggle="#hidden3">Toggle Hidden Div</a>
<div class="hidden" id="hidden3" style="display: none;" data-hidden="true">This content is normally hidden</div>


13

Tôi ngạc nhiên không ai thực sự thừa nhận focusoutsự kiện:

var button = document.getElementById('button');
button.addEventListener('click', function(e){
  e.target.style.backgroundColor = 'green';
});
button.addEventListener('focusout', function(e){
  e.target.style.backgroundColor = '';
});
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
</head>
<body>
  <button id="button">Click</button>
</body>
</html>


Bạn thiếu câu trả lời của tôi, tôi nghĩ. stackoverflow.com/a/47755925/6478359
Muhammet có thể TONBUL

Đây phải là câu trả lời được chấp nhận, đi thẳng vào vấn đề. Cảm ơn!!!
May mắn

8

Nếu bạn đang viết kịch bản cho IE và FF 3. * và bạn chỉ muốn biết liệu nhấp chuột có xảy ra trong một khu vực hộp nhất định hay không, bạn cũng có thể sử dụng một cái gì đó như:

this.outsideElementClick = function(objEvent, objElement){   
var objCurrentElement = objEvent.target || objEvent.srcElement;
var blnInsideX = false;
var blnInsideY = false;

if (objCurrentElement.getBoundingClientRect().left >= objElement.getBoundingClientRect().left && objCurrentElement.getBoundingClientRect().right <= objElement.getBoundingClientRect().right)
    blnInsideX = true;

if (objCurrentElement.getBoundingClientRect().top >= objElement.getBoundingClientRect().top && objCurrentElement.getBoundingClientRect().bottom <= objElement.getBoundingClientRect().bottom)
    blnInsideY = true;

if (blnInsideX && blnInsideY)
    return false;
else
    return true;}

8

Thay vì sử dụng gián đoạn dòng chảy, sự kiện làm mờ / lấy nét hoặc bất kỳ kỹ thuật phức tạp nào khác, chỉ cần khớp luồng sự kiện với mối quan hệ của phần tử:

$(document).on("click.menu-outside", function(event){
    // Test if target and it's parent aren't #menuscontainer
    // That means the click event occur on other branch of document tree
    if(!$(event.target).parents().andSelf().is("#menuscontainer")){
        // Click outisde #menuscontainer
        // Hide the menus (but test if menus aren't already hidden)
    }
});

Để loại bỏ nhấp vào bên ngoài trình nghe sự kiện, chỉ cần:

$(document).off("click.menu-outside");

Đối với tôi đây là giải pháp tốt nhất. Đã sử dụng nó trong một cuộc gọi lại sau khi hoạt hình vì vậy tôi cần phải tách ra một số sự kiện. +1
qwertzman

Có một kiểm tra dư thừa ở đây. nếu yếu tố thực sự là #menuscontainerbạn vẫn đang trải qua đó là cha mẹ. trước tiên bạn nên kiểm tra xem, và nếu đó không phải là phần tử đó, thì hãy đi lên cây DOM.
vsync

Đúng ! Bạn có thể thay đổi điều kiện để if(!($(event.target).is("#menuscontainer") || $(event.target).parents().is("#menuscontainer"))){. Đó là một tối ưu hóa nhỏ, nhưng chỉ xảy ra vài lần trong vòng đời chương trình của bạn: cho mỗi lần nhấp, nếu click.menu-outsidesự kiện được đăng ký. Nó dài hơn (+32 ký tự) và không sử dụng phương thức xâu chuỗi
mems

8

Sử dụng:

var go = false;
$(document).click(function(){
    if(go){
        $('#divID').hide();
        go = false;
    }
})

$("#divID").mouseover(function(){
    go = false;
});

$("#divID").mouseout(function (){
    go = true;
});

$("btnID").click( function(){
    if($("#divID:visible").length==1)
        $("#divID").hide(); // Toggle
    $("#divID").show();
});

7

Nếu ai đó tò mò ở đây là giải pháp javascript (es6):

window.addEventListener('mouseup', e => {
        if (e.target != yourDiv && e.target.parentNode != yourDiv) {
            yourDiv.classList.remove('show-menu');
            //or yourDiv.style.display = 'none';
        }
    })

và es5, chỉ trong trường hợp:

window.addEventListener('mouseup', function (e) {
if (e.target != yourDiv && e.target.parentNode != yourDiv) {
    yourDiv.classList.remove('show-menu'); 
    //or yourDiv.style.display = 'none';
}

});


7

Đây là một giải pháp đơn giản bằng javascript thuần túy. Nó được cập nhật với ES6 :

var isMenuClick = false;
var menu = document.getElementById('menuscontainer');
document.addEventListener('click',()=>{
    if(!isMenuClick){
       //Hide the menu here
    }
    //Reset isMenuClick 
    isMenuClick = false;
})
menu.addEventListener('click',()=>{
    isMenuClick = true;
})

"Cập nhật với ES6" là một tuyên bố khá táo bạo, khi điều duy nhất cập nhật với ES6 là () => {}thay vì function() {}. Những gì bạn có ở đó được phân loại là JavaScript đơn giản với một vòng xoắn ES6.
MortenMoulder

@MortenMoulder: Ya. Nó chỉ gây chú ý mặc dù nó thực sự là ES6. Nhưng chỉ cần nhìn vào giải pháp. Tôi nghĩ nó tốt.
Duannx

Đó là vanilla JS và hoạt động cho mục tiêu sự kiện được xóa khỏi DOM (ví dụ: khi giá trị từ cửa sổ bật lên bên trong được chọn, ngay lập tức đóng cửa sổ bật lên). +1 từ tôi!
Alice

6

Móc một người nghe sự kiện bấm vào tài liệu. Bên trong trình nghe sự kiện, bạn có thể nhìn vào đối tượng sự kiện , cụ thể là event.target để xem phần tử nào được nhấp:

$(document).click(function(e){
    if ($(e.target).closest("#menuscontainer").length == 0) {
        // .closest can help you determine if the element 
        // or one of its ancestors is #menuscontainer
        console.log("hide");
    }
});

6

Tôi đã sử dụng kịch bản dưới đây và thực hiện với jQuery.

jQuery(document).click(function(e) {
    var target = e.target; //target div recorded
    if (!jQuery(target).is('#tobehide') ) {
        jQuery(this).fadeOut(); //if the click element is not the above id will hide
    }
})

Dưới đây tìm mã HTML

<div class="main-container">
<div> Hello I am the title</div>
<div class="tobehide">I will hide when you click outside of me</div>
</div>

Bạn có thể đọc hướng dẫn ở đây


5
$(document).click(function() {
    $(".overlay-window").hide();
});
$(".overlay-window").click(function() {
    return false;
});

Nếu bạn nhấp vào tài liệu, ẩn một phần tử đã cho, trừ khi bạn nhấp vào phần tử đó.


5

Upvote cho câu trả lời phổ biến nhất, nhưng thêm

&& (e.target != $('html').get(0)) // ignore the scrollbar

vì vậy, một nhấp chuột trên thanh cuộn không [ẩn hoặc bất cứ thứ gì] phần tử mục tiêu của bạn.


5

Để sử dụng dễ dàng hơn và mã biểu cảm hơn, tôi đã tạo một plugin jQuery cho việc này:

$('div.my-element').clickOut(function(target) { 
    //do something here... 
});

Lưu ý: mục tiêu là yếu tố người dùng thực sự nhấp vào. Nhưng gọi lại vẫn được thực hiện trong ngữ cảnh của phần tử gốc, vì vậy bạn có thể sử dụng điều này như bạn mong đợi trong một cuộc gọi lại jQuery.

Cắm vào:

$.fn.clickOut = function (parent, fn) {
    var context = this;
    fn = (typeof parent === 'function') ? parent : fn;
    parent = (parent instanceof jQuery) ? parent : $(document);

    context.each(function () {
        var that = this;
        parent.on('click', function (e) {
            var clicked = $(e.target);
            if (!clicked.is(that) && !clicked.parents().is(that)) {
                if (typeof fn === 'function') {
                    fn.call(that, clicked);
                }
            }
        });

    });
    return context;
};

Theo mặc định, trình nghe sự kiện nhấp được đặt trên tài liệu. Tuy nhiên, nếu bạn muốn giới hạn phạm vi trình nghe sự kiện, bạn có thể chuyển vào một đối tượng jQuery đại diện cho một phần tử mức cha mẹ sẽ là phần tử cha mẹ hàng đầu mà tại đó các nhấp chuột sẽ được lắng nghe. Điều này ngăn chặn người nghe sự kiện cấp tài liệu không cần thiết. Rõ ràng, nó sẽ không hoạt động trừ khi phần tử cha được cung cấp là phần tử mẹ của phần tử ban đầu của bạn.

Sử dụng như vậy:

$('div.my-element').clickOut($('div.my-parent'), function(target) { 
    //do something here...
});
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.