click event bubbles.
Practically, if a click is triggered on <Child /> in this structure:
<GrandParent>
<Parent>
<Child />
</Parent>
</GrandParent>
, the event will fire on each element, in this order 1:
- on Child
- on Parent
- on GrandParent
- ... on each of the consequent parents in DOM tree, all the way up to
window object.
To prevent an event from bubbling you have to call its stopPropagation method. You can do this at any level, because the events described above are fired in sequence, not in parallel.
To prevent propagation to parents and also prevent any subsequent event handlers registered on the current element from being fired, call stopImmediatePropagation method on the event.
Vue provides event modifiers, significantly reducing the boilerplate typically associated with calling event native methods.
In your case, you have to use the .stop modifier on .card to prevent it from bubbling to .overlay:
<div v-if="displayOverlay" class="overlay" @click="displayOverlay = false">
<div class="card" @click.stop> Hello </div>
</div>
Demo 2, 3:
new Vue({
el: '#app',
data: () => ({
displayCard: true
})
})
.overlay {
position: absolute;
background-color: rgba(0,0,0,.21);
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.card {
height: 200px;
width: 300px;
padding: 1rem;
background-color: white;
}
body {
margin: 0;
}
#app {
min-height: 100vh;
}
<script src="https://unpkg.com/vue@2.6.14/dist/vue.min.js"></script>
<div id="app" @click="displayCard = true">
<div class="overlay" v-if="displayCard" @click.stop="displayCard = false">
<div class="card" @click.stop>Test</div>
</div>
<button v-if="!displayCard">Reopen overlay</button>
</div>
NOTES:
1 - the order above is the native firing order. Vue provides a modifier (.capture) which allows you to change it. Before starting to run the native sequence, Vue checks all parents (starting from window all the way down to the clicked element) and if any of them has the .capture modifier, their event handler will be run at that time (before running the handlers on any of its children). This doesn't mean the event handler will run twice, though. If it has been run in capture phase, it won't be run in bubbling phase. .capture won't prevent child handlers from running. They will still be run, but after the parent's handler.
2 - I've fixed the @click event handler on the overlay div:
!displayOverlay doesn't execute anything it just returns the negative boolean value of current displayOverlay value;
- to assign
false to displayOverlay you'd need: @click="() => { displayOverlay = false }". Vue allows us to write a shorter version: @click="displayOverlay = false" (will auto wrap it with an anonymous function).
3 - <div @click.stop>is short for <div @click="$event => $event.stopPropagation()">