Vue组件-检测外部点击
Vue component - detect outside click
所以,我已经在 Vue JS (2.x) 中实现了这个自定义下拉列表,并且几乎有我需要的东西,除了列表打开后,我想在组件外部单击 ( parent 页面或组件上的任意位置)关闭列表。我尝试捕获我的组件的根 div
的 blur
事件,但可以理解它不起作用,因为 div
s 没有获得焦点,因此无法模糊。所以此时此刻,解决方案似乎是——能够在组件外部监听点击事件。那可能吗? child 可以在 Vue 中监听其 parent 上的事件吗?如果或者即使不是,什么是最好的 and/or 实现此行为的最简单方法?
这是我在 CodeSandbox 中的代码,为了方便起见,我还在下面复制它 - https://codesandbox.io/s/romantic-night-ot7i8
Dropdown.vue
<template>
<div class="main-container" @blur="listOpen = false">
<button class="sel-btn" @click="listOpen = !listOpen">
{{ getText() }}
</button>
<br />
<div class="list-items" v-show="listOpen">
<button
class="item"
v-for="(l, i) in list"
:key="i"
@click="btnClicked(i)"
>
{{ l }}
</button>
</div>
</div>
</template>
<script>
export default {
props: {
defaultText: {
type: String,
required: false,
},
list: {
type: Array,
required: true,
},
},
data() {
return {
selectedIndex: -1,
listOpen: false,
};
},
methods: {
getText() {
if (this.selectedIndex !== -1) return this.list[this.selectedIndex];
if (this.defaultText) return this.defaultText;
return "Please Select";
},
btnClicked(index) {
this.selectedIndex = index;
this.listOpen = false;
this.$emit("input", this.list[index]);
},
},
};
</script>
<style scoped>
.main-container {
position: relative;
display: inline-block;
min-width: 125px;
max-width: 125px;
text-align: left;
border: 0;
background: #0000;
}
.sel-btn {
display: inline-block;
width: 100%;
text-align: left;
border: 0;
background: linear-gradient(0deg, #efefef, #fff);
padding: 8px;
border: 1px solid #1111;
box-shadow: 1px 1px 1px #1115;
cursor: pointer;
overflow: hidden;
}
.sel-btn:hover {
background: linear-gradient(0deg, #cef, #fff);
box-shadow: 1px 1px 1px #6390bd;
color: #58a;
}
.sel-btn::after {
content: "bc6";
position: absolute;
right: 4%;
}
.list-items {
position: absolute;
width: 100%;
background: #fff;
box-shadow: 1px 1px 3px #3333;
}
.item {
display: block;
width: 100%;
text-align: left;
background: #fff;
border: 0;
cursor: pointer;
padding: 8px;
border-bottom: 1px solid #2221;
font-size: 0.8rem;
}
.item:hover {
background: #4b84c5;
color: #fff;
}
</style>
App.vue
<template>
<div id="app">
<br />
<div style="margin-top: 30px; margin-bottom: 10px">
<Dropdown
:list="regions"
v-model="selectedRegion"
style="margin-right: 10px"
defaultText="--Select Region--"
></Dropdown>
<Dropdown
:list="cities"
v-model="selectedCity"
defaultText="--Select City--"
></Dropdown>
</div>
<div style="padding: 30px; background: #27e2">
(This poem excerpt is here to act as a filler)
<h2>The Daffodils</h2>
<h4><i>William Wordsworth</i></h4>
I wondered lonely<br />
As a cloud<br />
That floats on high<br />
O'er vales and hills<br />
When all at once<br />
I saw a crowd<br />
A host<br />
Of golden daffodils.<br />
</div>
<div style="padding: 30px">
<b>Region Selected:</b> {{ selectedRegion }}<br />
<b>City Selected:</b> {{ selectedCity }}
</div>
</div>
</template>
<script>
import Dropdown from "./components/Dropdown";
export default {
name: "App",
components: {
Dropdown,
},
data() {
return {
set: false,
regions: ["California", "Nova Scotia", "Kerala", "Bavaria", "Queensland"],
cities: [
"Munich",
"San Diego",
"Paris",
"Prague",
"Copenhagen",
"Kolkata",
"Dhaka",
"Colombo",
"Little City",
],
selectedRegion: "",
selectedCity: "",
};
},
};
</script>
<style>
* {
font-family: "Segoe UI", Helvetica, Arial, sans-serif;
}
h1,
h2,
h3,
h4,
b {
font-weight: 400;
color: #27c;
}
</style>
好的...到目前为止,这不是最佳解决方案,但它是一个可行的解决方案。
我使用了我所有的 MacGyver 能力并找到了一个方法。
请检查这个CodeSandbox
我所做的只是使用您的 listOpen
并添加了一个 eventListner。我发现您的自定义下拉列表没有内置 @blur
,因为它不是输入 ofc。
所以我在 mounted
挂钩中为它添加了一个事件。
关键是我还在 100 毫秒上添加了一个 setTimeout
,否则你无法 select 下拉列表中的任何项目,下拉列表将以 blur
更快地关闭,然后你能够select任何东西。
最好的方法是使用 Vue Custom Directive 将焦点功能挂接到您的组件,并在组件内部或外部发生点击时将事件传播到组件外部。
在下面的示例中,我们创建了 v-ctrlfocus 指令,它将挂接到组件的根目录并为整个 window.
创建一个点击事件监听器
这样,当点击事件被触发时,我们可以比较它是发生在组件内部还是外部。
如果它发生在内部,我们将触发一个 focusin 事件,您可以使用 @focusin 在组件外部收听该事件。相反,如果点击发生在组件外部,它将触发 focusout 事件。
为了使用 Vue 新玩具进行更多更新,我使用了 typescript 和
所以,我已经在 Vue JS (2.x) 中实现了这个自定义下拉列表,并且几乎有我需要的东西,除了列表打开后,我想在组件外部单击 ( parent 页面或组件上的任意位置)关闭列表。我尝试捕获我的组件的根 div
的 blur
事件,但可以理解它不起作用,因为 div
s 没有获得焦点,因此无法模糊。所以此时此刻,解决方案似乎是——能够在组件外部监听点击事件。那可能吗? child 可以在 Vue 中监听其 parent 上的事件吗?如果或者即使不是,什么是最好的 and/or 实现此行为的最简单方法?
这是我在 CodeSandbox 中的代码,为了方便起见,我还在下面复制它 - https://codesandbox.io/s/romantic-night-ot7i8
Dropdown.vue
<template>
<div class="main-container" @blur="listOpen = false">
<button class="sel-btn" @click="listOpen = !listOpen">
{{ getText() }}
</button>
<br />
<div class="list-items" v-show="listOpen">
<button
class="item"
v-for="(l, i) in list"
:key="i"
@click="btnClicked(i)"
>
{{ l }}
</button>
</div>
</div>
</template>
<script>
export default {
props: {
defaultText: {
type: String,
required: false,
},
list: {
type: Array,
required: true,
},
},
data() {
return {
selectedIndex: -1,
listOpen: false,
};
},
methods: {
getText() {
if (this.selectedIndex !== -1) return this.list[this.selectedIndex];
if (this.defaultText) return this.defaultText;
return "Please Select";
},
btnClicked(index) {
this.selectedIndex = index;
this.listOpen = false;
this.$emit("input", this.list[index]);
},
},
};
</script>
<style scoped>
.main-container {
position: relative;
display: inline-block;
min-width: 125px;
max-width: 125px;
text-align: left;
border: 0;
background: #0000;
}
.sel-btn {
display: inline-block;
width: 100%;
text-align: left;
border: 0;
background: linear-gradient(0deg, #efefef, #fff);
padding: 8px;
border: 1px solid #1111;
box-shadow: 1px 1px 1px #1115;
cursor: pointer;
overflow: hidden;
}
.sel-btn:hover {
background: linear-gradient(0deg, #cef, #fff);
box-shadow: 1px 1px 1px #6390bd;
color: #58a;
}
.sel-btn::after {
content: "bc6";
position: absolute;
right: 4%;
}
.list-items {
position: absolute;
width: 100%;
background: #fff;
box-shadow: 1px 1px 3px #3333;
}
.item {
display: block;
width: 100%;
text-align: left;
background: #fff;
border: 0;
cursor: pointer;
padding: 8px;
border-bottom: 1px solid #2221;
font-size: 0.8rem;
}
.item:hover {
background: #4b84c5;
color: #fff;
}
</style>
App.vue
<template>
<div id="app">
<br />
<div style="margin-top: 30px; margin-bottom: 10px">
<Dropdown
:list="regions"
v-model="selectedRegion"
style="margin-right: 10px"
defaultText="--Select Region--"
></Dropdown>
<Dropdown
:list="cities"
v-model="selectedCity"
defaultText="--Select City--"
></Dropdown>
</div>
<div style="padding: 30px; background: #27e2">
(This poem excerpt is here to act as a filler)
<h2>The Daffodils</h2>
<h4><i>William Wordsworth</i></h4>
I wondered lonely<br />
As a cloud<br />
That floats on high<br />
O'er vales and hills<br />
When all at once<br />
I saw a crowd<br />
A host<br />
Of golden daffodils.<br />
</div>
<div style="padding: 30px">
<b>Region Selected:</b> {{ selectedRegion }}<br />
<b>City Selected:</b> {{ selectedCity }}
</div>
</div>
</template>
<script>
import Dropdown from "./components/Dropdown";
export default {
name: "App",
components: {
Dropdown,
},
data() {
return {
set: false,
regions: ["California", "Nova Scotia", "Kerala", "Bavaria", "Queensland"],
cities: [
"Munich",
"San Diego",
"Paris",
"Prague",
"Copenhagen",
"Kolkata",
"Dhaka",
"Colombo",
"Little City",
],
selectedRegion: "",
selectedCity: "",
};
},
};
</script>
<style>
* {
font-family: "Segoe UI", Helvetica, Arial, sans-serif;
}
h1,
h2,
h3,
h4,
b {
font-weight: 400;
color: #27c;
}
</style>
好的...到目前为止,这不是最佳解决方案,但它是一个可行的解决方案。 我使用了我所有的 MacGyver 能力并找到了一个方法。
请检查这个CodeSandbox
我所做的只是使用您的 listOpen
并添加了一个 eventListner。我发现您的自定义下拉列表没有内置 @blur
,因为它不是输入 ofc。
所以我在 mounted
挂钩中为它添加了一个事件。
关键是我还在 100 毫秒上添加了一个 setTimeout
,否则你无法 select 下拉列表中的任何项目,下拉列表将以 blur
更快地关闭,然后你能够select任何东西。
最好的方法是使用 Vue Custom Directive 将焦点功能挂接到您的组件,并在组件内部或外部发生点击时将事件传播到组件外部。
在下面的示例中,我们创建了 v-ctrlfocus 指令,它将挂接到组件的根目录并为整个 window.
创建一个点击事件监听器这样,当点击事件被触发时,我们可以比较它是发生在组件内部还是外部。
如果它发生在内部,我们将触发一个 focusin 事件,您可以使用 @focusin 在组件外部收听该事件。相反,如果点击发生在组件外部,它将触发 focusout 事件。
为了使用 Vue 新玩具进行更多更新,我使用了 typescript 和