I'm looking for a way to control how many keydown events are registered in a given period of time when a key is held down. I have two animation functions, collapse() and expand() which collapse and expand a box when the down key is pressed. I've got it rigged so that the second animation is kicked off after the first. However, I have a timer, hovering(t) set within the first animation that is reset with every keypress so that the second animation doesn't begin until the key is released and the timer expires.
function collapse(){
if(h > 1 && arrayCount < myArray.length){
reqAnimFrame(collapse);
h -= 10;
clear();
draw();
} else {
arrayCount += 1;
h = 0;
clearHoverTimer();
hovering(250);
}
}
function expand(){
if(h < 100){
reqAnimFrame(expand);
h += 10;
clear();
draw();
} else {
h = 100;
clear();
draw();
}
}
Here's where my problem is: the first animation function also cycles through an array of strings via the arrayCount variable. When collapse() fires, the arrayCount increments by one. Unfortunately, when the key is held down, it fires off the collapse function in quick succession and the array is cycled through way too quickly.
Is it possible to restrict the key event timing so that say half the keys are registered?
I tried setting a variable heldDown to false, which would allow the keyEvent to register. The keyEvent would call collapse and start heldDownTimer. Once heldDownTimer expires, heldDown would be reset to false and the cycle would start over.
Set flags indicating the current state of your collapsing & expanding animation.
var doCollapsing indicates if the collapsing code should animate.
var doExpanding indicates if the expanding code should animate.
In your keydown handler, you can ignore 'extra' keydown by only setting the flags when they indicate the animation loop is idle.
// listen for keydown and doCollapsing only if the animation is currently idle
if(!doCollapsing && !doExpanding){
doCollapsing=true; // start collapsing
doExpanding=false; // don't expand until a keyup fires
}
This will cause collapse+expand to execute once instead of being triggered with every keydown.
// variables indicating the state of the animation
var doCollapsing=false;
var doExpanding=false;
// listen for keydown events
document.addEventListener("keydown",handleKeydown,false);
// listen for keyup events
document.addEventListener("keyup",handleKeyup,false);
// start the animation loop
requestAnimationFrame(animate);
// handle keydown events
function handleKeydown(e){
// listen for keydown
// doCollapsing only if the animation is idle
if(!doCollapsing && !doExpanding){
doCollapsing=true;
doExpanding=false;
}
}
// handle keyup events
function handleKeyup(e){
doExpanding=true;
}
// the animation loop
function animate(time){
requestAnimationFrame(animate);
if(doCollapsing){
// Do collapsing until done
// When done: doCollapsing=false;
}else if(doExpanding && hoveringTimerHasExpired){
// Do expanding until done
// When done: doExpanding=false;
}
}
Related
So I have worked hard to get this code correct thus far. Basically my click event makes my shapes DECAY gradually start being affected. It works perfectly as I wanted. But my question is when I let go of holding down my mouse or finger it automatically jumps back to the original frame. Can I please get some help on how to make it gradually go back (or gradually end) like how it starts? that way its a fluid animation from start to finish.
Here's my Click event code
decaybackup = config.shader.decay;
world.resize()
});
let interval='';
let myshape = document.getElementById('shapeId');
myshape.addEventListener('pointerdown', function(event) {
interval = setInterval(()=>{
config.shader.decay += .001;
},1)
});
myshape.addEventListener('pointerup', function(event) {
config.shader.decay = decaybackup;
clearInterval(interval)
interval = '';
});
also here is a read only link to my site if you need a visual of what im talking about and can also see any code I have added...
enter link description here
THANK YOU!!!
Use setInterval() to decrement decay back to decaybackup similar to the way you increment it during pointerdown.
let undecayInterval;
myshape.addEventListener('pointerup', function(event) {
config.shader.decay = decaybackup;
clearInterval(interval);
clearInterval(undecayInterval);
undecayInterval = setInterval(() => {
config.shader.decay -= 0.001;
if (config.share.decay <= decaybackup) {
clearInterval(undecayInterval);
}, 1);
}
});
Introduction
I have a page that has full screen fixed blocks and I would like to navigate these blocks using the javascript wheel event.
If that doesn't make sense; to put it simply, I have a custom carousel that I would like to navigate using the mouse wheel one block at a time i.e one scroll roll/click moves to the previous/next item.
My current solution works, but it needs a little tweaking to make it work better.
The Issue
The problem I am having is that 1 roll/click on my mouse wheel sometimes scrolls one block, and sometimes scrolls 2 blocks.
The algorithim I am using is as follows:
For every fired wheel event, increment/decrement the global variable deltaY value by 1 (my mouse wheel tends to fire around 35-40 events each time the wheel is scrolled when in click mode, not sure if this is standard)
Whenever the deltaY value changes, determine which block (by index) should be active. I do this with the following; Math.floor(deltaY / 35) where 35 is the number of wheel events fired by 1 roll of my mouse wheel (very unsure about this as it may differ for other mice)
Update the active index and update deltaY with an integer divisible by 35
The Code
I am using Vue.js but not to worry if you are not familiar with it, all I really need is the algorithm and I can implement it into my code.
Vanilla JS
I haven't tested this code, I simply mocked it up based on my Vue.js code for those of you that are not familiar with Vue.
// Set the default values
let items = ['x', 'y', 'z'];
let active = 0;
let deltaY = 0;
/**
* Update the delta value and any other relevant
* values.
*
* #param {event} event
* #return void
*/
function updateDelta (value) {
active = Math.floor(value / 35);
deltaY = active * 35;
}
// Register the `wheel` event
window.addEventListener('wheel', (event) => {
// The navigation is only active when the page has not
// been scrolled
if (document.documentElement.scrollTop === 0) {
// If the last item is currently active then we do not need to
// listen to `down` scrolls, or, if the first item is active,
// then we do not need to listen to `up` scrolls
if (
(event.deltaY > 0 && (active - 1) === items.length)
|| (event.deltaY < 0 && deltaY === 0)
) {
return;
}
updateDelta(Math.sign(event.deltaY));
}
}, { passive: true });
My Code (Vue JS)
I have added clear comments to illustrate what Vue is doing
export default {
data() {
return {
items: ['x', 'y', 'z'],
active: 0, // Define the active index
deltaY: 0 // This is used for the scroll wheel navigation
}
},
created() {
// Register the `wheel` event
window.addEventListener('wheel', this._handleWheel, { passive: true });
},
destroyed() {
// Remove the `wheel` event
window.removeEventListener('wheel', this._handleWheel, { passive: true });
},
watch: {
// I have added watchers to both `deltaY` and `active`, however,
// this may not be necessary. These will not create an endless loop
// because the watcher is only called when a value is changed
active: function(index) {
// Whenever the `active` index changes, update the `deltaY` value
this.deltaY = index * 35;
},
deltaY: function(value) {
// Whenever the `deltaY` value changes, update the `active` index
this.active = Math.floor(value / 35);
}
},
methods: {
/**
* Handle the window wheel event.
*
* #param {event} event
* #return void
*/
_handleWheel (event) {
// The navigation is only active when the page has not
// been scrolled
if (document.documentElement.scrollTop === 0) {
// If the last item is currently active then we do not need to
// listen to `down` scrolls, or, if the first item is active,
// then we do not need to listen to `up` scrolls
if (
(event.deltaY > 0 && (this.active - 1) === this.items.length)
|| (event.deltaY < 0 && this.deltaY === 0)
) {
return;
}
this.deltaY += Math.sign(event.deltaY);
}
}
}
}
As you already pointed out yourself, the number of times a wheel event fires may vary from mice to mice and from time to time. Thus, relying on the number of events as a way to determine when to shift a block will always be unpredictable.
Instead, I suggest to try an alternative approach. For example, you could try to detect when the user stops scrolling, and just increment your index by 1. A quick Google search returned this helper function, which does just that.
I create a square div, and I want to know how many pointer is triggering pointer down event in the div on the screen; there is no property like event.touches.length in pointer event, so I use a counter variable to calculate the number of finger down (ie pointer down event) inside the div, and achieve these conditions:
1. Pointer down inside the div, counter plus 1.
3. Pointer down inside the div (counter plus 1), and release pointer inside the div, counter minus 1.
3. Pointer down inside the div (counter plus 1), move outside the div, and release outside the div, counter still minus 1.
4. Pointer down and pointer up outside the div (click outside), counter does nothing.
My program is like this:
var elem, counter,
onDown, onUp,
animate;
counter = 0;
onDown = function () {
var body;
body = document.body;
counter++;
// body.addEventListener('mouseup', onUp, false);
body.addEventListener('pointerup', onUp, false);
};
onUp = function () {
var body;
body = document.body;
counter--;
// body.removeEventListener('mouseup', onUp);
body.removeEventListener('pointerup', onUp);
};
elem = document.getElementById('square');
//elem.addEventListener('mousedown', onDown, false);
elem.addEventListener('pointerdown', onDown, false);
animate = function () {
// Use different color according to the number of fingers
// How many fingers are trigger pointer down inside the div,
// and not released yet.
// (Include the finger which is moved outside after
// pointer down inside the div)
switch (counter) {
case 1:
elem.style.backgroundColor = 'red';
break;
case 2:
elem.style.backgroundColor = 'yellow';
break;
case 3:
elem.style.backgroundColor = 'blue';
break;
case 4:
elem.style.backgroundColor = 'black';
break;
default:
elem.style.backgroundColor = 'white';
}
requestAnimationFrame(animate);
};
animate();
#square {
width: 300px;
height: 300px;
border: 1px solid black;
}
<div id="square"></div>
In general, we only use one mouse, so there is no problem occurred.
But there is a problem occurred in pointer event. For example, my index finger of left hand touch down inside the div, the pointer down event is triggered, counter is 1 now; but my index finger of right hand "click" outside the div, pointer up event is triggered here, so my counter get a wrong number 0. (My left index finger is still pressed)
I don't want click event occurred outside of the div (pointerdown and pointerup are both fired outside of the div) to affect my counter.
If there is a way to determine my pointer up event is correlated to which pointer down event?
Just like the event target of touch start event and touch end event is the same, so that I can know they are related.
Thanks in advance.
Since the question is old I'll keep this brief, but I think you might be able to solve your problem by keeping track of the different pointerIds you get in the different pointer events. Also have a look at this MSDN page, specifically at this code snippet:
function pointerdownHandler (evt) {
evt.target.setPointerCapture(evt.pointerId);
}
I'd like to detect in a web page when the user selects some text by dragging. However, there's one scenario in Windows which I'm calling a "double-click-drag" (sorry if there's already a better name I don't know) and I can't figure out how to detect it. It goes like this:
press mouse button
quickly release mouse button
quickly press mouse button again
drag with the button held down
This causes the dragging to select whole Words. It's quite a useful technique from the user perspective.
What I'm trying to do is tell the difference between a double-click-drag and a click followed by a separate drag. So when I get to step 2 I will get a click event but I don't want to treat it as a click yet; I want to see if they're about to immediately do step 3.
Presumably Windows detects this on the basis of the timing and how much the mouse has moved between step 2 and 3, but I don't know the parameters it uses so I can't replicate the windows logic. note that even if the mouse doesn't move at all between step 2 and 3, I still get a mousemove event.
I realise that I should be designing interfaces that are touch-friendly and device-neutral, and I have every intention of supporting other devices, but this is an enterprise application aimed at users on windows PCs so I want to optimize this case if I can.
We've done something similar. Our final solution was to create a click handler that suppressed the default response, and then set a global variable to the current date/time. We then set another function to fire in some 200ms or so that would handle the "click" event. That was our base function.
We then modified it to look at the global variable to determine when the last click occured. If it's been less than 200ms (modify based on your needs) we set a flag that would cause the click handler to fizzle and called a double click handler.
You could extend that approach by having your click and double click handlers manually fire the drag functionality.
I don't have access to the aforementioned code right now, but here is an example of that framework being used to track keyboard clicks to determine if a scanner or user has finished typing in a field:
var lastKeyPress = loadTime.getTime();
// This function fires on each keypress while the cursor is in the field. It checks the field value for preceding and trailing asterisks, which
// denote use of a scanner. If these are found it cleans the input and clicks the add button. This function also watches for rapid entry of keyup events, which
// also would denote a scanner, possibly one that does not use asterisks as control characters.
function checkForScanKeypress() {
var iVal = document.getElementById('field_id').value;
var currentTime = new Date()
var temp = currentTime.getTime();
if (temp - lastKeyPress < 80) {
scanCountCheck = scanCountCheck + 1;
} else {
scanCountCheck = 0;
}
lastKeyPress = currentTime.getTime();
}
// The script above tracks how many successive times two keyup events have occurred within 80 milliseconds of one another. The count is reset
// if any keypress occurs more than 80 milliseconds after the last (preventing false positives from manual entry). The script below runs
// every 200 milliseconds and looks to see if more than 3 keystrokes have occurred in such rapid succession. If so, it is assumed that a scanner
// was used for this entry. It then waits until at least 200 milliseconds after the last event and then triggers the next function.
// The 200ms buffer after the last keyup event insures the function is not called before the scanner completes part number entry.
function checkForScan() {
var currentTime = new Date();
var temp = currentTime.getTime();
if (temp - lastKeyPress > 200 && scanCountCheck > 3) {
FiredWhenUserStopsTyping();
scanCountCheck = 0;
}
setTimeout(checkForScan, 200);
}
Here is some code that I just wrote up based upon the above ideas. It's not tested and doesn't contain the actual drag events, but should give you a good starting point:
var lastClick = loadTime.getTime();
function fireOnClickEvent(event) {
event.preventDefault;
var currentTime = new Date()
var temp = currentTime.getTime();
if (temp - lastClick < 80) {
clearTimeout(tf);
doubleClickHandler();
} else {
tf = setTimeout(singleClickHandler, 100);
}
lastClick = currentTime.getTime();
}
function singleClickHandler() {
// Begin normal drag function
}
function doubleClickHandler() {
// Begin alternate drag function
}
A single double-click-drag action involves the following events in sequence:
mousedown -> mouseup -> click -> mousedown -> mousemove
With that in mind, I came up with this simple solution:
let maybeDoubleClickDragging = false;
let maybeDoubleClickDraggingTimeout;
const element = document.querySelector('#container');
element.addEventListener("click", function (e) {
maybeDoubleClickDragging = true;
element.removeEventListener("mousemove", handleMousemove);
});
element.addEventListener("mousedown", (e) => {
element.addEventListener("mousemove", handleMousemove);
if (maybeDoubleClickDragging) {
clearTimeout(maybeDoubleClickDraggingTimeout);
return;
}
});
element.addEventListener("mouseup", (event) => {
maybeDoubleClickDraggingTimeout = setTimeout(() => {
maybeDoubleClickDragging = false;
}, 200);
});
function handleMousemove(e) {
if(maybeDoubleClickDragging) {
element.textContent = 'you are double-click-dragging'
}
}
#container {
width: 300px;
height: 300px;
background: yellow;
}
<div id="container"></div>
If a user taps (touchstart) outside a popup-div, I want to hide the popup.
But if the user's intent is to scroll/swipe (touchmove), I don't want to hide the popup.
How could the code look like to detect and respond to those two actions (with or without jQuery)?
Here is a basic example of how you could do this:
http://jsfiddle.net/4CrES/2/
The logic behind it involves detecting the initial touch time and saving it to a var
touchTime = new Date();
In the touchend handler subtract this time from the current time to get the difference:
var diff = new Date() - touchTime;
Use an if statement to decide whether the touch duration was short enough to consider it a tap, or long enough to consider it a drag.
if (diff < 100){
//It's a tap
}
else {
//Not a quick tap
}
You could write a more robust implementation by doing a similar difference of the initial touch y position to the final touch y position in the handlers. Another option is to compare the scrollTop of the scrolling area to see if it has been scrolled.
Since click events do not bubble up the DOM on mobile Safari while touch events and custom events do, I recently wrote some code to detect a quick-tap.
It's a quick-tap when
The touch event starts and ends without any movement along the screen
No scrolling occurrs
It all happens in less than 200ms.
If the touch is determined to be a 'quickTap', the TouchManager causes the touched element in the DOM to emit a custom "quickTap" event which then bubbles up the DOM to any other elements that happen to be listening for it. This code defines and creates the touch manager and it will be ready to go immediately
Drawbacks:
Uses jQuery
Only designed with one finger in mind.
also borrowed some code from modernizr. (You can omit that bit if you already include Modernizr.)
Maybe this is overkill, but it's part of a larger codebase I'm working on.
/**
* Click events do not bubble up the DOM on mobile Safari unless the click happens on a link or form input, but other events do bubble up.
* The quick-tap detects the touch-screen equivalent of a click and triggers a custom event on the target of the tap which will bubble up the DOM.
* A touch is considered a click if there is a touch and release without any movement along the screen or any scrolling.
*/
var qt = (function ($) {
/**
* Modernizr 3.0.0pre (Custom Build) | MIT
* Modernizr's touchevent test
*/
var touchSupport = (function() {
var bool,
prefixes = ' -webkit- -moz- -o- -ms- '.split(' ')
if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
bool = true;
} else {
var query = ['#media (',prefixes.join('touch-enabled),('),'heartz',')','{#modernizr{top:9px;position:absolute}}'].join('');
testStyles(query, function( node ) {
bool = node.offsetTop === 9;
});
}
return bool;
}()),
MobileTapEvent = 'tapEvent';
if(touchSupport) {
/* Create a new qt (constructor)*/
var startTime = null,
startTouch = null,
isActive = false,
scrolled = false;
/* Constructor */
function qt() {
var _qt = this,
context = $(document);
context.on("touchstart", function (evt) {
startTime = evt.timeStamp;
startTouch = evt.originalEvent.touches.item(0);
isActive = true;
scrolled = false;
})
context.on("touchend", function (evt) {
window.ct = evt.originalEvent['changedTouches'];
// Get the distance between the initial touch and the point where the touch stopped.
var duration = evt.timeStamp - startTime,
movement = _qt.getMovement(startTouch, evt.originalEvent['changedTouches'].item(0)),
isTap = !scrolled && movement < 5 && duration < 200;
if (isTap) {
$(evt.target).trigger('quickTap', evt);
}
})
context.on('scroll mousemove touchmove', function (evt) {
if ((evt.type === "scroll" || evt.type === 'mousemove' || evt.type === 'touchmove') && isActive && !scrolled) {
scrolled = true;
}
});
}
/* Calculate the movement during the touch event(s)*/
qt.prototype.getMovement = function (s, e) {
if(!s || !e) return 0;
var dx = e.screenX - s.screenX,
dy = e.screenY - s.screenY;
return Math.sqrt((dx * dx) + (dy * dy));
};
return new qt();
}
}(jQuery));
To use the code you would add it to your page then just listen for the quickTap event.
<script type="text/javascript" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script type="text/javascript" src="quick-tap.js"></script>
<script type="text/javascript">
$(document).on('quickTap', function(evt, originalEvent) {
console.log('tap event detected on: ', evt.target.nodeName, 'tag');
});
</script>
evt is the quickTap event.
evt.target is the tapped DOM element (not the jQuery object).
originalEvent is the touchend event where qt determines whether it was a tap or not.
You can hide the popup-div on touchend event.
In touchstart event you remember window.scrollY.
In touchend event, if scrollY positions differ the user has scrolled.