要素の外側のクリックを検出するにはどうすればよいですか?私はVue.jsを使用しているので、それは私のテンプレート要素の外にあります。私はバニラJSでそれを行う方法を知っていますが、私がVue.jsを使用しているときにそれを行うためのより適切な方法があるかどうかはわかりませんか?
これはバニラJSのソリューションです: divの外部のJavascript Detect Clickイベント
要素にアクセスするためのより良い方法を使用できると思いますか?
要素の外側のクリックを検出するにはどうすればよいですか?私はVue.jsを使用しているので、それは私のテンプレート要素の外にあります。私はバニラJSでそれを行う方法を知っていますが、私がVue.jsを使用しているときにそれを行うためのより適切な方法があるかどうかはわかりませんか?
これはバニラJSのソリューションです: divの外部のJavascript Detect Clickイベント
要素にアクセスするためのより良い方法を使用できると思いますか?
回答:
カスタムディレクティブを1回設定するだけでうまく解決できます。
Vue.directive('click-outside', {
bind () {
this.event = event => this.vm.$emit(this.expression, event)
this.el.addEventListener('click', this.stopProp)
document.body.addEventListener('click', this.event)
},
unbind() {
this.el.removeEventListener('click', this.stopProp)
document.body.removeEventListener('click', this.event)
},
stopProp(event) { event.stopPropagation() }
})
使用法:
<div v-click-outside="nameOfCustomEventToCall">
Some content
</div>
コンポーネント内:
events: {
nameOfCustomEventToCall: function (event) {
// do something - probably hide the dropdown menu / modal etc.
}
}
警告に関する追加情報を含むJSFiddleのデモ:
私が使用したソリューションがあります。これはLinus Borgの回答に基づいており、vue.js 2.0で正常に動作します。
Vue.directive('click-outside', {
bind: function (el, binding, vnode) {
el.clickOutsideEvent = function (event) {
// here I check that click was outside the el and his children
if (!(el == event.target || el.contains(event.target))) {
// and if it did, call method provided in attribute value
vnode.context[binding.expression](event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent)
},
unbind: function (el) {
document.body.removeEventListener('click', el.clickOutsideEvent)
},
});
あなたはそれを使ってそれにバインドしますv-click-outside
:
<div v-click-outside="doStuff">
カスタムディレクティブの詳細と、el、バインディング、vnodeの意味については、https://vuejs.org/v2/guide/custom-directive.html#Directive-Hook-Argumentsをご覧ください。
tabindex
フォーカスできるようにコンポーネントに属性を追加し、以下を実行します。
<template>
<div
@focus="handleFocus"
@focusout="handleFocusOut"
tabindex="0"
>
SOME CONTENT HERE
</div>
</template>
<script>
export default {
methods: {
handleFocus() {
// do something here
},
handleFocusOut() {
// do something here
}
}
}
</script>
outline: none;
ため、要素のフォーカスに追加しました。
コミュニティでは、このタスクに使用できる2つのパッケージがあります(どちらも維持されます)。
vue-clickaway
パッケージは私の問題を完全に解決しました。ありがとう
これはVue.js 2.5.2で私のために働きました:
/**
* Call a function when a click is detected outside of the
* current DOM node ( AND its children )
*
* Example :
*
* <template>
* <div v-click-outside="onClickOutside">Hello</div>
* </template>
*
* <script>
* import clickOutside from '../../../../directives/clickOutside'
* export default {
* directives: {
* clickOutside
* },
* data () {
* return {
showDatePicker: false
* }
* },
* methods: {
* onClickOutside (event) {
* this.showDatePicker = false
* }
* }
* }
* </script>
*/
export default {
bind: function (el, binding, vNode) {
el.__vueClickOutside__ = event => {
if (!el.contains(event.target)) {
// call method provided in v-click-outside value
vNode.context[binding.expression](event)
event.stopPropagation()
}
}
document.body.addEventListener('click', el.__vueClickOutside__)
},
unbind: function (el, binding, vNode) {
// Remove Event Listeners
document.removeEventListener('click', el.__vueClickOutside__)
el.__vueClickOutside__ = null
}
}
export default {
bind: function (el, binding, vNode) {
// Provided expression must evaluate to a function.
if (typeof binding.value !== 'function') {
const compName = vNode.context.name
let warn = `[Vue-click-outside:] provided expression '${binding.expression}' is not a function, but has to be`
if (compName) { warn += `Found in component '${compName}'` }
console.warn(warn)
}
// Define Handler and cache it on the element
const bubble = binding.modifiers.bubble
const handler = (e) => {
if (bubble || (!el.contains(e.target) && el !== e.target)) {
binding.value(e)
}
}
el.__vueClickOutside__ = handler
// add Event Listeners
document.addEventListener('click', handler)
},
unbind: function (el, binding) {
// Remove Event Listeners
document.removeEventListener('click', el.__vueClickOutside__)
el.__vueClickOutside__ = null
}
}
私はすべての答え(vue-clickawayからの行を含む)を組み合わせて、私のために働くこのソリューションを思いつきました:
Vue.directive('click-outside', {
bind(el, binding, vnode) {
var vm = vnode.context;
var callback = binding.value;
el.clickOutsideEvent = function (event) {
if (!(el == event.target || el.contains(event.target))) {
return callback.call(vm, event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent);
},
unbind(el) {
document.body.removeEventListener('click', el.clickOutsideEvent);
}
});
コンポーネントでの使用:
<li v-click-outside="closeSearch">
<!-- your component here -->
</li>
MadisonTrashの回答を更新してMobile Safariをサポートしました(click
イベントがないため、touchend
代わりに使用する必要があります)。これには、モバイルデバイスでのドラッグによってイベントがトリガーされないようにチェックも組み込まれています。
Vue.directive('click-outside', {
bind: function (el, binding, vnode) {
el.eventSetDrag = function () {
el.setAttribute('data-dragging', 'yes');
}
el.eventClearDrag = function () {
el.removeAttribute('data-dragging');
}
el.eventOnClick = function (event) {
var dragging = el.getAttribute('data-dragging');
// Check that the click was outside the el and its children, and wasn't a drag
if (!(el == event.target || el.contains(event.target)) && !dragging) {
// call method provided in attribute value
vnode.context[binding.expression](event);
}
};
document.addEventListener('touchstart', el.eventClearDrag);
document.addEventListener('touchmove', el.eventSetDrag);
document.addEventListener('click', el.eventOnClick);
document.addEventListener('touchend', el.eventOnClick);
}, unbind: function (el) {
document.removeEventListener('touchstart', el.eventClearDrag);
document.removeEventListener('touchmove', el.eventSetDrag);
document.removeEventListener('click', el.eventOnClick);
document.removeEventListener('touchend', el.eventOnClick);
el.removeAttribute('data-dragging');
},
});
私はこのコードを使用します:
表示/非表示ボタン
<a @click.stop="visualSwitch()"> show hide </a>
show-hide要素
<div class="dialog-popup" v-if="visualState" @click.stop=""></div>
脚本
data () { return {
visualState: false,
}},
methods: {
visualSwitch() {
this.visualState = !this.visualState;
if (this.visualState)
document.addEventListener('click', this.visualState);
else
document.removeEventListener('click', this.visualState);
},
},
更新:時計を削除します。伝播停止を追加
私は追加の関数が嫌いなので...追加のvueメソッドなしの素晴らしいvueソリューションはvarのみです
<p @click="popup = !popup" v-out="popup">
<div v-if="popup">
My awesome popup
</div>
data:{
popup: false,
}
Vue.directive('out', {
bind: function (el, binding, vNode) {
const handler = (e) => {
if (!el.contains(e.target) && el !== e.target) {
//and here is you toggle var. thats it
vNode.context[binding.expression] = false
}
}
el.out = handler
document.addEventListener('click', handler)
},
unbind: function (el, binding) {
document.removeEventListener('click', el.out)
el.out = null
}
})
要素の外側であるが親の内部でのクリックを具体的に探している場合は、
<div class="parent" @click.self="onParentClick">
<div class="child"></div>
</div>
これをモーダルに使用します。
このようにクリックイベントに対して2つのイベントリスナーを登録できます
document.getElementById("some-area")
.addEventListener("click", function(e){
alert("You clicked on the area!");
e.stopPropagation();// this will stop propagation of this event to upper level
}
);
document.body.addEventListener("click",
function(e) {
alert("You clicked outside the area!");
}
);
<button
class="dropdown"
@click.prevent="toggle"
ref="toggle"
:class="{'is-active': isActiveEl}"
>
Click me
</button>
data() {
return {
isActiveEl: false
}
},
created() {
window.addEventListener('click', this.close);
},
beforeDestroy() {
window.removeEventListener('click', this.close);
},
methods: {
toggle: function() {
this.isActiveEl = !this.isActiveEl;
},
close(e) {
if (!this.$refs.toggle.contains(e.target)) {
this.isActiveEl = false;
}
},
},
短い答え:これは、カスタムディレクティブを使用して行う必要があります。
ここにも素晴らしい答えがたくさんありますが、私が見た答えのほとんどは、外部クリックを広範囲に使用し始めると壊れます(特に、階層化または複数の除外あり)。記事を書きました、カスタムディレクティブのニュアンスと具体的にはこのディレクティブの実装について話しているメディア。すべてのエッジケースをカバーしているわけではありませんが、私が考えたすべてをカバーしています。
これにより、複数のバインディング、他の要素の除外の複数のレベルが考慮され、ハンドラーが「ビジネスロジック」のみを管理できるようになります。
少なくとも定義部分のコードは次のとおりです。詳しい説明については、記事をご覧ください。
var handleOutsideClick={}
const OutsideClick = {
// this directive is run on the bind and unbind hooks
bind (el, binding, vnode) {
// Define the function to be called on click, filter the excludes and call the handler
handleOutsideClick[el.id] = e => {
e.stopPropagation()
// extract the handler and exclude from the binding value
const { handler, exclude } = binding.value
// set variable to keep track of if the clicked element is in the exclude list
let clickedOnExcludedEl = false
// if the target element has no classes, it won't be in the exclude list skip the check
if (e.target._prevClass !== undefined) {
// for each exclude name check if it matches any of the target element's classes
for (const className of exclude) {
clickedOnExcludedEl = e.target._prevClass.includes(className)
if (clickedOnExcludedEl) {
break // once we have found one match, stop looking
}
}
}
// don't call the handler if our directive element contains the target element
// or if the element was in the exclude list
if (!(el.contains(e.target) || clickedOnExcludedEl)) {
handler()
}
}
// Register our outsideClick handler on the click/touchstart listeners
document.addEventListener('click', handleOutsideClick[el.id])
document.addEventListener('touchstart', handleOutsideClick[el.id])
document.onkeydown = e => {
//this is an option but may not work right with multiple handlers
if (e.keyCode === 27) {
// TODO: there are minor issues when escape is clicked right after open keeping the old target
handleOutsideClick[el.id](e)
}
}
},
unbind () {
// If the element that has v-outside-click is removed, unbind it from listeners
document.removeEventListener('click', handleOutsideClick[el.id])
document.removeEventListener('touchstart', handleOutsideClick[el.id])
document.onkeydown = null //Note that this may not work with multiple listeners
}
}
export default OutsideClick
created()内の関数を使用して、少し異なる方法でそれを行いました。
created() {
window.addEventListener('click', (e) => {
if (!this.$el.contains(e.target)){
this.showMobileNav = false
}
})
},
このように、誰かが要素の外側をクリックすると、私の場合、モバイルナビゲーションが非表示になります。
お役に立てれば!
この質問にはすでに多くの回答があり、それらのほとんどは同様のカスタムディレクティブのアイデアに基づいています。このアプローチの問題は、メソッド関数をディレクティブに渡す必要があり、他のイベントのようにコードを直接作成できないことです。
vue-on-clickout
違うパッケージを作りました。それをチェックしてください:
v-on:clickout
他のイベントと同じように書くことができます。たとえば、次のように書くことができます
<div v-on:clickout="myField=value" v-on:click="myField=otherValue">...</div>
そしてそれは動作します。
vue-on-clickout
Vue 3に対応しました!
誰かがモーダルの外側をクリックしたときにモーダルを非表示にする方法を探している場合でも、モーダルは通常、そのクラスのラッパーmodal-wrap
またはユーザーが名前を付けたものを持っているので、ラッパーを付けることができ@click="closeModal"
ます。vuejsのドキュメントに記載されているイベント処理を使用して、クリックしたターゲットがラッパーまたはモーダルのどちらにあるかを確認できます。
methods: {
closeModal(e) {
this.event = function(event) {
if (event.target.className == 'modal-wrap') {
// close modal here
this.$store.commit("catalog/hideModal");
document.body.removeEventListener("click", this.event);
}
}.bind(this);
document.body.addEventListener("click", this.event);
},
}
<div class="modal-wrap" @click="closeModal">
<div class="modal">
...
</div>
<div>
@Denis Danilenkoソリューションは私にとってはうまくいきます、ここで私がやったことです:ちなみに私はVueJS CLI3とNuxtJSをこことBootstrap4で使用していますが、NuxtJSのないVueJSでも動作します:
<div
class="dropdown ml-auto"
:class="showDropdown ? null : 'show'">
<a
href="#"
class="nav-link"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
@click="showDropdown = !showDropdown"
@blur="unfocused">
<i class="fas fa-bars"></i>
</a>
<div
class="dropdown-menu dropdown-menu-right"
aria-labelledby="dropdownMenuLink"
:class="showDropdown ? null : 'show'">
<nuxt-link class="dropdown-item" to="/contact">Contact</nuxt-link>
<nuxt-link class="dropdown-item" to="/faq">FAQ</nuxt-link>
</div>
</div>
export default {
data() {
return {
showDropdown: true
}
},
methods: {
unfocused() {
this.showDropdown = !this.showDropdown;
}
}
}
ディレクティブからカスタムのネイティブJavaScriptイベントを発行できます。node.dispatchEventを使用して、ノードからイベントをディスパッチするディレクティブを作成します
let handleOutsideClick;
Vue.directive('out-click', {
bind (el, binding, vnode) {
handleOutsideClick = (e) => {
e.stopPropagation()
const handler = binding.value
if (el.contains(e.target)) {
el.dispatchEvent(new Event('out-click')) <-- HERE
}
}
document.addEventListener('click', handleOutsideClick)
document.addEventListener('touchstart', handleOutsideClick)
},
unbind () {
document.removeEventListener('click', handleOutsideClick)
document.removeEventListener('touchstart', handleOutsideClick)
}
})
このように使用できます
h3( v-out-click @click="$emit('show')" @out-click="$emit('hide')" )
そのようにボディの最後にdivを作成します。
<div v-if="isPopup" class="outside" v-on:click="away()"></div>
.outsideの場所:
.outside {
width: 100vw;
height: 100vh;
position: fixed;
top: 0px;
left: 0px;
}
そして、away()はVueインスタンスのメソッドです:
away() {
this.isPopup = false;
}
簡単です。
ルート要素の内部に複数の要素を持つコンポーネントがある場合は、これを使用できます。ブール値を使用したIt Works™ソリューションです。
<template>
<div @click="clickInside"></div>
<template>
<script>
export default {
name: "MyComponent",
methods: {
clickInside() {
this.inside = true;
setTimeout(() => (this.inside = false), 0);
},
clickOutside() {
if (this.inside) return;
// handle outside state from here
}
},
created() {
this.__handlerRef__ = this.clickOutside.bind(this);
document.body.addEventListener("click", this.__handlerRef__);
},
destroyed() {
document.body.removeEventListener("click", this.__handlerRef__);
},
};
</script>
シンプルで信頼性が高く、現在他の多くのパッケージで使用されています。必要なコンポーネントでのみパッケージを呼び出すことにより、JavaScriptバンドルサイズを減らすこともできます(以下の例を参照)。
npm install vue-click-outside
<template>
<div>
<div v-click-outside="hide" @click="toggle">Toggle</div>
<div v-show="opened">Popup item</div>
</div>
</template>
<script>
import ClickOutside from 'vue-click-outside'
export default {
data () {
return {
opened: false
}
},
methods: {
toggle () {
this.opened = true
},
hide () {
this.opened = false
}
},
mounted () {
// prevent click outside event with popupItem.
this.popupItem = this.$el
},
// do not forget this section
directives: {
ClickOutside
}
}
</script>
外部クリックを処理する新しいコンポーネントを作成できます
Vue.component('click-outside', {
created: function () {
document.body.addEventListener('click', (e) => {
if (!this.$el.contains(e.target)) {
this.$emit('clickOutside');
})
},
template: `
<template>
<div>
<slot/>
</div>
</template>
`
})
そして、このコンポーネントを使用します:
<template>
<click-outside @clickOutside="console.log('Click outside Worked!')">
<div> Your code...</div>
</click-outside>
</template>
トグルドロップダウンメニューを処理するためのソリューションがあります。
export default {
data() {
return {
dropdownOpen: false,
}
},
methods: {
showDropdown() {
console.log('clicked...')
this.dropdownOpen = !this.dropdownOpen
// this will control show or hide the menu
$(document).one('click.status', (e)=> {
this.dropdownOpen = false
})
},
}
私はこのパッケージを使用しています:https : //www.npmjs.com/package/vue-click-outside
私にとってはうまくいきます
HTML:
<div class="__card-content" v-click-outside="hide" v-if="cardContentVisible">
<div class="card-header">
<input class="subject-input" placeholder="Subject" name=""/>
</div>
<div class="card-body">
<textarea class="conversation-textarea" placeholder="Start a conversation"></textarea>
</div>
</div>
私のスクリプトコード:
import ClickOutside from 'vue-click-outside'
export default
{
data(){
return {
cardContentVisible:false
}
},
created()
{
},
methods:
{
openCardContent()
{
this.cardContentVisible = true;
}, hide () {
this.cardContentVisible = false
}
},
directives: {
ClickOutside
}
}