JavaScript touch events contain properties for radius and force. Unfortunately it appears that events aren't generated when either property changes. Events are only triggered for things like touch start, move or end. Can anyone think of a way to get more updates on change of touch size?
Currently to get radius updates I have to wiggle my finger to trigger the touch move event, but I would prefer a software solution.
I had the same issue and then discovered this blog post: http://blog.framerjs.com/posts/prototyping-3D-touch-interactions.html
In a nutshell, we need to use touchstart event to capture the touch event, assign the event to a variable and then use setInterval to get the force value:
var el = document.getElementById('myElement');
var currTouch = null;
var currTouchInterval = null;
attachListeners = function () {
el.addEventListener('touchstart', enterForceTouch, false);
el.addEventListener('touchend', exitForceTouch, false);
}
enterForceTouch = function (evt) {
evt.preventDefault();
currTouch = evt;
currTouchInterval = setInterval(updateForceTouch, 10); // 100 times per second.
}
updateForceTouch = function() {
if (currTouch) {
console.log(currTouch.touches[0].force); // Log our current force value.
}
}
exitForceTouch = function (evt) {
evt.preventDefault();
currTouch = null;
clearInterval(currTouchInterval);
}
attachListeners();
Related
I'm making a web app where a button's behavior is different if the user clicks vs holds the button. I have been experimenting with different timings and it got me wondering if there is any established standard for this kind of thing.
For clarification: I am wondering if there is an exact timing that is standard. Below is the code I am using with 150ms being the threshold for a hold.
function onMouseDown()
{
var holdTimeout = setTimeout(function()
{
//Hold code (also cancels click event)
}, 150);
var cancelHold = function()
{
clearTimeout(holdTimeout);
};
window.onmouseup = cancelHold;
}
function onClick()
{
//Click code
}
Answering exactly your question, hold becomes click. You could set the click event (it's release in fact), inside the mousedown event. Run the code below and try holding and release the mouse button.
document.getElementById("click").addEventListener('mousedown', (e) => {
var i = 0;
var int = setInterval(() => {
console.log("hold " + i++);//<-- actions when we hold the button
}, 200)
document.getElementById("click").addEventListener("click", () => {
clearInterval(int);
console.log("release")//<-- actions when we release the button
})
});
<div id="click">click</div>
In this case, if we hold the button less that 200 milliseconds, just the click (release) event is fired.
Is there a way to know all the events that happened on document in JavaScript, without going through them one by one?
Instead of running through all events and doing this:
document.onDocumentContentLoaded = function(e) {
documentLoaded = true
};
document.onmousewheel = function(e) {
mouseWheel = true;
};
Is there a function I can call last thing in a page that gives me an array of all events that were called on a certain DOM (document in this case)?
$(window).on("beforeunload", function() {
//var events = document.GetAllEvents();
});
I am building a javascript game, and i want to create a background music based on sound file snippets. Short mp3 files to play them as one continuous track. I have tried binding an "ended" event handler on the audio file, though this causes a delay between audio fragments.
To solve this I made a hacky solution that still does not work, changing the audio 1 second before it finishes.
Ebuc.manageAudio = function(){
var listener = function (event) {
if (this.currentTime > (this.duration - 1) && Ebuc.bgnext) {
Ebuc.manageAudio();
console.log("aduio");
Ebuc.bgnext = false;
}
if(this.currentTime < 2){
Ebuc.bgnext = true;
console.log("reset");
}
console.log(event);
console.log("listener active")
};
var color = Level.current.color;
if(Ebuc.bgsong == null) {
Ebuc.bgsong = new Audio('assets/sound/' + Resources.audioSetList[color].getcurrentsong());
Ebuc.bgsong.addEventListener('timeupdate', listener, true);
}
else{
Ebuc.bgsong = new Audio('assets/sound/' + Resources.audioSetList[color].getcurrentsong());
}
Ebuc.bgsong.play();
Resources.audioSetList[color].next();
};
This sample works once, when it is time to switch fragment 2 to fragment 3 the loop stops. Console logging the event listener gives 4 times a log before stopping.
Q1: Why is this eventlistener suddenly disappearing?
Q2: Is there a non hack solution for chaining these audio fragments.
I thank you in advance.
You're going to have more than just pausing issues trying to rapidly switch between two short audio clips, you're going to probably want to crossfade between the two audio tracks quickly as well to prevent any popping, artifacts, etc.
Here's an example of crossfading using howler from howler's github issues. You could probably use this example, and keep a queue of loaded instances to transition to. I hope that helps.
//you'll probably want this crossfade duration to be shorter.
var crossfadeDuration = 5000,
volume = 0.7;
var instance1, instance2, soundDuration;
// Singleton helper to build similar instances
var createHowlerInstance = function (urls, onload) {
return new Howl({
urls: urls,
loop: false,
volume: 0,
onload: onload
});
};
// Create "slave" instance. This instance is meant
// to be played after the first one is done.
instance2 = createHowlerInstance(['file2.mp3']);
// Create "master" instance. The onload function passed to
// the singleton creator will coordinate the crossfaded loop
instance1 = createHowlerInstance(['file1.mp3'], function(){
// Get the sound duration in ms from the Howler engine
soundDuration = Math.floor(instance1._duration * 1000);
(function crossfadedLoop(enteringInstance, leavingInstance){
// Fade in entering instance
enteringInstance.pos(0).play().fade(0, volume, crossfadeDuration);
// Wait for the audio end to fade out entering instance
// white fading in leaving instance
setTimeout(function(){
enteringInstance.fade(volume, 0, crossfadeDuration);
crossfadedLoop(leavingInstance, enteringInstance);
}, soundDuration - crossfadeDuration);
})(instance1, instance2);
});
By using the idea of setting a timeOut in the answer of pantalohnes I have created the following code to solve the gap:
Ebuc.manageAudio = function(){
var color = Level.current.color;
Ebuc.bgsong = new Audio('assets/sound/' + Resources.audioSetList[color].getcurrentsong());
Ebuc.bgsong.addEventListener("loadedmetadata",function(){
setTimeout(Ebuc.manageAudio, (Ebuc.bgsong.duration * 1000) - 50);
Ebuc.bgsong.play();
console.log(Ebuc.bgsong.duration);
Resources.audioSetList[color].next();
});
};
The 50 milliseconds timeout bridges the gap between the sequenced files exactly.
Answering your question (although I see you found another solution), I think I found your bug:
The second time your enter Ebuc.manageAudio(), Ebuc.bgsong is already set and you just create a new Audio Ebuc.bgsong = new Audio(...) without attaching the listener to it, so you're not being notified for any 'timeupdate' events emitted when playing the second audio file.
You should also remove the listener from the previous playing audio.
So, if all else is ok, I think this should fix it:
Ebuc.manageAudio = function(){
var listener = function (event) {
if (this.currentTime > (this.duration - 1) && Ebuc.bgnext) {
Ebuc.manageAudio();
console.log("aduio");
Ebuc.bgnext = false;
}
if(this.currentTime < 2){
Ebuc.bgnext = true;
console.log("reset");
}
console.log(event);
console.log("listener active")
};
var color = Level.current.color;
if(Ebuc.bgsong != null) {
Ebuc.bgsong.removeEventListener('timeupdate', listener, true);
}
Ebuc.bgsong = new Audio('assets/sound/' + Resources.audioSetList[color].getcurrentsong());
Ebuc.bgsong.addEventListener('timeupdate', listener, true);
Ebuc.bgsong.play();
Resources.audioSetList[color].next();
};
More than that, I think that if you properly remove the listener from the previous playing audio, you won't need that bgnext hack at all:
var listener = function (event) {
if (this.currentTime > (this.duration - 1)) {
Ebuc.manageAudio();
console.log("aduio");
}
console.log(event);
console.log("listener active")
};
Ebuc.manageAudio = function () {
var color = Level.current.color;
if (Ebuc.bgsong != null) {
Ebuc.bgsong.removeEventListener('timeupdate', listener, true);
}
Ebuc.bgsong = new Audio('assets/sound/' + Resources.audioSetList[color].getcurrentsong());
Ebuc.bgsong.addEventListener('timeupdate', listener, true);
Ebuc.bgsong.play();
Resources.audioSetList[color].next();
};
Let me know if that worked :)
I’m using FastClick on a page with large links because I want to bypass the 300ms delay for taps in mobile browsers. I have a “highlight” style for the links’ :active states and it is properly firing quickly, thanks to FastClick.
My problem is that – in Mobile Safari at least – it also fires while you’re tapping and swiping to scroll the page. This makes it feel like you can’t scroll the page without it thinking you’re trying to tap the links.
Is there a way to prevent it from firing when someone scrolls?
Maybe you can add the needsclick class to the body?
<body class="needsclick">
...
</body>
Just an idea :)
Nice question! +1
This problem has nothing to do with FastClick, but FastClick does make the solution to your problem more complex. So I will stick to pure JavaScript and raw Touch Events )
On a mobile touch device the implementation of the webview is significantly different from the desktop web browser for reasons specific to the platform.
One feature that is important in this case is momentum scrolling in the webview.
Momentum Scrolling is a hardware accelerated feature of the device. So when a scrollable area is touched on the screen, the hardware identifies the touch and attaches a 200 millisecond countdown timer to the scrollable area, that when triggered, puts the area into a hardware accelerated scroll. When the hardware is in the state it does not broadcast touch events because they are specific to the hardware accelerated scroll. The timer can be cancelled, and momentum scrolling prevented by using preventDefault on the touch event within the provided 200 milliseconds.
Here is how I approach this problem. I assume you are using native scroll i.e overflow:scroll.
The method is to attach a touchstart event to the element you want touchable.
Once the touchstart event has fired, the event handler attaches touchend, touchmove and touchcancel events to the target element. A setTimout timer is initiated that removes the newly added events after 140ms.
Here are some clips for my production code:
I have two event methods for adding and removing events from collections:
var eventify = (function () {
function eventify(type, el, callback, phase) {
phase = phase || false;
if ((el.constructor.toString().contains('NodeList')) || (el.constructor.toString().contains('HTMLCollection'))) {
[].forEach.call(el, function (element) {
if (!element.hasEvent(type)) {
element.addEvent(type);
HTMLElement.prototype.addEventListener.apply(element, [type, callback, phase]);
}
});
} else {
if (!el.hasEvent(type)) {
el.addEvent(type);
HTMLElement.prototype.addEventListener.apply(el, [type, callback, phase]);
}
}
return callback;
}
return eventify
})();
var uneventify = (function () {
function uneventify(type, el, callback) {
if ((el.constructor.toString().contains('NodeList')) || (el.constructor.toString().contains('HTMLCollection'))) {
[].forEach.call(el, function (element) {
if (element.hasEvent(type)) {
element.removeEvent(type);
HTMLElement.prototype.removeEventListener.apply(element, [type, callback]);
}
});
} else {
if (el.hasEvent(type)) {
el.removeEvent(type);
HTMLElement.prototype.removeEventListener.apply(el, [type, callback]);
}
}
}
return uneventify
})();
Then I have a tapify method for tap events on the scroller:
var tapify = (function () {
function tapify(el, callback) {
eventify('touchstart', el, function (e) {
var that = this;
var start = e.pageY;
var target = e.target;
function dynamicEvents() {
var endfn = eventify('touchend', target, function (evt) {
e.preventDefault();
e.stopImmediatePropagation();
evt.preventDefault();
evt.stopImmediatePropagation();
uneventify('touchmove', target, movefn);
uneventify('touchend', target, endfn);
uneventify('touchcancel', target, cancelfn);
callback && callback(target);
});
var cancelfn = eventify('touchcancel', target, function (evt) {
e.preventDefault();
e.stopImmediatePropagation();
evt.preventDefault();
evt.stopImmediatePropagation();
uneventify('touchmove', target, movefn);
uneventify('touchend', target, endfn);
uneventify('touchcancel', target, cancelfn);
callback && callback(target);
});
var movefn = eventify('touchmove', target, function (evt) {
var distance = start - evt.pageY;
if (distance > 20) {
uneventify('touchend', target, endfn);
uneventify('touchcancel', target, cancelfn);
uneventify('touchmove', el, movefn);
}
});
setTimeout(function () {
uneventify('touchmove', target, movefn);
uneventify('touchend', target, endfn);
uneventify('touchcancel', target, cancelfn);
}, 140);
}
if (global.isIos) setTimeout(function () {
dynamicEvents();
}, 60);
else dynamicEvents();
}, false);
}
return tapify;
})();
I use global.isIos to identify the target device. Android stops sending touch events to webview ater 200ms.
Then to attach the event to a element or a collection of elements, use :
tapify(document.querySelectorAll('button'), function (e) {
//your event handler here!!
});
Hope this helps
There is a good example for doing long press in Javascript here: Long Press in JavaScript?
But it does not provide for knowing the duration of the press.
If I want to do different things based on the length of the press I cant use the pattern in that post.
I was trying to do something similar by saving current time in a variable on('mousedown')
and then calculating the time difference on('mouseup').
this works fine within a normal Javasript page in a "normal" browser.
However within my phonegap app something happens,
looks like the mouseup event is not being called if the finger is kept on the screen for a long duration (say 5 sec..).
Is this some native mobile browser behavior? Can I override it somehow?
I am using plain jQuery not jQuery mobile.
Any ideas anyone?
You could have a look at how the taphold and vmouseup (handleTouchEnd() line 752) events are implemented in jQuery mobile source code.
Since it is already tested and implemented I'd suggest to use jquery mobile instead of jquery and modify (since it already handles all the 'quirks' related each mobile browser), and change the code as you need.
You can check the time to identify Click or Long Press [jQuery]
function AddButtonEventListener() {
try {
var mousedowntime;
var presstime;
$("button[id$='" + buttonID + "']").mousedown(function() {
var d = new Date();
mousedowntime = d.getTime();
});
$("button[id$='" + buttonID + "']").mouseup(function() {
var d = new Date();
presstime = d.getTime() - mousedowntime;
if (presstime > 999/*You can decide the time*/) {
//Do_Action_Long_Press_Event();
}
else {
//Do_Action_Click_Event();
}
});
}
catch (err) {
alert(err.message);
}
}
Note that this solution is usefull if you do not use jQuery Mobile for some reason.
I used the article Fast Touch Event Handling and just added a piece of code
$.event.special.tap = {
distanceThreshold: 10,
timeThreshold: 350,
setup: function () {
var self = this,
$self = $(self);
// Bind touch start
$self.on('touchstart', function (startEvent) {
// Save the target element of the start event
var target = startEvent.target,
touchStart = startEvent.originalEvent.touches[0],
startX = touchStart.pageX,
startY = touchStart.pageY,
threshold = $.event.special.tap.distanceThreshold,
timeout,
expired = false;
function timerFired() {
expired = true;
}
function removeTapHandler() {
clearTimeout(timeout);
$self.off('touchmove', moveHandler).off('touchend', tapHandler).off('touchcancel', removeTapHandler);
};
function tapHandler(endEvent) {
removeTapHandler();
if (target == endEvent.target) {
if (expired) {
$.event.simulate('longtap', self, endEvent);
} else {
$.event.simulate('tap', self, endEvent);
}
}
};
// Remove tap and move handlers if the touch moves too far
function moveHandler(moveEvent) {
var touchMove = moveEvent.originalEvent.touches[0],
moveX = touchMove.pageX,
moveY = touchMove.pageY;
if (Math.abs(moveX - startX) > threshold || Math.abs(moveY - startY) > threshold) {
removeTapHandler();
}
};
// Remove the tap and move handlers if the timeout expires
timeout = setTimeout(timerFired, $.event.special.tap.timeThreshold);
// When a touch starts, bind a touch end and touch move handler
$self.on('touchmove', moveHandler).on('touchend', tapHandler).on('touchcancel', removeTapHandler);
});
}
};
So, now I have a tap and a longtap events