Update moment.js duration with ionic touch event - javascript

I am building an iOS application with Ionic. The application is a timer where the user will specify a time limit and then countdown that time for a certain activity. I am trying to achieve an interaction where the user will drag a handle around the outside of a circle and each clockwise rotation will increment the time limit by one minute, and going the opposite direction will decrement by one.
I have the circle working, where you can drag the handle and it will adhere to the bounds of the container. Now I am trying to use Moment.js to create the countdown, but am having a tough time getting the timer values to update inside of the touch event.
The $scope.duration variable is what I am using to track the timer value. I have tried using the moment().duration() method to specify a duration object and am initializing it to '00:00:00'. When I try to update that value in the touch gesture event, I am unable to update the timer value. I am assuming I either don't understand how to correctly apply updated scope values in Angular/Ionic, I don't know how to correctly use Moment.js, or quite possibly - both.
Here is my template code:
<ion-view hide-nav-bar="true" view-title="Dashboard">
<ion-content>
<div class="timer">
<div class="timer-slider"></div>
<span class="timer-countdown">
{{duration}}
</span>
</div>
</ion-content>
</ion-view>
And my large controller:
.controller('DashCtrl', function($scope, $ionicGesture) {
var $timer = angular.element(document.getElementsByClassName('timer')[0]);
var $timerSlider = angular.element(document.getElementsByClassName('timer-slider')[0]);
var timerWidth = $timer[0].getBoundingClientRect().width;
var sliderWidth = $timerSlider[0].getBoundingClientRect().width;
var radius = timerWidth / 2;
var deg = 0;
var X = Math.round(radius * Math.sin(deg*Math.PI/180));
var Y = Math.round(radius * -Math.cos(deg*Math.PI/180));
var counter = 0;
$scope.duration = moment().hour(0).minute(0).second(0).format('HH : mm : ss');
// Set timer circle aspect ratio
$timer.css('height', timerWidth + 'px');
$timerSlider.css({
left: X + radius - sliderWidth / 2 + 'px',
top: Y + radius - sliderWidth / 2 + 'px'
});
$ionicGesture.on('drag', function(e) {
e.preventDefault();
var pos = {
x: e.gesture.touches[0].clientX,
y: e.gesture.touches[0].clientY
};
var atan = Math.atan2(pos.x - radius, pos.y - radius);
deg = -atan/(Math.PI/180) + 180; // final (0-360 positive) degrees from mouse position
// for attraction to multiple of 90 degrees
var distance = Math.abs( deg - ( Math.round(deg / 90) * 90 ) );
if ( distance <= 5 || distance >= 355 )
deg = Math.round(deg / 90) * 90;
if(Math.floor(deg) % 6 === 0) {
console.log(Math.floor(deg));
$scope.duration = moment().hour(0).minute(0).second(counter++).format('HH : mm : ss');
}
if (deg === 360)
deg = 0;
X = Math.round(radius * Math.sin(deg * Math.PI / 180));
Y = Math.round(radius * -Math.cos(deg * Math.PI / 180));
$timerSlider.css({
left: X + radius - sliderWidth / 2 + 'px',
top: Y + radius - sliderWidth / 2 + 'px'
});
}, $timerSlider);
})
I hacked up a CodePen demo. It doesn't track the drag event all that well without a mobile format, but you can get the idea of what I am going for.
http://codepen.io/stat30fbliss/pen/xZRrXY
Here's a screenshot of the app in-progress

After updating $scope.duration, run $scope.$apply() and it should start working :)

Related

JS randomly distribute child elements in their parent without overlap

I am trying to make something where a bunch of circles (divs with border-radius) can be dynamically generated and laid out in their container without overlapping.
Here is my progress so far - https://jsbin.com/domogivuse/2/edit?html,css,js,output
var sizes = [200, 120, 500, 80, 145];
var max = sizes.reduce(function(a, b) {
return Math.max(a, b);
});
var min = sizes.reduce(function(a, b) {
return Math.min(a, b);
});
var percentages = sizes.map(function(x) {
return ((x - min) * 100) / (max - min);
});
percentages.sort(function(a, b) {
return b-a;
})
var container = document.getElementById('container');
var width = container.clientWidth;
var height = container.clientHeight;
var area = width * height;
var maxCircleArea = (area / sizes.length);
var pi = Math.PI;
var maxRadius = Math.sqrt(maxCircleArea / pi);
var minRadius = maxRadius * 0.50;
var range = maxRadius - minRadius;
var radii = percentages.map(function(x) {
return ((x / 100) * range) + minRadius;
});
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
var coords = [];
radii.forEach(function(e, i) {
var circle = document.createElement('div');
var randomTop = getRandomArbitrary(0, height);
var randomLeft = getRandomArbitrary(0, width);
var top = randomTop + (e * 2) < height ?
randomTop :
randomTop - (e * 2) >= 0 ?
randomTop - (e * 2) :
randomTop - e;
var left = randomLeft + (e * 2) < width ?
randomLeft :
randomLeft - (e * 2) >= 0 ?
randomLeft - (e * 2) :
randomLeft - e;
var x = left + e;
var y = top + e;
coords.push({x: x, y: y, radius: e});
circle.className = 'bubble';
circle.style.width = e * 2 + 'px';
circle.style.height = e * 2 + 'px';
circle.style.top = top + 'px';
circle.style.left = left + 'px';
circle.innerText = i
container.appendChild(circle);
});
I have got them being added to the parent container but as you can see they overlap and I don't really know how to solve this. I tried implementing a formula like (x1 - x2)^2 + (y1 - y2)^2 < (radius1 + radius2)^2 but I have no idea about this.
Any help appreciated.
What you're trying to do is called "Packing" and is actually a pretty hard problem. There are a couple potential approaches you can take here.
First, you can randomly distribute them (like you are currently doing), but including a "retry" test, in which if a circle overlaps another, you try a new location. Since it's possible to end up in an impossible situation, you would also want a retry limit at which point it gives up, goes back to the beginning, and tries randomly placing them again. This method is relatively easy, but has the down-side that you can't pack them very densely, because the chances of overlap become very very high. If maybe 1/3 of the total area is covered by circle, this could work.
Second, you can adjust the position of previously placed circles as you add more. This is more equivalent to how this would be accomplished physically -- as you add more you start having to shove the nearby ones out of the way in order to fit the new one. This will require not just finding the things that your current circle hits, but also the ones that would be hit if that one was to move. I would suggest something akin to a "springy" algorithm, where you randomly place all the circles (without thinking about if they fit), and then have a loop where you calculate overlap, and then exert a force on each circle based on that overlap (They push each other apart). This will push the circles away from each other until they stop overlapping. It will also support one circle pushing a second one into a third, and so on. This will be more complex to write, but will support much more dense configurations (since they can end up touching in the end). You still probably need a "this is impossible" check though, to keep it from getting stuck and looping forever.

Translating a function that styles a div based on mouse movement from jQuery to Angular makes coordinates spazz out

Here is a JSFiddle of the function I built some time ago in JQuery before I learned AngularJS for the project I intended to use it in.
var numberOfPosts = 1; // To calculate the absolute/starting position of each post
var post = $('#post'); // Need to track multiple posts, ideally by array of getElementsByClass
var postOffset = post.offset(); // Relative to the document
var postPosition = post.position(); // Relative to the parent
var radiansBetweenPosts = (90 / numberOfPosts) * Math.PI / 180;
$('#wrapper').mousemove(function(event) {
// Mouse horizontal percentage position inside the wrapper (double to make full circle)
mouseX = (event.pageX - postOffset.left) / post.parent().width() * 2;
x = (Math.cos(Math.PI * mouseX + radiansBetweenPosts)) * 50 + 50; // Multiply by % size of a quadrant,
y = (Math.sin(Math.PI * mouseX + radiansBetweenPosts)) * 50 + 50; // add a % offset to the centre of the circle
post.css({
'left': x + '%',
'top': y + '%'
});
// Mouse horizontal % coordinates from the centre of the circle
$('p').html(Math.round(mouseX * 100));
});
And here is a Plunker of the same idea I translated to AngularJS, which is how it currently behaves on my project.
angular.module('mouseMovement', [])
.controller('MouseMovementController', ['$scope', '$element', function MouseMovementController($scope, $element) {
$scope.msg = "Mouse X position inside the div"
numberOfPosts = 1
radiansBetweenPosts = (90 / numberOfPosts) * Math.PI / 180
$scope.mousePosition = function(event) {
postOffsetLeft = event.target.querySelector('.postDiv').offsetLeft
frameWidth = event.target.offsetWidth
mouseXpercent = (event.pageX - postOffsetLeft) / frameWidth * 2
x = (Math.cos(Math.PI * mouseXpercent + radiansBetweenPosts)) * 50 + 50
y = (Math.sin(Math.PI * mouseXpercent + radiansBetweenPosts)) * 50 + 50
$scope.position = {
left: x + '%',
top: y + '%'
}
$scope.mouseX = Math.round(mouseXpercent * 100)
$scope.postX = Math.round(x)
$scope.postY = Math.round(y)
}
}])
It appears to me that when the mouse is moved across the div, the coordinates jump between a single digit and a two or three digit number very quickly, which you can observe if you move the mouse for a bit and check the numbers a few times. That I believe is what causes the position to spazz out like that.
Oddly, that only happens when the $scope.position variable is there, so if you comment that bit out, both the Post X and Post Y numbers will steadily change as they should when you move your mouse across the div.
What am I missing here? It seems like the coordinate calculation is suddenly wrong when the styles are applied, but that can't be true. To make it more weird, at some sedctions of the div the numbers are steadily and correctly changing, for example verticall under this bolded word on the Plunker "Mouse X position inside the div"
If it's something in the way AngularJS works internally, what solutions are there?
In addition to that, I'll need to somehow keep tracking the mouse movement across the gray div even if the mouse appears on top of the
Your math is off. Try this. My math isn't exact but it's closer to what you're looking for
angular.module('mouseMovement', [])
.controller('MouseMovementController', ['$scope', '$element', function MouseMovementController($scope, $element) {
$scope.msg = "Mouse X position inside the div"
numberOfPosts = 1
radiansBetweenPosts = (2 / numberOfPosts) * Math.PI
$scope.mousePosition = function(event) {
postOffsetLeft = event.target.querySelector('.postDiv').offsetLeft
frameWidth = event.target.offsetWidth
mouseXpercent = (event.pageX) / frameWidth
x = Math.PI * (Math.cos(mouseXpercent * radiansBetweenPosts)) * 10 + 40
y = Math.PI * (Math.sin(mouseXpercent * radiansBetweenPosts)) * 10 + 20
$scope.position = {
left: x + '%',
top: y + '%'
}
$scope.mouseX = Math.round(mouseXpercent * 100)
$scope.postX = Math.round(x)
$scope.postY = Math.round(y)
}
}])

Rotating a clock hand in javascript

I am learning to make a clock using raphael js,
I am using this tutorial to get me started http://www.tuttoaster.com/creating-a-clock-animation-without-css3/
When this is diplayed the second hand doesnt move one second per second. I know one second is 6 degrees, it moves around 45 degrees though!
If someone could please explain what he has done wrong and how to make the hands rotate at appropriate angles that would be great. I am a beginner so plain english please :)
The code is as follows.
window.onload = function(){
var canvas = Raphael("pane",0,0,500,500);
canvas.circle(200,150,100).attr("stroke-width",2);
canvas.circle(200,150,3).attr("fill","#000");
var angleplus = 360,rad = Math.PI / 180,
cx = 200,cy =150 ,r = 90,
startangle = -90,angle=30,x,y, endangle;
for(i=1;i<13;i++)
{
endangle = startangle + angle ;
x = cx + r * Math.cos(endangle * rad);
y = cy + r * Math.sin(endangle * rad);
canvas.text(x,y,i+"");
startangle = endangle;
}
var hand = canvas.path("M200 70L200 150").attr("stroke-width",1);
var minute_hand = canvas.path("M200 100L200 150").attr("stroke-width",2);
var hour_hand = canvas.path("M200 110L200 150").attr("stroke-width",3);
var time = new Date();
angle = time.getSeconds() * 6;
minute_hand.rotate(6 * time.getMinutes(),200,150);
var hr = time.getHours();
if(hr>12)
hr = hr -11;
hour_hand.rotate(30 * hr,200,150);
var minute_angle= 6 + time.getMinutes()*6,hour_angle=0.5+
time.getMinutes()*30;
setInterval(function(){
angle = angle + 6;
if(angle>=360)
{
angle=0;
minute_hand.rotate(minute_angle,200,150);
minute_angle = minute_angle + 6;
hour_hand.rotate(hour_angle,200,150);
hour_angle = hour_angle + 0.5;
}
if(minute_angle>=360)
{
minute_angle=0;
}
hand.rotate(angle,200,150);
},1000);
hand.rotate(6,200,150);
Bernard, you don't need to rotate by the variable angle since you're simply rotating by 6 degrees every second regardless of how many seconds have elapsed.
http://jsbin.com/domoqojipe/1/
So you want to speed up the clock speed by twenty?
It's a long shot, but try changing the 1000 at the bottom to 50. Because 1000 divided by 20 equals 50.
Try that and see if it works...

jQuery image rotation to follow angle of sine wave

SOLVED -
I am trying to animate an image to rotate following the angle of a sine wave - the sine wave is created using the jQuery.path plugin.
The problem is with getting a smooth rotation.
I am currently using the jQuery rotate plugin for the rotation, which - as it currently stands - is not creating a smooth rotation.
See http://hellosmithers.com/GNU/STALLMANQUEST.html for JavaScript and jQuery.
The script is relatively heavily commented.
Currently the rotation just repeats and takes a certain amount of time to complete - this means it will not work for all screen sizes as the sine wave takes longer to complete the wider the screen.
function mRot() { // image rotation - it goes from rA[0] (-8 degrees) to rA[1] (8 degrees) then swaps the values
// flip the values in rA - making the rotation oposite each iteration of this funciton
rA[1] = [rA[0], rA[0] = rA[1]][0];
$("#rmat").rotate({
duration: 1700,
angle: rA[0],
animateTo: rA[1],
callback: mRot
}); // I would like to remove this function - instead tying the rotation into the sine wave function somehow
}
The sine wave is created as follows:
SineWave = function() { // create the sine wave - as per https://github.com/weepy/jquery.path
this.css = function(p) {
s = Math.sin(p * 12);
x = winWidth - p * (winWidth + 250);
y = s * 40 + (winHeight - 440);
return {top: y + "px", left: x + "px"};
}
}
Thanks
I figured it out - the angle is derived from p, I've used (p * 12) -
SineWave = function() {
this.css = function(p) {
rA[0] = p * 12; // get the angle
s = Math.sin(p * 12);
x = winWidth - p * (winWidth + 250);
y = s * 40 + (winHeight - 440);
return {top: y + "px", left: x + "px"};
}
}
The function mRot uses rA -
function mRot() {
rA[0] = prA[1]; // rA[0] will always be the previous rA[1]
if (prA[1] < 0) rA[1] = Math.abs(rA[1]); // if the previous rA[1] is a negative number, make the current rA positive.
else rA[1] = -(rA[1]); // otherwise make it negative
prA = rA;
$("#rmat").rotate({
duration: 1500,
angle: rA[0],
animateTo: rA[1],
callback: mRot
});
}
Making sure to declare the array variables -
rA = new Array (-8, 8);
prA = rA[1] = [rA[0], rA[0] = rA[1]][0];
If you want to see the full code - visit http://hellosmithers.com/GNU/STALLMANQUEST.html

Three.js First Person Controls

I'm playing around with Three.js and WebGL and can't quite get the controls the way I want. I chose to try to "roll my own" controls since Three.js's FirstPersonControls do not use pointer lock.
Anyway, I took most of my code from the built-in FirstPersonControls, converted it to use pointer lock (movementX instead of pageX - offset), but I am having trouble smoothing the look motion.
Here is my onMouseMove (using originalEvent since it is a jquery event):
onMouseMove: function(e) {
if(!document.pointerLockElement) return;
var moveX = e.originalEvent.movementX ||
e.originalEvent.mozMovementX ||
e.originalEvent.webkitMovementX ||
0,
moveY = e.originalEvent.movementY ||
e.originalEvent.mozMovementY ||
e.originalEvent.webkitMovementY ||
0;
//Update the mouse movement for coming frames
this.mouseMovementX = moveX;
this.mouseMovementY = moveY;
}
And my Controls.update() (called on each animation frame, with the THREE.Clock delta):
update: function(delta) {
if(this.freeze) {
return;
}
//movement, works fine
if(this.moveForward) this.camera.translateZ(-(actualMoveSpeed + this.autoSpeedFactor));
if(this.moveBackward) this.camera.translateZ(actualMoveSpeed);
if(this.moveLeft) this.camera.translateX(-actualMoveSpeed);
if(this.moveRight) this.camera.translateX(actualMoveSpeed);
/////////
//ISSUES ARE WITH THIS CODE:
/////////
//look movement, really jumpy
this.lon += this.mouseMovementX;
this.lat -= this.mouseMovementY;
this.lat = Math.max(-85, Math.min(85, this.lat));
this.phi = (90 - this.lat) * Math.PI / 180;
this.theta = this.lon * Math.PI / 180;
this.target.x = this.camera.position.x + 100 * Math.sin(this.phi) * Math.cos(this.theta);
this.target.y = this.camera.position.y + 100 * Math.cos(this.phi);
this.target.z = this.camera.position.z + 100 * Math.sin(this.phi) * Math.sin(this.theta);
this.camera.lookAt(this.target);
}
This code does work, but moving the camera is jumpy as the mouse moves around. I could really use some help figuring out how to smooth it.
You can see what I mean by "jumpy" here. I'm new to Three.js, WebGL, and just 3D in general so any help is appreciated.
Thanks,
-Chad
EDIT After working with #przemo_li, here is the working code he came up with:
onMouseMove: function(e) {
if(!document.pointerLockElement) return;
var moveX = e.originalEvent.movementX ||
e.originalEvent.mozMovementX ||
e.originalEvent.webkitMovementX ||
0,
moveY = e.originalEvent.movementY ||
e.originalEvent.mozMovementY ||
e.originalEvent.webkitMovementY ||
0;
//Update the initial coords on mouse move
this.mouseMovementX += moveX; //aggregate mouse movements as a total delta delta
this.mouseMovementY += moveY;
},
update: function(delta) {
if(this.freeze) {
return;
}
//movement
if(this.moveForward) this.camera.translateZ(-(actualMoveSpeed + this.autoSpeedFactor));
if(this.moveBackward) this.camera.translateZ(actualMoveSpeed);
if(this.moveLeft) this.camera.translateX(-actualMoveSpeed);
if(this.moveRight) this.camera.translateX(actualMoveSpeed);
//look movement
this.lon += this.mouseMovementX;
this.lat -= this.mouseMovementY;
this.mouseMovementX = 0; //reset mouse deltas to 0 each rendered frame
this.mouseMovementY = 0;
this.phi = (90 - this.lat) * Math.PI / 180;
this.theta = this.lon * Math.PI / 180;
if(this.constrainVertical) {
this.phi = THREE.Math.mapLinear(this.phi, 0, Math.PI, this.verticalMin, this.verticalMax);
}
this.target.x = this.camera.position.x + 100 * Math.sin(this.phi) * Math.cos(this.theta);
this.target.y = this.camera.position.y + 100 * Math.cos(this.phi);
this.target.z = this.camera.position.z + 100 * Math.sin(this.phi) * Math.sin(this.theta);
this.camera.lookAt(this.target);
}
'Official' version just added: https://github.com/mrdoob/three.js/blob/master/examples/js/controls/PointerLockControls.js
1)Constraints?
In your code you limit mouse X movement to -|+ 85 Its unlikely that such constraint is needed.
2)Aggregate all events that happen during frame
In your code you override mouse movement with each new event. So if you get 3 events during frame only most recent will be stored.
Add those movements. Than after rendering frame you can clear count. And start gathering events again.

Categories