本篇文章由:http://xinpure.com/js-how-to-use-timer-press-event/
JS 原生事件並沒有長按事件,但是我們可以利用一些原有的事件,來實現長按事件
任務需求
最近在工作上遇到一個特殊的需求,就是需要實現長按來增加或者減少數值
這就類似於,購物車中的物品數量的加減按鈕,單擊按鈕物品數量相應增加或者減少一個數量,利用長按來實現快速增加或者減少物品數量

思考方法
在知道這個需求之后,開始還是比較茫然的
雖然在之前我也在一些購物 APP 里見到過這種長按的功能,但是在 JS 里似乎並沒有長按事件
后來我就在想,怎么樣利用現有的一些事件來實現這一功能呢?
這個時候我想到了 mousedown 和 mouseup 這兩個事件
當時我就想,如果在 mousedown 事件觸發的時候,利用 setTimeout 或者 setInterval 定時增加或者減少數值
然后在 mouseup 事件觸發的時候,利用 clearTimeout 或者 clearInterval 清除定時器
這樣是不是就能實現這樣的需求呢?
實踐想法
既然有了想法,就要付諸實踐
我寫了個例子來測試這個想法,結果卻並沒有想得這么簡單
當我通過鼠標按住按鈕之后,數值是不斷的增加或者減少,但是即使我松開鼠標,數值卻並沒有停止,而是依然為不斷的增加或者減少
這時我就疑惑了,理論上說,在 mouseup 事件觸發之后,定時器應該是已經被清除的,為何沒有清除呢?
帶着疑惑,我開始了 Google
Google 將我指引到了 Jquery Mobile 庫
這個類庫實現了一個 taphold 事件,就是我想要的長按事件
既然已經有了類似的實現,我就在想,是不是我哪里想錯了?
然后我就查看了 Jquery Mobile 關於 taphold 的源碼
看完源碼后,我驚喜的發現,原來他也是利用 setTimeout 來實現的這一事件,證明我的想法是對的!
帶着驚喜,我開始分析我思考的不足的地方。
最后我發現,原來是我沒有做好對事件的監聽
我只是單純的綁定了 mousedown 和 mouseup 兩個事件,這是我在 JS 事件處理上的不足
完善實現
知道了問題之后,我就開始修改之前寫的例子
采用 Jquery Mobile 庫對事件的處理方式,來實現這個長按的功能,並且也根據自身的需求進行修改
在修改的過程中,我發現了一個問題,就是當我長按一個按鈕的時候,如果我移動鼠標,長按事件也會一直持續下去,並且放開鼠標也不會停止
在翻看 JS 事件的之后,我找到了 mouseleave 這個事件,就是當鼠標離開按鈕之后,會觸發這個事件,加上之后,問題也得己解決。
為了兼容移動設備,我加上了對 touchstart 、touchend 、touchcencel 幾個事件的監聽
本來也想加上 touchleave 事件,來處理觸摸時用戶移動到按鈕外的情況,但是似乎這個事件已經被廢棄掉了:
This event was a proposal in an early version of the specification and has not been implemented. Do not rely on it. —— MDN
也嘗試了使用 touchmove 事件來代替,但是似乎會影響用戶體驗
因為添加了這個事件之后,就算是在按鈕上觸摸移動,也會觸發 touchmove 事件
所以如果是用戶誤操作的話,也會中止長按操作。
不過,touch 事件並不會像 mouse 事件一樣,觸摸移動到按鈕外之后再放開手指,事件還是可以正常處理,並不會影響使用
最終代碼
JS Code
var tapParams = {
timer: {},
element: {},
tapStartTime: 0,
type: 'increment'
};
function clearTapTimer() {
clearTimeout(tapParams.timer);
}
function clearTapHandlers() {
clearTapTimer();
$(tapParams.element).unbind('mouseup', clearTapTimer)
.unbind('mouseleave', clearTapHandlers);
/* 移動設備 */
$(tapParams.element).unbind('touchend', clearTapTimer)
.unbind('touchcencel', clearTapHandlers);
}
function tapEvent(aEvent, aType) {
/* 阻止默認事件並解除冒泡 */
aEvent.preventDefault();
aEvent.stopPropagation();
tapParams = {
element: aEvent.target,
startTime: new Date().getTime() / 1000,
type: aType
};
$(tapParams.element).bind('mouseup', clearTapTimer)
.bind('mouseleave', clearTapHandlers);
/* 移動設備 */
$(tapParams.element).bind('touchend', clearTapTimer)
.bind('touchcencel', clearTapHandlers);
changeNumber();
}
function changeNumber() {
var currentDate = new Date().getTime() / 1000;
var intervalTime = currentDate - tapParams.startTime;
/* 根據長按的時間改變數值變化幅度 */
if (intervalTime < 1) {
intervalTime = 0.5;
}
var secondCount = intervalTime * 10;
if (intervalTime == 3) {
secondCount = 50;
}
if (intervalTime >= 4) {
secondCount = 100;
}
var numberElement = $('.number');
var currentNumber = parseInt(numberElement.val());
if (tapParams.type == 'increment') {
currentNumber += 1;
} else if (tapParams.type == 'decrement') {
currentNumber -= 1;
}
numberElement.val(currentNumber <= 0 ? 1 : currentNumber);
tapParams.timer = setTimeout('changeNumber()', 1000 / secondCount);
}
HTML Code
<div class="container">
<div class="section">
<div class="decrement" onmousedown="tapEvent(event, 'decrement')" ontouchstart="tapEvent(event, 'decrement')">-</div>
<input class="number" value="1">
<div class="increment" onmousedown="tapEvent(event, 'increment')" ontouchstart="tapEvent(event, 'increment')">+</div>
</div>
</div>
CSS Code
.section {
display: -webkit-flex;
display: flex;
-webkit-justify-content: center;
justify-content: center;
-webkit-flex-flow: row nowrap;
flex-flow: row nowrap;
-webkit-align-items: center;
align-items: center;
height: 30px;
width: 130px;
font-size: 16px;
}
.number {
-webkit-flex: 1;
flex: 1;
width: 30px;
height: 30px;
border: 1px solid #000;
display: inline-block;
border-radius: 5px;
margin: 0 10px;
text-align: center;
}
.decrement, .increment {
width: 30px;
height: 30px;
border: 1px solid #000;
display: inline-block;
border-radius: 5px;
text-align: center;
line-height: 28px;
cursor: pointer;
font-size: 20px;
}
效果展示

