Have you ever wanted to execute a function in your Vue application just by holding a button down for a few seconds?
Have you ever wanted to create a button on your application that helps to clear out either a single input by pressing once (or a whole input holding a button down)?
You have? Good. Me too. And youâre in the right place.
This article will explain how to both execute functions and remove inputs by a press (or hold) of a button.
First, I will explain how to achieve this in VanillaJS. Then, create a Vue directive for it.
Buckle up. Iâm about to blow your mind.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
To achieve a long press, a user needs to press and hold the button for a few seconds.
To replicate this in code, we need to listen to when the mouse âclickâ button is pressed, start a timer for however long we want the user to hold down the button before executing the function, and execute the function after the time set has passed.
Pretty straightforward! However, we need to know when the user is holding down that button.
When a user clicks a button, two other events gets fired before the click event: mousedown and mouseup.
The mousedown event gets called when the user presses the mouse button, while the mouseup event gets called when the user releases that button.
All we need to do is:
mousedown event occursmouseup event gets fired before the 2secs mark, i.e., a full click eventAs long as the timer doesnât get cleared before it gets to that time weâve set â i.e., the mouseup event doesnât get fired â we can say that user hasnât released the button. Therefore, itâs considered a long press and we can then proceed to execute said function.
Letâs dive into the code and get this done.
Firstly, we have to define three things, namely:
This variable basically holds the value of the setTimeout so we can cancel this when a mouseup event occurs.
We are setting the variable to null just so we can check the variable to know if thereâs an active timer currently on before going ahead to cancel it.
This function consists of a setTimeout which, basically, is a method in Javascript that allows us to execute a function after a particular duration stated in the function.
Remember, in the process of creating a click event, two events gets fired. But what we need to start the timer is the mousedown event. Therefore, we do not need to start the timer if itâs a click event.
This function basically does what the name says, to cancel the setTimeout that was created when the start function got called.
To cancel the setTimeout, we would be using the clearTimeout method in JavaScript, which basically clears a timer set with the setTimeout() method.
Before using the clearTimeout we first need to check if the pressTimer variable is set to null. If itâs not set to null that means thereâs an active timer. So, we need to clear the timer and, you guessed it, set the pressTimer variable to null.
This function would be called once the mouseup event is fired.
Whatâs left is to add event listeners to the button you want to add the long press effect on.
addEventListener("mousedown", start);
addEventListener("click", cancel);
When creating a Vue directive, Vue allows us to define a directive globally or locally to a component, but in this article we would be going the global route.
Letâs build the directive that accomplishes this.
Firstly we have to declare the name of the custom directive.
This basically registers a global custom directive named v-longpress.
Next, we add the bind hook function with some arguments, which allows us to reference the element the directive is bound to, fetch the value that is passed to the directive, and identify the component the directive is used in.
Vue.directive('longpress', {
bind: function (el, binding, vNode) {
}
}
Next, we make add our long-press JavaScript code in the bind function.
Vue.directive('longpress', {
bind: function (el, binding, vNode) {
// Define variable
let pressTimer = null
// Define funtion handlers
// Create timeout ( run function after 1s )
let start = (e) => {
if (e.type === 'click' && e.button !== 0) {
return;
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
// Execute something !!!
}, 1000)
}
}
// Cancel Timeout
let cancel = (e) => {
// Check if timer has a value or not
if (pressTimer !== null) {
clearTimeout(pressTimer)
pressTimer = null
}
}
// Add Event listeners
el.addEventListener("mousedown", start);
// Cancel timeouts if this events happen
el.addEventListener("click", cancel);
el.addEventListener("mouseout", cancel);
}
})
Next, we need to add a function that would run the method that will be passed to the longpress directive.
Vue.directive('longpress', {
bind: function (el, binding, vNode) {
// Define variable
let pressTimer = null
// Define funtion handlers
// Create timeout ( run function after 1s )
let start = (e) => {
if (e.type === 'click' && e.button !== 0) {
return;
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
// Execute function
handler()
}, 1000)
}
}
// Cancel Timeout
let cancel = (e) => {
// Check if timer has a value or not
if (pressTimer !== null) {
clearTimeout(pressTimer)
pressTimer = null
}
}
// Run Function
const handler = (e) => {
// Execute method that is passed to the directive
binding.value(e)
}
// Add Event listeners
el.addEventListener("mousedown", start);
// Cancel timeouts if this events happen
el.addEventListener("click", cancel);
el.addEventListener("mouseout", cancel);
}
})
Now we can use the directive in our Vue application which will work fine until a user adds a value that isnât a function in the value of the directive. So we have to prevent this by warning the user once this happens.
To warn the user, we add the following to the bind function:
// Make sure expression provided is a function
if (typeof binding.value !== 'function') {
// Fetch name of component
const compName = vNode.context.name
// pass warning to console
let warn = `[longpress:] provided expression '${binding.expression}' is not a function, but has to be`
if (compName) { warn += `Found in component '${compName}' ` }
console.warn(warn)
}
Lastly, it would be great for this directive to also work on touch devices. So we add event listeners for touchstart, touchend, touchcancel.
Putting everything together:
Vue.directive('longpress', {
bind: function (el, binding, vNode) {
// Make sure expression provided is a function
if (typeof binding.value !== 'function') {
// Fetch name of component
const compName = vNode.context.name
// pass warning to console
let warn = `[longpress:] provided expression '${binding.expression}' is not a function, but has to be`
if (compName) { warn += `Found in component '${compName}' ` }
console.warn(warn)
}
// Define variable
let pressTimer = null
// Define funtion handlers
// Create timeout ( run function after 1s )
let start = (e) => {
if (e.type === 'click' && e.button !== 0) {
return;
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
// Run function
handler()
}, 1000)
}
}
// Cancel Timeout
let cancel = (e) => {
// Check if timer has a value or not
if (pressTimer !== null) {
clearTimeout(pressTimer)
pressTimer = null
}
}
// Run Function
const handler = (e) => {
binding.value(e)
}
// Add Event listeners
el.addEventListener("mousedown", start);
el.addEventListener("touchstart", start);
// Cancel timeouts if this events happen
el.addEventListener("click", cancel);
el.addEventListener("mouseout", cancel);
el.addEventListener("touchend", cancel);
el.addEventListener("touchcancel", cancel);
}
})
Now to reference in our Vue component:
<template>
<div>
<button v-longpress="incrementPlusTen" @click="incrementPlusOne">{{value}}</button>
</div>
</template>
<script>
export default {
data() {
return {
value: 10
}
},
methods: {
// Increment value plus one
incrementPlusOne() {
this.value++
},
// increment value plus 10
incrementPlusTen() {
this.value += 10
}
}
}
</script>
If you would love to know more about custom directives, the hook functions that are available, the arguments you can pass into this hook functions, and function shorthands, the great guys @vuejs have done a great job of explaining it here.
Cheers !!!
Debugging Vue.js applications can be difficult, especially when users experience issues that are difficult to reproduce. If youâre interested in monitoring and tracking Vue mutations and actions for all of your users in production, try LogRocket.

LogRocket lets you replay user sessions, eliminating guesswork by showing exactly what users experienced. It captures console logs, errors, network requests, and pixel-perfect DOM recordings â compatible with all frameworks.
With Galileo AI, you can instantly identify and explain user struggles with automated monitoring of your entire product experience.
Modernize how you debug your Vue apps â start monitoring for free.

:has(), with examplesThe CSS :has() pseudo-class is a powerful new feature that lets you style parents, siblings, and more â writing cleaner, more dynamic CSS with less JavaScript.

Kombai AI converts Figma designs into clean, responsive frontend code. It helps developers build production-ready UIs faster while keeping design accuracy and code quality intact.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the October 22nd issue.

John Reilly discusses how software development has been changed by the innovations of AI: both the positives and the negatives.
Would you be interested in joining LogRocket's developer community?
Join LogRocketâs Content Advisory Board. Youâll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now
5 Replies to "Building a long-press directive in Vue"
Really cool. Thanks
But click event will be triggered alongside longpress. Is there any approach how to prevent it?
Thanks a lot
Thanks for this well written post. I have a long-press button (a push-to-talk application that keeps the mic on only while the button is being held down) and it’s working fine, but one of the beta testers kept moving the mouse while holding it down so the mouse-up event often didn’t fire on the button itself but on a different component. Is that common and is there a workaround that could call a method on the longpress component even if the mouse-up occurs elsewhere on the page? (The component maintains its state so if it’s not in the “recording” state, that would be a no-op.)
Thank you for the post. When I handle the longpress event, it is getting triggered continuously as soon as I launch the app. Any idea what I am missing?