Mouseover or hover vue.js - javascript

I would like to show a div when hovering over an element in vue.js. But I can't seem to get it working.
It looks like there is no event for hover or mouseover in vue.js. Is this really true?
Would it be possible to combine jquery hover and vue methods?

i feel above logics for hover is incorrect. it just inverse when mouse hovers. i have used below code. it seems to work perfectly alright.
<div #mouseover="upHere = true" #mouseleave="upHere = false" >
<h2> Something Something </h2>
<some-component v-show="upHere"></some-component>
</div>
on vue instance
data: {
upHere: false
}

Here is a working example of what I think you are asking for.
http://jsfiddle.net/1cekfnqw/3017/
<div id="demo">
<div v-show="active">Show</div>
<div #mouseover="mouseOver">Hover over me!</div>
</div>
var demo = new Vue({
el: '#demo',
data: {
active: false
},
methods: {
mouseOver: function(){
this.active = !this.active;
}
}
});

There's no need for a method here.
HTML
<div v-if="active">
<h2>Hello World!</h2>
</div>
<div v-on:mouseover="active = !active">
<h1>Hover me!</h1>
</div>
JS
new Vue({
el: 'body',
data: {
active: false
}
})

To show child or sibling elements it's possible with CSS only. If you use :hover before combinators (+, ~, >, space). Then the style applies not to hovered element.
HTML
<body>
<div class="trigger">
Hover here.
</div>
<div class="hidden">
This message shows up.
</div>
</body>
CSS
.hidden { display: none; }
.trigger:hover + .hidden { display: inline; }

With mouseover and mouseleave events you can define a toggle function that implements this logic and react on the value in the rendering.
Check this example:
var vm = new Vue({
el: '#app',
data: {btn: 'primary'}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<div id='app'>
<button
#mouseover="btn='warning'"
#mouseleave="btn='primary'"
:class='"btn btn-block btn-"+btn'>
{{ btn }}
</button>
</div>

I think what you want to achieve is with the combination of
#mouseover, #mouseout, #mouseenter and #mouseleave
So the two best combinations are
"#mouseover and #mouseout"
or
"#mouseenter and #mouseleave"
And I think, It's better to use the 2nd pair so that you can achieve the hover effect and call functionalities on that.
<div #mouseenter="activeHover = true" #mouseleave="activeHover = false" >
<p v-if="activeHover"> This will be showed on hover </p>
<p v-if ="!activeHover"> This will be showed in simple cases </p>
</div>
on vue instance
data : {
activeHover : false
}
Note: 1st pair will effect/travel on the child elements as well but 2nd pair will only effect where you want to use it not the child elements. Else you will experience some glitch/fluctuation by using 1st pair. So, better to use 2nd pair to avoid any fluctuations.
I hope, it will help others as well :)

Though I would give an update using the new composition api.
Component
<template>
<div #mouseenter="hovering = true" #mouseleave="hovering = false">
{{ hovering }}
</div>
</template>
<script>
import { ref } from '#vue/composition-api'
export default {
setup() {
const hovering = ref(false)
return { hovering }
}
})
</script>
Reusable Composition Function
Creating a useHover function will allow you to reuse in any components.
export function useHover(target: Ref<HTMLElement | null>) {
const hovering = ref(false)
const enterHandler = () => (hovering.value = true)
const leaveHandler = () => (hovering.value = false)
onMounted(() => {
if (!target.value) return
target.value.addEventListener('mouseenter', enterHandler)
target.value.addEventListener('mouseleave', leaveHandler)
})
onUnmounted(() => {
if (!target.value) return
target.value.removeEventListener('mouseenter', enterHandler)
target.value.removeEventListener('mouseleave', leaveHandler)
})
return hovering
}
Here's a quick example calling the function inside a Vue component.
<template>
<div ref="hoverRef">
{{ hovering }}
</div>
</template>
<script lang="ts">
import { ref } from '#vue/composition-api'
import { useHover } from './useHover'
export default {
setup() {
const hoverRef = ref(null)
const hovering = useHover(hoverRef)
return { hovering, hoverRef }
}
})
</script>
You can also use a library such as #vuehooks/core which comes with many useful functions including useHover.
Reference: Vuejs composition API

It's possible to toggle a class on hover strictly within a component's template, however, it's not a practical solution for obvious reasons. For prototyping on the other hand, I find it useful to not have to define data properties or event handlers within the script.
Here's an example of how you can experiment with icon colors using Vuetify.
new Vue({
el: '#app'
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-toolbar color="black" dark>
<v-toolbar-items>
<v-btn icon>
<v-icon #mouseenter="e => e.target.classList.toggle('pink--text')" #mouseleave="e => e.target.classList.toggle('pink--text')">delete</v-icon>
</v-btn>
<v-btn icon>
<v-icon #mouseenter="e => e.target.classList.toggle('blue--text')" #mouseleave="e => e.target.classList.toggle('blue--text')">launch</v-icon>
</v-btn>
<v-btn icon>
<v-icon #mouseenter="e => e.target.classList.toggle('green--text')" #mouseleave="e => e.target.classList.toggle('green--text')">check</v-icon>
</v-btn>
</v-toolbar-items>
</v-toolbar>
</v-app>
</div>

With mouseover only the element stays visible when mouse leaves the hovered element, so I added this:
#mouseover="active = !active" #mouseout="active = !active"
<script>
export default {
data(){
return {
active: false
}
}
</script>

I came up with the same problem, and I work it out !
<img :src='book.images.small' v-on:mouseenter="hoverImg">

There is a correct working JSFiddle: http://jsfiddle.net/1cekfnqw/176/
<p v-on:mouseover="mouseOver" v-bind:class="{on: active, 'off': !active}">Hover over me!</p>

Please take a look at the vue-mouseover package if you are not satisfied with how does this code look:
<div
#mouseover="isMouseover = true"
#mouseleave="isMouseover = false"
/>
vue-mouseover provides a v-mouseover directive that automaticaly updates the specified data context property when the cursor enters or leaves an HTML element the directive is attached to.
By default in the next example isMouseover property will be true when the cursor is over an HTML element and false otherwise:
<div v-mouseover="isMouseover" />
Also by default isMouseover will be initially assigned when v-mouseover is attached to the div element, so it will not remain unassigned before the first mouseenter/mouseleave event.
You can specify custom values via v-mouseover-value directive:
<div
v-mouseover="isMouseover"
v-mouseover-value="customMouseenterValue"/>
or
<div
v-mouseover="isMouseover"
v-mouseover-value="{
mouseenter: customMouseenterValue,
mouseleave: customMouseleaveValue
}"
/>
Custom default values can be passed to the package via options object during setup.

Here is a very simple example for MouseOver and MouseOut:
<div id="app">
<div :style = "styleobj" #mouseover = "changebgcolor" #mouseout = "originalcolor">
</div>
</div>
new Vue({
el:"#app",
data:{
styleobj : {
width:"100px",
height:"100px",
backgroundColor:"red"
}
},
methods:{
changebgcolor : function() {
this.styleobj.backgroundColor = "green";
},
originalcolor : function() {
this.styleobj.backgroundColor = "red";
}
}
});

This worked for me for nuxt
<template>
<span
v-if="item"
class="primary-navigation-list-dropdown"
#mouseover="isTouchscreenDevice ? null : openDropdownMenu()"
#mouseleave="isTouchscreenDevice ? null : closeDropdownMenu()"
>
<nuxt-link
to="#"
#click.prevent.native="openDropdownMenu"
v-click-outside="closeDropdownMenu"
:title="item.title"
:class="[
item.cssClasses,
{ show: isDropdownMenuVisible }
]"
:id="`navbarDropdownMenuLink-${item.id}`"
:aria-expanded="[isDropdownMenuVisible ? true : false]"
class="
primary-navigation-list-dropdown__toggle
nav-link
dropdown-toggle"
aria-current="page"
role="button"
data-toggle="dropdown"
>
{{ item.label }}
</nuxt-link>
<ul
:class="{ show: isDropdownMenuVisible }"
:aria-labelledby="`navbarDropdownMenuLink-${item.id}`"
class="
primary-navigation-list-dropdown__menu
dropdown-menu-list
dropdown-menu"
>
<li
v-for="item in item.children" :key="item.id"
class="dropdown-menu-list__item"
>
<NavLink
:attributes="item"
class="dropdown-menu-list__link dropdown-item"
/>
</li>
</ul>
</span>
</template>
<script>
import NavLink from '#/components/Navigation/NavLink';
export default {
name: "DropdownMenu",
props: {
item: {
type: Object,
required: true,
},
},
data() {
return {
isDropdownMenuVisible: false,
isTouchscreenDevice: false
};
},
mounted() {
this.detectTouchscreenDevice();
},
methods: {
openDropdownMenu() {
if (this.isTouchscreenDevice) {
this.isDropdownMenuVisible = !this.isDropdownMenuVisible;
} else {
this.isDropdownMenuVisible = true;
}
},
closeDropdownMenu() {
if (this.isTouchscreenDevice) {
this.isDropdownMenuVisible = false;
} else {
this.isDropdownMenuVisible = false;
}
},
detectTouchscreenDevice() {
if (window.PointerEvent && ('maxTouchPoints' in navigator)) {
if (navigator.maxTouchPoints > 0) {
this.isTouchscreenDevice = true;
}
} else {
if (window.matchMedia && window.matchMedia("(any-pointer:coarse)").matches) {
this.isTouchscreenDevice = true;
} else if (window.TouchEvent || ('ontouchstart' in window)) {
this.isTouchscreenDevice = true;
}
}
return this.isTouchscreenDevice;
}
},
components: {
NavLink
}
};
</script>
<style scoped lang="scss">
.primary-navigation-list-dropdown {
&__toggle {
color: $white;
&:hover {
color: $blue;
}
}
&__menu {
margin-top: 0;
}
&__dropdown {
}
}
.dropdown-menu-list {
&__item {
}
&__link {
&.active,
&.nuxt-link-exact-active {
border-bottom: 1px solid $blue;
}
}
}
</style>

You can also use VueUse composables.
This is for mouse hover
<script setup>
import { useElementHover } from '#vueuse/core'
const myHoverableElement = ref()
const isHovered = useElementHover(myHoverableElement)
</script>
<template>
<button ref="myHoverableElement">
{{ isHovered }}
</button>
</template>
This one for mouse hover
import { useMouse } from '#vueuse/core'
const { x, y, sourceType } = useMouse()
or even in a specific element.
TLDR: quite a few handy composables for your Vue2/Vue3 apps!

Related

Vue3 can't get dynamic styling to only apply to specific buttons

I am in the process of learning vue and I'm stumped on how to get these buttons to dynamically style separately when clicked. These are filters for a list of products and I would like the apply one style when the filter is 'on' and a different style when the filter is 'off'. I can get the styles to update dynamically, but all of the buttons change style when any of them are clicked. The actual filter functionality is working as expected (the products are being filtered out when the button for that product is clicked).
In the code snippet, mode is passed to the BaseButton component, which is then applied as the class.
<template>
<ul>
<li v-for="genus of genusList" :key="genus.label">
<BaseButton #click="filterGenus(genus.label)" :mode="genusClicked.clicked ? 'outline' :''">
{{ genus.label }}
</BaseButton>
</li>
<BaseButton #click="clearFilter()" mode="flat">Clear Filter</BaseButton>
</ul>
</template>
methods: {
filterGenus(selectedGenus) {
this.clickedGenus = selectedGenus
this.clicked = !this.clicked
this.$emit('filter-genus', selectedGenus)
},
clearFilter() {
this.$emit('clear-filter')
}
},
I have tried making a computed value to add a .clicked value to the genusList object but that didn't seem to help.
Maybe something like following snippet (if you need more buttons to be styled at once save selected in array, if only one just save selected):
const app = Vue.createApp({
data() {
return {
genusList: [{label: 1}, {label: 2}, {label: 3}],
selGenus: [],
};
},
methods: {
isSelected(selectedGenus) {
return this.selGenus.includes(selectedGenus)
},
filterGenus(selectedGenus) {
if (this.isSelected(selectedGenus)) {
this.selGenus = this.selGenus.filter(s => s !== selectedGenus)
} else {
this.selGenus = [...this.selGenus, selectedGenus]
}
this.$emit('filter-genus', selectedGenus)
},
clearFilter() {
this.selGenus = []
this.$emit('clear-filter')
}
},
})
app.component('baseButton', {
template: `<button :class="mode"><slot /></button>`,
props: ['mode']
})
app.mount('#demo')
.outline {
outline: 2px solid red;
}
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<ul>
<li v-for="genus of genusList" :key="genus.label">
<base-button #click="filterGenus(genus.label)"
:mode="isSelected(genus.label) ? 'outline' :''">
{{ genus.label }}
</base-button>
</li>
<base-button #click="clearFilter()" mode="flat">
Clear Filter
</base-button>
</ul>
</div>

Automatic resizing of textarea after loading data in Vue

I have a Vue page which loads an json array from an api and displays the content in a list of multiple s by using v-for.
If you focus on one of the textarea's or change the text a function automatically resize's the textarea to fit the content.
<div v-for="post in posts">
<textarea v-model="post.body" rows="1" #focus="resizeTextarea" #keyup="resizeTextarea"></textarea>
</div>
resizeTextarea(e) {
let area = e.target;
area.style.overflow = 'hidden';
area.style.height = area.scrollHeight + 'px';
}
With my limited Vue knowledge, I can't find a solution to automatically resize all textarea's after loading the data from the API. There is no #load on a textarea.
I was trying to reach the same goal with using a watcher on the data but it feels like a long workaround.
Anyone a descent solution? Thank you!
https://jsfiddle.net/oehoe83/c1b8frup/19/
One solution would be to create a component for your textarea element and then resize it in the mounted() hook. Here's an example using single-file components:
// CustomTextarea.vue
<template>
<textarea
v-model="value"
ref="textarea"
rows="1"
#focus="resize"
#keyup="resize"
>
</textarea>
</template>
<script>
export default {
props: {
value: {
type: String,
required: true,
}
},
mounted() {
this.resize();
},
methods: {
resize() {
const { textarea } = this.$refs;
textarea.style.height = textarea.scrollHeight - 4 + 'px';
}
}
}
</script>
Then in your parent:
<template>
<div v-for="post in posts">
<CustomTextarea v-model="post.body" />
</div>
</template>
<script>
import CustomTextarea from './CustomTextarea.vue';
export default {
components: {
CustomTextarea,
}
// etc.
}
</script>
Note: if you're using Vue 3, replace value with modelValue in the child component.
Alternatively you could use a watch like you suggested, there's nothing wrong with that. Something like this:
watch: {
posts() {
// Wait until the template has updated
this.$nextTick(() => {
[...document.querySelectorAll('textarea')].forEach(textarea => {
this.resizeTextarea({ target: textarea });
});
});
}
}
you can add the ref attribute :
<div id="app">
<div v-for="post in posts" ref="container">
<textarea v-model="post.body" rows="1"#focus="resizeTextarea" #keyup="resizeTextarea" ></textarea>
</div>
</div>
and add the following code at the end of mounted() :
this.$nextTick(()=>{
this.$refs.container.forEach( ta => {
ta.firstChild.dispatchEvent(new Event("keyup"));
});
});

Float transparent PNG over text

I am looking to add semi-transparent stamp type image over a particular part of the text.
We are using vue to build our new website.
I've not really found anything about how to do this in vue.
I'm looking to place the image over the 4th line of the code shown below. Basically what it does is display the 'Report accepted by:' text along with the person who accepted the report taken from the database. I'd like to display that with an overlapping image that resembles a stamp of approval.
<template>
<div class="clearfix">
<span>Report accepted by:</span>
<span v-if="report_info.accepted && report_info.accepted_by !== null">{{ memberById(report_info.accepted_by).callsign }}</span>
<button
v-if="isAdmin"
class="float-right"
v-on:click="acceptRejectReport"
>{{ acceptButtonText }}</button>
</div>
</template>
<script>
import { mapState, mapGetters } from "vuex"
export default {
name: "ReportApprovalComp",
mounted () {
this.checkAdmin();
},
data () {
return {
isAdmin: false
}
},
computed: {
acceptButtonText() {
if(this.report_info.accepted){
return "Revoke report acceptance";
} else {
return "Approve report";
}
},
...mapState("missionStore", {
report_info: state => state.report,
}),
...mapGetters("missionStore", [
"memberById"
])
},
methods: {
checkAdmin: async function () {
this.isAdmin = await this.$auth.isAdmin(this.$options.name);
},
acceptRejectReport: async function () {
this.$store.dispatch('missionStore/acceptRejectReport',
{
caller: this.$options.name,
member_id: await this.$auth.getUserId(),
});
}
}
}
</script>
<style scoped>
</style>
already has the logic ... you just need to actually provide your img element as the last child inside that span. Your span will need to have css position:relative and your img needs css position:absolute;top:0;right:0; ... you might need display:inline-block on your span
<span v-if="report_info.accepted && report_info.accepted_by !== null"
style="position:relative;display:inline-block;">
{{ memberById(report_info.accepted_by).callsign }}
<img style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
width:100%;height:auto;" src="web path to img file"/>
</span>

before-leave transition JavaScript hook not working Vue js

My variable, transitionName, is not changing in the beforeLeaveHook. The value of transitionName remains to be 'right' when it should change to 'left'. Please help.
<template lang="pug">
transition(:name="transitionName" v-on:before-leave="beforeLeaveHook")
.componentMain
h1 {{transitionName}}
</template>
<script>
export default {
name: 'Component',
data () {
return {
transitionName: 'right',
}
},
methods: {
beforeLeaveHook: function(event){
this.transitionName = 'left';
}
}
}
</script>
The before-leave event won't fire until you remove the .componentMain element.
Here's an example:
new Vue({
el: '#app',
data () {
return {
transitionName: 'right',
show: true,
}
},
methods: {
beforeLeaveHook() {
this.transitionName = 'left';
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<button #click="show = !show">Toggle</button>
<transition :name="transitionName" v-on:before-leave="beforeLeaveHook">
<div class="componentMain" v-if="show">
<h1>
{{transitionName}}
</h1>
</div>
</transition>
</div>
Thanks for the help. I needed route specific transitions for my app. I solved the problem using beforeRouteUpdate(to, from, next) in the parent component to check the from and to paths before calling next(), in order to set the correct transition, using Vuex, and then using a computed property in my child component to listen for the transitionName change.

V-Html with Updated doesn’t work

I m trying to make a game.
When a player submit to the game, normally the welcome message appears ! But Nothing appears.
<template>
<div>
<span v-html = "welcomeMessage" v-hide></span>
<form v-hide v-on:submit.prevent="setPlayer">
<input name="player" placeholder="Enter the player name" v-border:blue/>
<button type="submit" v-border:red>Play</button>
</form>
</div>
</template>
<script>
export default {
name: 'player',
data: function () {
return {
player: '',
welcomeMessage: ''
}
},
updated: function () {
this.welcomeMessage = `Hi <span class="player">${this.player}</span> ! `
},
methods: {
setPlayer: function (event) {
this.player = event.target[0].value
}
},
directives: {
border: function (el, binding) {
el.style.borderColor = binding.arg
},
hide: function (el, binding, vnode) {
let isForm = vnode.tag === 'form'
let player = vnode.context.player
if (isForm) {
el.style.display = player ? 'none' : 'block'
} else {
el.style.display = player ? 'block' : 'none'
}
}
}
}
</script>
<style scoped>
</style>
it seems that doesn't work, i don't know why ! the name of theplayer is updated in the hook updated but the template doesn't show it.
Any clue.
Thxs.
Use v-model to bind input to data, this create a two binding on inputs. This can reduce a couple to code lines.
<template>
<div>
// show message only if new player created
<span v-html="welcomeMessage" v-show="playerCreated"></span>
<form v-on:submit.prevent="setPlayer" v-if="!playerCreated">
<input name="player" v-model="player" placeholder="Enter the player name" v-border:blue/> // bind input to `player` data property.
<button type="submit" v-border:red>Play</button>
</form>
</div>
</template>
In the component logic:
data: function () {
return {
player: '',
playerCreated: false,
welcomeMessage: ''
}
},
methods: {
setPlayer: function () {
this.playerCreated = true;
this.welcomeMessage = `Hi <span class="player">${this.player}</span> !`
}
},
You can use this, its worked for me in modal, when loading data to body
JSFiddle
<template id="some-template">
<div class="header">
<slot name="header"></slot>
<slot name="body"></slot>
<slot name="footer"></slot>
</div>
</template>
<div id="app">
<some>
<div slot="header" v-html="rawHtmlheader"></div>
<div slot="body" v-html="rawHtmlbody"></div>
<div slot="footer" v-html="rawHtmlfooter"></div>
</some>
</div>
JS logic
Vue.component('some', {
template: '#some-template'
})
new Vue({
el: '#app',
data: {
rawHtmlheader: '<p style="color:red">RED HEADER</p>',
rawHtmlbody: '<p style="color:green">GREEN TEXT</p>',
rawHtmlfooter: '<p style="color:brown">BROWN FOOTER</p>',
}
})

Categories

Resources