I listened for the mouse wheel event in the code and executed a rendering of WebGL each time the wheel event was triggered. However, there was one problem that could not be solved: when scrolling, frames were often lost.
I'm making a map app, and the mouse scroll triggers the map to zoom in and out.
// Render function call:
public requestRender() {
if (!this._waitingRender) {
this._waitingRender = requestAnimationFrame(
this._renderFrame.bind(this),
);
}
}
// Render function defined
private _renderFrame() {
if (!this._styleLoaded) {
cancelAnimationFrame((this._waitingRender = 0));
this._waitingRender = 0;
return;
}
const frameState = this._getState();
this._sourceManager.loadData(
frameState.viewState,
this,
this._mapRender.context,
);
this._mapRender.renderFrame(frameState);
this._waitingRender = 0;
this._diffState();
return;
}
This is my performance:
Related
I'm running into a bit of an odd issue with Paper.js - I'm using the library to scale the "petals" of a randomly generated flower while audio plays.
The issue crops up if the flower is "growing" and the user navigates to a different tab in the browser. Even though it appears that the onFrame event is not firing when the window is out of view, whichever petal is currently scaling at the time will continue to scale indefinitely.
I even tried using a special js library to determine if the window is in view and still wasn't able to get the petals to stop scaling.
You can view a demo here, as I was not even able to replicate this in a Paper sketch: https://demos2.paperbeatsscissors.com/
Also including my onFrame code here in case the problem is obvious to someone:
view.onFrame = function(event) {
// See if something is playing
if (playing > -1) {
// Get the active flower
var activeFlower = garden[playing],
activeData = activeFlower.data;
// Active layer and petal
var activeLayer = getEl(activeFlower, activeData.lIndex),
activePetal = getEl(activeLayer, activeData.pIndex);
// Variables
var time = activeData.audio.seek(),
scaleAmount = (1 / (activeData.timing / event.delta.toFixed(3))) * 2;
// Petal progression
if (!activeData.completed) {
if (activePetal.scaling.x < 1 && activePetal.scaling.y < 1) {
activePetal.pivot = {x: 0, y: activePetal.height / 2};
activePetal.scaling.x = activePetal.scaling.x + scaleAmount;
activePetal.scaling.y = activePetal.scaling.y + scaleAmount;
} else {
if (activeData.pIndex < (activeLayer.children.length - 1)) {
// If the petal is scaled, jump to a new petal
activeData.pIndex += 1;
} else {
if (activeData.lIndex > 0) {
// When all petals are bloomed, jump to a new layer
activeData.pIndex = 0;
activeData.lIndex -= 1;
} else {
// Set the flower as completed
activeData.completed = true;
}
}
}
}
activeFlower.rotate(.125, activeData.center);
// Reset the playing variable if the audio clip is complete and the flower has completed
if (!activeData.audio.playing() && time === 0 && activeData.completed) {
playing = -1;
}
}
}
Really stumped on this one so any help is greatly appreciated!
I think that your problem is coming from the fact that you base your scaling calculation on event.delta which represents the time elapsed since the last event fired.
The thing is that, if I'm not mistaken, under the hood, Paper.js onFrame event relies on requestAnimationFrame which does not fire when the tab if inactive.
So when you switch tab, wait for a while and get back to your tab event.delta value is big and your scaling value too, hence the size of your petals. This basic sketch showcase this behavior.
So in my opinion, you should simply check event.delta value and limit it if it's too high.
I am developing a WebGL application. For example, I have a sphere object that uses orbit controls for Zoom in/out.
Now I want to setup an event for the mousewheel. When zooming on my current WebGL block corresponding map location it can be zoomed in/out (used inline-block) for maps and WebGL. But the problem is that first of all my event is being triggered when I use the mousewheel. I also want to know whether my event logic is correct or not.
root.addEventListener('mousewheel', mousewheel, false);
function mousewheel(event) {
var mX = (event.wheeldetailX/width)* 2 - 1;
var mY = (event.wheeldetailY/height)* 2 + 1;
var WebGLZoom = new THREE.Vector3(mX, mY, 1);
var raycaster = new THREE.Raycaster();
raycaster.setFromCamera(WebGLZoom, camera);
WebGLZoom.sub(camera.position);
var mapzoom = map.getZoom();
if (WebGLZoom.z <= 5 && WebGLZoom.z > 2) {
map.setZoom(17);
} else if (WebGLZoom.z <= 2 && WebGLZoom.z > 0.0) {
map.setZoom(19);
} else {
map.setZoom(14);
}
}
You can use the wheel event like so.
document.addEventListener('wheel', function(event){
console.log(event.deltaY);
}, false);
This will result in a console log every time your mousewheel scrolls over the document. The deltaX and deltaY properties of the MouseWheel event are especially useful to figure out exactly how much the user scrolled and in which direction.
The event to use is wheel. You can find a working example here.
I'm using Phaser.js to create a map (tileSprite) and have some sprites on it, because not all the sprites can get in, I'm using the camera to pan right and left.
I want the user to either click a keyboard key (left or right) or a directional button sprite to continuously pan the camera until the user releases the control.
I've implemented keyboard panning similar to this example, I hold down a key and the camera moves/pans (10 pixels to each side on an event) and stops on key release.
However, when I've tried to implement the same thing using the 2 sprite buttons, each button fired only 1 event and panned the camera only 10 pixels per click. I need to to keep firing until I let go of the key.
var panRightButton = game.add.button(800, 5, 'right_pan_btn', onClickAction, this);
panRightButton.onInputOver.add(onButtonOver, this);
panRightButton.onInputOut.add(onButtonOut, this);
panRightButton.onInputDown.add(panScreenRight, this);
function panScreenRight() {
game.camera.x += 10;
}
I've tried using a boolean flag (isPanning) that would turn to true if i'm clicking a button and false on release. and have a while loop on game.camera.x += 10;, but it just slowed and stopped the script.
function onClickAction() {
isPanning = true;
}
function onButtonOut() {
isPanning = false;
}
function onButtonUp() {
isPanning = false;
}
function panScreenLeft() {
if (isPanning) {
game.camera.x -= 10;
}
}
The proper way to do it is on the update method, but not within a loop. Using a flag to know if the button is being pressed is ok, but just let Phaser to update the camera position, like in the example that you have linked:
function update() {
//...
if (isPanningLeft) {
game.camera.x -= 10;
} else if (isPanningRight) {
game.camera.x += 10;
}
//...
}
You don't need a loop because the update method is executed within a loop (and it is expected to be executed once by frame)
I have an AngularJS component that should react to either a single click or a drag (resizing an area).
I started to use RxJS (ReactiveX) in my application, so I try to find a solution using it. The Angular side of the request is minor...
To simplify the problem (and to train myself), I made a slider directive, based on the rx.angular.js drag'n'drop example: http://plnkr.co/edit/UqdyB2
See the Slide.js file (the other code is for other experiments). The code of this logic is:
function(scope, element, attributes)
{
var thumb = element.children(0);
var sliderPosition = element[0].getBoundingClientRect().left;
var sliderWidth = element[0].getBoundingClientRect().width;
var thumbPosition = thumb[0].getBoundingClientRect().left;
var thumbWidth = thumb[0].getBoundingClientRect().width;
// Based on drag'n'drop example of rx-angular.js
// Get the three major events
var mousedown = rx.Observable.fromEvent(thumb, 'mousedown');
var mousemove = rx.Observable.fromEvent(element, 'mousemove');
var mouseup = rx.Observable.fromEvent($document, 'mouseup');
// I would like to be able to detect a single click vs. click and drag.
// I would say if we get mouseup shortly after mousedown, it is a single click;
// mousedown.delay(200).takeUntil(mouseup)
// .subscribe(function() { console.log('Simple click'); }, undefined, function() { console.log('Simple click completed'); });
var locatedMouseDown = mousedown.map(function (event)
{
event.preventDefault();
// console.log('Click', event.clientX - sliderPosition);
// calculate offsets when mouse down
var initialThumbPosition = thumb[0].getBoundingClientRect().left - sliderPosition;
return { from: initialThumbPosition, offset: event.clientX - sliderPosition };
});
// Combine mouse down with mouse move until mouse up
var mousedrag = locatedMouseDown.flatMap(function (clickInfo)
{
return mousemove.map(function (event)
{
var move = event.clientX - sliderPosition - clickInfo.offset;
// console.log('Move', clickInfo);
// calculate offsets from mouse down to mouse moves
return clickInfo.from + move;
}).takeUntil(mouseup);
});
mousedrag
.map(function (position)
{
if (position < 0)
return 0;
if (position > sliderWidth - thumbWidth)
return sliderWidth - thumbWidth;
return position;
})
.subscribe(function (position)
{
// console.log('Drag', position);
// Update position
thumb.css({ left: position + 'px' });
});
}
That's mostly D'n'D constrained horizontally and to a given range.
Now, I would like to listen to mousedown, and if mouse up happens within a short while (say 200 ms, to adjust), I see it as a click and I do a specific treatment (eg. resetting the position to zero).
I tried with delay().takeUntil(mouseup), as seen in another SO answer, without success. Perhaps a switch() might be needed, too (to avoid going the drag route).
Any idea? Thanks in advance.
You can use timeout (timeoutWith if you are using ReactiveX/RxJS)
var click$ = mousedown.flatMap(function (md) {
return mouseup.timeoutWith(200, Observable.empty());
});
If the mouseup doesn't occur before the timeout it will just propagate an empty Observable instead. If it does then the downstream observer will receive an event.
Isn't the trick with delay(Xms).takeUntil(mouseup) doing the opposite of what you want? I mean, you want to detect when the mouseup event happens before the countdown, while the aformentioned trick detect when the mouseup event happens after.
I would try something around those lines (untested for now, but hopefully it will orient you in some positive direction):
var click$ = mousedown.flatMap(function ( mouseDownEv ) {
return merge(
Rx.just(mouseDownEv).delay(Xms).map(function ( x ) {return {event : 'noclick'};}),
mouseup.map(function ( mouseUpEv ) {return {event : mouseUpEv};})
).first();
});
The idea is to race the mouseup event against a dummy emission happening after your delay, and see who wins. So if click$ emits 'noclick' then you can consider that no click happened.
Hopefully that works, i will test soon but if you do before me, let me know.
I am using event.preventDefault but after some checks, I want the event to continue.
For eg, For the
touchmove
event I am using event.preventDefault() as I don't want the browser to scroll horizontally, but after determining the direction of swipe, if the direction turns out to be up/down, whatever event i have prevented, i want to nullify the effect. Is that possible?
My scenario
This is my html
<div id="teazer" class="globalTeaser" ontouchcancel="touchCancel(event);" ontouchmove="touchMove(event);" ontouchend="touchEnd(event,'gTeaser');" ontouchstart="touchStart(event,'teazer');">
This is my js after removing the irrelevant part
function touchMove(event) {
if(fingerCount==1){
event.preventDefault();
if ( event.touches.length == 1 ) {
curX = event.touches[0].pageX;
curY = event.touches[0].pageY;
}
else {
touchCancel(event);
}
}else {
touchCancel(event);
alert("hi");
}
}
function touchEnd(event,eventType) {
//I get the swipe direction here,after some calculations
//I get the directions fine,now
//if the swipe direction is up/down whatever event i have prevented in touch move I need to nullify
}
Please note, if anyone is gonna suggest window.scrollBy, for some reason its not working for me. Any other solutions I am happy to try.
I don't know how your code behaves exactly, but this is the general principle:
// Keep a reference for last point both events can access
var start;
$( 'body' ).on( {
// On start, assign the event data to start
touchstart : function touchStart( touchstartEvent ){
start = touchstartEvent.touches[ 0 ];
},
touchmove : function touchMove( touchmoveEvent ){
// The difference between both events positions
var delta = {};
// The end event's position
var end = touchmoveEvent.touches[ 0 ];
// Calculate the offset compared to start for this move event
delata.pageX = start.pageX - end.pageX;
delata.pageY = start.pageY - end.pageY;
// If pageX is less than or greater than 0, it means there has been horizontal movement,
// and this if condition will pass
if( delta.pageX ){
touchmoveEvent.preventDefault();
}
}
} );
This isn't perfect, because in practice it's very difficult to create a perfect downward or upward swipe — my finger might move a couple of pixels left or right even if the main movement is downwards. But then you have the problem of preventing horizontal motion but not vertical! In any case, the general principal for testing different properties of a touch event and only preventing default in certain conditions is illustrated.
My touchMove() function has been rewritten as follows
function touchMove(event) {
if (event.touches.length == 1) {
curX = event.touches[0].pageX;
curY = event.touches[0].pageY;
if (Math.abs((curX - startX)) > 10) {
//default acion is prevented only if the
//finger count is one and change in
//x coordinatesis greater than 10 px
event.preventDefault();
}
}
else {
touchCancel(event);
}
}
This code will make sure of two things
1)Pinch to zoom is not affected
2)default action for vertical swipe is not affected