I have searched the web but all the ones readily available are where you specificy a date and it counts down to that date. What I need is something which will simply count down from "27 minutes and 43 seconds" (in that format) all the way down to 0 from whenever they land on the page, anyone got any snippets available?
Something like this should do the trick. I'm bored and decided to do it myself instead of Googling. Just set the minutes and seconds at the top and change the call to countdown inside the onload to the id of the element you want it to update.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script>
var interval;
var minutes = 1;
var seconds = 5;
window.onload = function() {
countdown('countdown');
}
function countdown(element) {
interval = setInterval(function() {
var el = document.getElementById(element);
if(seconds == 0) {
if(minutes == 0) {
el.innerHTML = "countdown's over!";
clearInterval(interval);
return;
} else {
minutes--;
seconds = 60;
}
}
if(minutes > 0) {
var minute_text = minutes + (minutes > 1 ? ' minutes' : ' minute');
} else {
var minute_text = '';
}
var second_text = seconds > 1 ? 'seconds' : 'second';
el.innerHTML = minute_text + ' ' + seconds + ' ' + second_text + ' remaining';
seconds--;
}, 1000);
}
</script>
</head>
<body>
<div id='countdown'></div>
</body>
</html>
I have made a simple countdown you can use.
It is generating the format:
DAYS X, HOURS X, MINUTES X, SECONDS X
The JS:
countIt();
function countIt(){
year = 2013;
month = 05;
day = 28;
hours = 12;
minutes = 00;
seconds = 00;
setTimeout(function(){
endDate = new Date(year, (month - 1), day, hours, minutes, seconds, 00);
thisDate = new Date();
thisDate = new Date(thisDate.getFullYear(), thisDate.getMonth(), thisDate.getDate(), thisDate.getHours(), thisDate.getMinutes(), thisDate.getSeconds(), 00, 00);
var daysLeft = parseInt((endDate-thisDate)/86400000);
var hoursLeft = parseInt((endDate-thisDate)/3600000);
var minutsLeft = parseInt((endDate-thisDate)/60000);
var secondsLeft = parseInt((endDate-thisDate)/1000);
seconds = minutsLeft*60;
seconds = secondsLeft-seconds;
minutes = hoursLeft*60;
minutes = minutsLeft-minutes;
hours = daysLeft*24;
hours = (hoursLeft-hours) < 0 ? 0 : hoursLeft-hours;
days = daysLeft;
startCount(days, hours, minutes,seconds);
}, 1000);
}
function startCount(days, hours, minutes, seconds){
document.getElementById("counter").innerHTML="DAYS "+days+", HOURS "+hours+", MINUTES "+minutes+", SECONDS: "+seconds;
countIt();
}
The HTML:
<div id="counter"></div>
Click here to see it live
I think you're looking for something like this.
um, why not just use this:
<div id="countdown">countdown displayed here</div>
<script type="text/javascript">
$(document).ready(function() {
//time to count down
hoursToGo = 0;
minutesToGo = 27;
secondsToGo = 43;
var startTime = new Date();
var endTime = new Date();
endTime.setHours(
startTime.getHours() + hoursToGo,
startTime.getMinutes() + minutesToGo,
startTime.getSeconds() + secondsToGo,
startTime.getMilliseconds()
);
//function to update counter
function update(){
var currentTime = new Date();
var remainingTime = new Date();
remainingTime.setTime(endTime.getTime()-currentTime.getTime());
$("#countdown").text(remainingTime.getHours()+":"+remainingTime.getMinutes()+":"+remainingTime.getSeconds());
//call itself every second
setTimeout(update,1000);
}
//start the countdown
update();
});
</script>
EDIT
As an added thought, you could implement this over multiple pages by passing the end time onto the next page as the value of a hidden form element. Or at least thats what i am going to do with it ;-)
jCounter is a plugin that also accepts custom values to display any countdown you want
I also made a count-down clock but with html5 canvas.
Feel free to use it if you like it. (Run the code snipet below)
(function() {
/**
* WlCounter pure JS class
*
* #class WlCounter
* #Author: Wiktor Lis
*/
var WlCounter = function(){
// date settings - REMEMBER months in Date object are counted as an Array from 0! So months range is between 0 to 11. Other than that everything is just as you would expect it to be.
this.setEventDate(new Date(2017, 03, 22, 22, 5, 0), 'Curently Unused'); // NOTE! months are represented from 0 to 11 in Date objects
this.setEventEndDate(new Date(2019, 04, 03, 22, 9, 0), 'Curently Unused', 'Curently Unused'); // NOTE! months are represented from 0 to 11 in Date objects
// CLOCK SETTINGS
// clock size
this.container = {
x: 0,
y: 0,
w: 150,
h: 150
};
// ring stroke
this.stroke = this.container.w / 10;
// number font size
this.numFontSize = this.container.w / 6 +'px';
// label font size
this.labelFontSize = this.container.w / 10 +'px';
// colors
this.colorRing = "hsla(355, 0%, 90%, 1)";
this.colorActive = "hsla(355, 100%, 50%, 1)";
this.colorTimedOut = "hsla(355, 0%, 50%, 0.3)";
// OTHER
// init objets
this.curent = {};
this.counter = {};
}
/**
* This method creates canvas based on css id
*
* #method createCanvas
* #param {String} cssId of canvas DOM object
*/
WlCounter.prototype.createCanvas = function(cssId){
this.canvas = document.getElementById(cssId);
this.canvas.setAttribute("width", this.container.w);
this.canvas.setAttribute("height", this.container.h);
this.c = this.canvas.getContext('2d');
return this.c
}
/**
* Init all canvas objects and start counting down
*
* #method init
*/
WlCounter.prototype.init = function(){
this.daysBox = this.createCanvas('canvas-d');
this.hoursBox = this.createCanvas('canvas-h');
this.minutesBox = this.createCanvas('canvas-m');
this.secoundsBox = this.createCanvas('canvas-s');
this.msBox = this.createCanvas('canvas-ms');
this.startCounting();
this.intervalClock();
}
/**
* Assign curent time to class properities
*
* #method getTime
*/
WlCounter.prototype.getTime = function() {
this.curent.time = new Date();
this.curent.days = this.curent.time.getDay();
this.curent.hours = this.curent.time.getHours();
this.curent.minutes = this.curent.time.getMinutes();
this.curent.seconds = this.curent.time.getSeconds();
this.curent.milliseconds = this.curent.time.getMilliseconds();
}
/**
* Creates event start date // and its message
*
* #method setEventDate
* #param {Object Date} date - defines event start date
* #param {String} messageBefore - curently unused
*/
WlCounter.prototype.setEventDate = function(date, messageBefore) {
this.eventStarts = date;
this.messageBefore = messageBefore;
}
/**
* Define event end date
*
* #method setEventEndDate
* #param {Object Date} date
* #param {String} messageDuring
* #param {String} messageAfter
*/
WlCounter.prototype.setEventEndDate = function(date, messageDuring, messageAfter) {
this.eventEnds = date;
this.messageDuring = messageDuring;
this.messageAfter = messageAfter;
}
/**
* Substract curent time from event date and define how many of days, hours, min, s, ms are left
* If event starts counter finished -> change counter color to greenish (hsla(160, 100%, 38%, 1)) and count to events End time.
*
* #method startCounting
*/
WlCounter.prototype.startCounting = function() {
this.intervalToEvent = setInterval(function() {
this.getTime();
if (this.curent.time < this.eventStarts) {
this.subject = this.eventStarts;
} else if(this.curent.time < this.eventEnds) {
// change colors
this.colorActive = "hsla(160, 100%, 38%, 1)";
this.subject = this.eventEnds;
} else {
this.stagnate();
return;
}
var until = this.subject - this.curent.time;
this.counter.ms = Math.floor(until % 1000);
this.counter.sec = Math.floor(until / (1000) % 60);
this.counter.min = Math.floor(until / (1000 * 60) % 60);
this.counter.hours = Math.floor(until / (1000 * 60 * 60 ) % 24);
this.counter.days = Math.floor(until / (1000 * 60 * 60 * 24));
// console.log(this.counter);
}.bind(this), 16.666);
}
/**
* If counter will go pass the event start and end date, simply stagneta the clock assigning 0 to each clock
*
* #method stagnate
*/
WlCounter.prototype.stagnate = function() {
clearInterval(this.intervalToEvent);
this.counter.ms = 0;
this.counter.sec = 0;
this.counter.min = 0;
this.counter.hours = 0;
this.counter.days = 0;
}
/**
* Convert deg to radians
*
* #method degToRad
* #param {Integer} deg
*/
WlCounter.prototype.degToRad = function(deg) {
radians = Math.PI / 180;
return radians * deg;
}
/**
* Draw the clock and sign it with the text from the #param2
*
* #method drawClock
* #param {Object} ctx - canvas selection
* #param {Array} timeUnit - example [this.counter.days, 365, 'D'] -> [0] counter number of days -> [1] max days for the clock to count down -> [2] Title that will be assign to the clock under the number
*/
WlCounter.prototype.drawClock = function(ctx, timeUnit) {
// background - gradient
gradient = ctx.createRadialGradient(this.container.w/2, this.container.h/2, 55, this.container.w/2, this.container.h/2, this.container.w/2);
gradient.addColorStop(1, 'rgba(245,245,245, 0.1)');
gradient.addColorStop(0, 'white');
// canvas rect
// ctx.fillStyle = "white";
ctx.fillStyle = gradient;
ctx.fillRect(0,0,this.container.w, this.container.h);
// clock rim
ctx.beginPath();
ctx.strokeStyle = this.colorRing;
ctx.lineWidth = this.container.w / 18;
ctx.arc(this.container.w/2, this.container.h/2,this.container.h/2-this.stroke, this.degToRad(270), this.degToRad(0 - 90));
ctx.stroke();
// clock time
ctx.beginPath();
// ctx.shadowBlur = this.stroke-10;
// ctx.shadowColor = "hsla(355, 100%, 0%, 0.2)";
// ring countdown
// ctx.lineCap = "round";
ctx.strokeStyle = this.colorActive;
if(timeUnit[0] == 0) ctx.shadowColor = ctx.strokeStyle = this.colorTimedOut; // if time is 0 set color to gray
ctx.lineWidth = this.stroke;
// draw ring countdown
ctx.arc(this.container.w/2, this.container.h/2,this.container.h/2-this.stroke, this.degToRad(270), this.degToRad(360 * timeUnit[0] / timeUnit[1] - 90));
// text - number
ctx.font = this.numFontSize+" Arial";
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = this.colorActive;
if(timeUnit[0] == 0) ctx.fillStyle = this.colorTimedOut;
if(timeUnit[1] == 60000) timeUnit[0] = Math.round(timeUnit[0]/1000);
ctx.fillText(timeUnit[0],this.container.w/2,this.container.h/2);
// text - label
ctx.font = this.labelFontSize+" Arial";
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(timeUnit[2],this.container.w/2,this.container.h/2 + (this.container.w / 5));
// draw!
ctx.stroke();
}
/**
* Set interval to draw clock around 60 frames per seckound (1s / 60 = 16.666)
*
* #method intervalClock
*/
WlCounter.prototype.intervalClock = function() {
var interval = setInterval(function(){
// cl(this.counter.ms);
this.drawClock(this.daysBox, [this.counter.days, 365, 'D']);
this.drawClock(this.hoursBox, [this.counter.hours, 24, 'H']);
this.drawClock(this.minutesBox, [this.counter.min, 60, 'M']);
this.drawClock(this.secoundsBox, [this.counter.sec, 60, 'S']);
this.drawClock(this.msBox, [this.counter.ms, 1000, 'ms']);
}.bind(this), 16.666);
}
/* RUN WlCounter */
var counter = new WlCounter();
// FUTURE -> To Do
// set event start and end
// counter.setEventStart(foo);
// counter.setEventEnd(foo);
// counter.onEventStart(function(){
// ...
// });
// counter.onEventEnd(function({
// ...
// });
counter.init();
})();
* {
padding: 0px;
margin: 0px;
text-decoration: none;
border: none;
}
html, body {
position: relative;
padding-top: 20px;
margin: 0 auto;
}
#wrap, .text {
width: 770px;
margin: auto;
}
canvas {
box-sizing: border-box;
border-bottom: 5px solid gray;
height: 100%;
}
h1, p {
font-weight: 400;
color: #666;
font-size: 2.5em;
width: 100%;
text-align: center;
}
p {
font-size: 1em;
text-align: left;
}
ul ol {
padding-left: 20px;
list-style-type: decimal;
line-height: 2em;
}
<div class="text">
<h1>"WlCounter" pure JS class</h1>
</div>
<div id="wrap">
<canvas id="canvas-d"></canvas>
<canvas id="canvas-h"></canvas>
<canvas id="canvas-m"></canvas>
<canvas id="canvas-s"></canvas>
<canvas id="canvas-ms"></canvas>
</div>
<div class="text">
<p>JS WlCounter class counts-down to the event starting and ending date with color change on reaching the first one. After event start date is reached it changes the color to green and start count-down to events end date (if end date was specyfied and is more then curent time). So when event is ended, counter gets graied out and set 0</p>
</div>
Related
I'm creating particles for the first time with javascript and I'm not sure if the below code is optimized.
When I create 100 particles on the screen I don't notice that much of an fps drop.
When multiple clicks happen in a row the fps takes major dip.
This is understandable but is there a way to optimize this code more so that multiple clicks will maintain a higher frame rate?
var fps = document.getElementById("fps");
var startTime = Date.now();
var frame = 0;
function tick() {
var time = Date.now();
frame++;
if (time - startTime > 1000) {
fps.innerHTML = (frame / ((time - startTime) / 1000)).toFixed(1);
startTime = time;
frame = 0;
}
window.requestAnimationFrame(tick);
}
tick();
function pop (target) {
let amount = 100;
// Quick check if user clicked the button using a keyboard
const bbox = target.getBoundingClientRect();
const x = bbox.left + bbox.width / 2;
const y = bbox.top + bbox.height / 2;
for (let i = 0; i < 100; i++) {
createParticle(x, y);
}
}
function createParticle (x, y) {
const particle = document.createElement('particle');
document.body.appendChild(particle);
let width = Math.floor(Math.random() * 30 + 8);
let height = width;
let destinationX = (Math.random() - 0.5) * 1000;
let destinationY = (Math.random() - 0.5) * 1000;
let rotation = Math.random() * 2000;
let delay = Math.random() * 200;
particle.style.background = `hsl(${Math.random() * 90 + 270}, 70%, 60%)`;
particle.style.border = '1px solid white';
particle.style.width = `${width}px`;
particle.style.height = `${height}px`;
const animation = particle.animate([
{
transform: `translate(-50%, -50%) translate(${x}px, ${y}px) rotate(0deg)`,
opacity: 1
},
{
transform: `translate(-50%, -50%) translate(${x + destinationX}px, ${y + destinationY}px) rotate(${rotation}deg)`,
opacity: 0
}
], {
duration: Math.random() * 1000 + 5000,
easing: 'cubic-bezier(0, .9, .57, 1)',
delay: delay
});
animation.onfinish = removeParticle;
}
function removeParticle (e) {
e.srcElement.effect.target.remove();
}
document.querySelector(".confetti").addEventListener("click",()=>{pop(document.querySelector(".confetti"));}
);
particle {
position: fixed;
top: 0;
left: 0;
opacity: 0;
pointer-events: none;
background-repeat: no-repeat;
background-size: contain;
}
<div class="wrapper">
<span id="fps">--</span> FPS</div>
<div class="confetti">Square particles</div>
</div>
It would be much quicker if you could add a canvas and instead of adding the elements and styling them, you could just draw things onto the canvas. Then, it can be way faster. If you don't get how to use a canvas with JavaScript, you might want to search it up on something like W3Schools, because they have lots of things you could learn from them.
I hope this helps. Please give me any feedback if you have any. I am new to Stack Overflow, so I want to know how to improve. Thanks and happy coding!
I have a 3D wheel that I am animating using javascript requestAnimationFrame() function.
The wheel looks like:
There are 4 main variables to concider:
items The number of segments on the wheel.
spinSpeed The spin speed modifier. Multiply the angle increase/decrease per frame by this value.
spinDuration The length of the full speed spinning animation before decelerating to a stop.
spinDirection The direction the wheel should spin. Accepts up or down.
Now I want to get the segment (where the red line intersects) from the DOM using the angle the wheel stopped on. The wheel segments have their arc start and end angles stored in data attributes. For example:
<div class="wheel__inner">
<div class="wheel_segment" ... data-start-angle="0" data-end-angle="12.85">Item 1</div>
<div class="wheel_segment" ... data-start-angle="12.85" data-end-angle="25.71">Item 2</div>
<div class="wheel_segment" ... data-start-angle="25.71" data-end-angle="38.58">Item 3</div>
...
</div>
I am keeping track of the current wheel rotation by storing the modified angle on each tick. For example:
let wheelAngle = 0;
window.requestAnimationFrame( function tick() {
if ( spinDirection === 'up' ) {
wheelAngle += speedModifier;
} else {
wheelAngle -= speedModifier;
}
window.requestAnimationFrame( tick );
} );
When the animation comes to a stop, I attempt to get the segement by normalising the rotation and filtering segements using the start and end angles.
I normalise the rotation because it can go above 360° and below 0°, I do this with the below function:
function normaliseAngle( angle ) {
angle = Math.abs( angle ) % 360;
angle = 360 - angle; // Invert
return angle;
}
And filter elements using jQuery like so:
const $found = $wheel.find( '.wheel__segment' ).filter( function() {
const startAngle = parseFloat( $( this ).data( 'start-angle' ) );
const endAngle = parseFloat( $( this ).data( 'end-angle' ) );
return angle >= startAngle && angle < endAngle;
} );
However, despite my best efforts I cannot get this to work. Please see my JSFiddle here: https://jsfiddle.net/thelevicole/ps04fnxm/2/
( function( $ ) {
// Settings
const items = 28; // Segments on wheel
const spinSpeed = randNumber( 1, 10 ); // Spin speed multiplier
const spinDuration = randNumber( 2, 5 ); // In seconds
const spinDirection = randNumber( 0, 1 ) ? 'up' : 'down'; // Animate up or down
// Vars
const $wheel = $( '.wheel .wheel__inner' );
const diameter = $wheel.height();
const radius = diameter / 2;
const angle = 360 / items;
const circumference = Math.PI * diameter;
const height = circumference / items;
// Trackers
let wheelAngle = 0;
const wheelStarted = new Date();
// Add segments to the wheel
for ( let i = 0; i < items; i++ ) {
var startAngle = angle * i;
var endAngle = angle * ( i + 1 );
var transform = `rotateX(${ startAngle }deg) translateZ(${ radius }px)`;
var $segment = $( '<div>', {
class: 'wheel__segment',
html: `<span>Item ${ i }</span>`
} ).css( {
'transform': transform,
'height': height,
} );
// Add start and end angles for this segment
$segment.attr( 'data-start-angle', startAngle );
$segment.attr( 'data-end-angle', endAngle );
$segment.appendTo( $wheel );
}
/**
* Print debug info to DOM
*
* #param {object}
*/
function logInfo( data ) {
const $log = $( 'textarea#log' );
let logString = '';
logString += '-----' + "\n";
for ( var key in data ) {
logString += `${ key }: ${ data[ key ] }` + "\n";
}
logString += "\n";
// Prepend log to last value
logString += $log.val();
// Update field value
$log.val( logString );
}
/**
* Get random number between min & max (inclusive)
*
* #param {number} min
* #param {number} max
* #returns {number}
*/
function randNumber( min, max ) {
min = Math.ceil( min );
max = Math.floor( max );
return Math.floor( Math.random() * ( max - min + 1 ) ) + min;
}
/**
* Limit angles to 0 - 360
*
* #param {number}
* #returns {number}
*/
function normaliseAngle( angle ) {
angle = Math.abs( angle ) % 360;
angle = 360 - angle;
return angle;
}
/**
* Get the wheel segment at a specific angle
*
* #param {number} angle
* #returns {jQuery}
*/
function segmentAtAngle( angle ) {
angle = normaliseAngle( angle );
const $found = $wheel.find( '.wheel__segment' ).filter( function() {
const startAngle = parseFloat( $( this ).data( 'start-angle' ) );
const endAngle = parseFloat( $( this ).data( 'end-angle' ) );
return angle >= startAngle && angle < endAngle;
} );
return $found;
}
/**
* #var {integer} Unique ID of requestAnimationFrame callback
*/
var animationId = window.requestAnimationFrame( function tick() {
// Time passed since wheel started spinning (in seconds)
const timePassed = ( new Date() - wheelStarted ) / 1000;
// Speed modifier value (can't be zero)
let speedModifier = parseInt( spinSpeed ) || 1;
// Decelerate animation if we're over the animation duration
if ( timePassed > spinDuration ) {
const decelTicks = ( spinDuration - 1 ) * 60;
const deceleration = Math.exp( Math.log( 0.0001 / speedModifier ) / decelTicks );
const decelRate = ( 1 - ( ( timePassed - spinDuration ) / 10 ) ) * deceleration;
speedModifier = speedModifier * decelRate;
// Stop animation from going in reverse
if ( speedModifier < 0 ) {
speedModifier = 0;
}
}
// Print debug info
logInfo( {
timePassed: timePassed,
speedModifier: speedModifier,
wheelAngle: wheelAngle,
normalisedAngle: normaliseAngle( wheelAngle )
} );
// Wheel not moving, animation must have finished
if ( speedModifier <= 0 ) {
window.cancelAnimationFrame( animationId );
const $stopped = segmentAtAngle( wheelAngle );
alert( $stopped.text() );
return;
}
// Increase wheel angle for animating upwards
if ( spinDirection === 'up' ) {
wheelAngle += speedModifier;
}
// Decrease wheel angle for animating downwards
else {
wheelAngle -= speedModifier;
}
// CSS transform value
const transform = `rotateX(${wheelAngle}deg) scale3d(0.875, 0.875, 0.875)`;
$wheel.css( {
'-webkit-transform': transform,
'-moz-transform': transform,
'-ms-transform': transform,
'-o-transform': transform,
'transform': transform,
'transform-origin': `50% calc(50% + ${height/2}px)`,
'margin-top': `-${height}px`
} );
// New tick
animationId = window.requestAnimationFrame( tick );
} );
} )( jQuery );
*, *:before, *:after {
box-sizing: border-box;
}
.app {
display: flex;
flex-direction: row;
padding: 15px;
}
textarea#log {
width: 300px;
}
.wheel {
perspective: 1000px;
border: 1px solid #333;
margin: 0 25px;
flex-grow: 1;
}
.wheel:after {
content: '';
display: block;
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 2px;
background-color: red;
transform: translateY(-50%);
}
.wheel .wheel__inner {
position: relative;
width: 200px;
height: 350px;
margin: 0 auto;
transform-style: preserve-3d;
}
.wheel .wheel__inner .wheel__segment {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 40px;
position: absolute;
top: 50%;
background-color: #ccc;
}
.wheel .wheel__inner .wheel__segment:nth-child(even) {
background-color: #ddd;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="app">
<textarea id="log"></textarea>
<div class="wheel">
<div class="wheel__inner">
</div>
</div>
</div>
There are two problems.
The following figure shows the state at wheelAngle = 0:
In your code, item 0 has startAngle = 0 and endAngle = some positive value. This does not correspond to what you see. Actually, item 0 should be centered around 0. So you need to offset your regions by half the angle width of an item:
var rotateAngle = angle * i;
var transform = `rotateX(${ rotateAngle }deg) translateZ(${ radius }px)`;
var startAngle = rotateAngle - angle / 2
var endAngle = rotateAngle + angle / 2;
The second problem is your normalize function. You take the absolute value and therefore lose any orientation information. Here is a better version of the function:
function normaliseAngle( angle ) {
angle = -angle;
return angle - 360 * Math.floor(angle / 360);
}
The main issue are the start/end angles. I updated the logic like below:
$segment.attr('data-start-angle', -startAngle + angle / 2);
$segment.attr('data-end-angle', -endAngle + angle / 2);
And also
function normaliseAngle(angle) {
angle = angle % 360;
if (angle > 0)
angle = angle - 360;
return angle;
}
A negative rotation will show you the elements starting from the first one (and not a positive rotation). You need to also consider an offset of angle / 2 since the startAngle will put you in the middle of the element. Then you should logically normalize your angle to a negative value.
Full code
(function($) {
// Settings
const items = 28; // Segments on wheel
const spinSpeed = randNumber(1, 10); // Spin speed multiplier
const spinDuration = randNumber(2, 5); // In seconds
const spinDirection = randNumber(0, 1) ? 'up' : 'down'; // Animate up or down
// Vars
const $wheel = $('.wheel .wheel__inner');
const diameter = $wheel.height();
const radius = diameter / 2;
const angle = 360 / items;
const circumference = Math.PI * diameter;
const height = circumference / items;
// Trackers
let wheelAngle = 0;
const wheelStarted = new Date();
// Add segments to the wheel
for (let i = 0; i < items; i++) {
var startAngle = angle * i;
var endAngle = angle * (i + 1);
var transform = `rotateX(${ startAngle }deg) translateZ(${ radius }px)`;
var $segment = $('<div>', {
class: 'wheel__segment',
html: `<span>Item ${ i }</span>`
}).css({
'transform': transform,
'height': height,
});
// Add start and end angles for this segment
$segment.attr('data-start-angle', -startAngle + angle / 2);
$segment.attr('data-end-angle', -endAngle + angle / 2);
$segment.appendTo($wheel);
}
/**
* Print debug info to DOM
*
* #param {object}
*/
function logInfo(data) {
const $log = $('textarea#log');
let logString = '';
logString += '-----' + "\n";
for (var key in data) {
logString += `${ key }: ${ data[ key ] }` + "\n";
}
logString += "\n";
// Prepend log to last value
logString += $log.val();
// Update field value
$log.val(logString);
}
/**
* Get random number between min & max (inclusive)
*
* #param {number} min
* #param {number} max
* #returns {number}
*/
function randNumber(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* Limit angles to 0 - 360
*
* #param {number}
* #returns {number}
*/
function normaliseAngle(angle) {
angle = angle % 360;
if (angle > 0)
angle = angle - 360;
return angle;
}
/**
* Get the wheel segment at a specific angle
*
* #param {number} angle
* #returns {jQuery}
*/
function segmentAtAngle(angle) {
angle = normaliseAngle(angle);
const $found = $wheel.find('.wheel__segment').filter(function() {
const startAngle = parseFloat($(this).data('start-angle'));
const endAngle = parseFloat($(this).data('end-angle'));
return angle >= endAngle && angle < startAngle;
});
return $found;
}
/**
* #var {integer} Unique ID of requestAnimationFrame callback
*/
var animationId = window.requestAnimationFrame(function tick() {
// Time passed since wheel started spinning (in seconds)
const timePassed = (new Date() - wheelStarted) / 1000;
// Speed modifier value (can't be zero)
let speedModifier = parseInt(spinSpeed) || 1;
// Decelerate animation if we're over the animation duration
if (timePassed > spinDuration) {
const decelTicks = (spinDuration - 1) * 60;
const deceleration = Math.exp(Math.log(0.0001 / speedModifier) / decelTicks);
const decelRate = (1 - ((timePassed - spinDuration) / 10)) * deceleration;
speedModifier = speedModifier * decelRate;
// Stop animation from going in reverse
if (speedModifier < 0) {
speedModifier = 0;
}
}
// Print debug info
logInfo({
timePassed: timePassed,
speedModifier: speedModifier,
wheelAngle: wheelAngle,
normalisedAngle: normaliseAngle(wheelAngle)
});
// Wheel not moving, animation must have finished
if (speedModifier <= 0) {
window.cancelAnimationFrame(animationId);
const $stopped = segmentAtAngle(wheelAngle);
alert($stopped.text());
return;
}
// Increase wheel angle for animating upwards
if (spinDirection === 'up') {
wheelAngle += speedModifier;
}
// Decrease wheel angle for animating downwards
else {
wheelAngle -= speedModifier;
}
// CSS transform value
const transform = `rotateX(${wheelAngle}deg) scale3d(0.875, 0.875, 0.875)`;
$wheel.css({
'-webkit-transform': transform,
'-moz-transform': transform,
'-ms-transform': transform,
'-o-transform': transform,
'transform': transform,
'transform-origin': `50% calc(50% + ${height/2}px)`,
'margin-top': `-${height}px`
});
// New tick
animationId = window.requestAnimationFrame(tick);
});
})(jQuery);
*,
*:before,
*:after {
box-sizing: border-box;
}
.app {
display: flex;
flex-direction: row;
padding: 15px;
}
textarea#log {
width: 300px;
}
.wheel {
perspective: 1000px;
border: 1px solid #333;
margin: 0 25px;
flex-grow: 1;
}
.wheel:after {
content: '';
display: block;
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 2px;
background-color: red;
transform: translateY(-50%);
}
.wheel .wheel__inner {
position: relative;
width: 200px;
height: 350px;
margin: 0 auto;
transform-style: preserve-3d;
}
.wheel .wheel__inner .wheel__segment {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 40px;
position: absolute;
top: 50%;
background-color: #ccc;
}
.wheel .wheel__inner .wheel__segment:nth-child(even) {
background-color: #ddd;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="app">
<textarea id="log"></textarea>
<div class="wheel">
<div class="wheel__inner">
</div>
</div>
</div>
I'm kind of a noob to JS and I'm starting to get used to the basics but I am struggling on passing a variable from within a function that is already in a function.. to another function.
I have looked up scope and I have declared a global variable but when I console log it displays undefined. Here is my code. The project is a bar that shows how long you are into your shift at work in percentage.
I need the percentage variable to be passed out of the timed loop and into the moveLoadingBar Function so that the bar will slowly move up as the day goes on. What am I missing?
const target = 100; //percent
let percentage;
function setup() {
//Set the start point for today at 09:00:00am
let start = new Date();
start.setHours(9, 00, 00);
setInterval(timeConversion, 1000);
function timeConversion() {
//Work out the difference in miliseconds from now to the start point.
let now = new Date();
let distance = now -= start;
// Time calculations for hours, minutes and seconds
let hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
let minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
let seconds = Math.floor((distance % (1000 * 60)) / 1000);
let miliseconds = distance;
//Percentage of day complete **** struggling to pass variable to global variable ****
percentage = (miliseconds / target) * 100
}
}
function moveLoadingBar() {
let loadingBar = document.getElementById("myBar");
let id = setInterval(frame, 1000);
function frame() {
if (percentage >= target) {
clearInterval(id);
} else {
loadingBar.style.width = percentage + "%";
}
}
}
//debugging
setup();
moveLoadingbar();
console.log(percentage);
console.log(target);
CSS
#myProgress {
width: 100%;
background-color: #ddd;
}
#myBar {
width: 1%;
height: 30px;
background: linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%);
}
HTML
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="stylesheet.css">
<title>Hello world</title>
</head>
<body>
<h1>Progress Bar</h1>
<div id="myProgress">
<div id="myBar"></div>
</div>
<br>
</body>
<script src="index.js"></script>
</html>
JavaScript processes code synchronously. There is only one thread of execution. The function you pass to setTimeout() won't be invoked until the JavaScript call stack is idle. So, after you call setTimeout(), the rest of your code continues to process, all the way down to your console.log() statements at the bottom, but at that point, your timer callback still hasn't run, so you see its current value (undefined) logged.
So, really the problem is that your console.log() statements are running before the asynchronous timer runs. Moving the log into the async function shows you that it's working.
const target = 100; //percent
let percentage;
function setup() {
//Set the start point for today at 09:00:00am
let start = new Date();
start.setHours(9, 00, 00);
setInterval(timeConversion, 1000);
function timeConversion() {
//Work out the difference in miliseconds from now to the start point.
let now = new Date();
let distance = now -= start;
// Time calculations for hours, minutes and seconds
let hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
let minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
let seconds = Math.floor((distance % (1000 * 60)) / 1000);
let miliseconds = distance;
//Percentage of day complete **** struggling to pass variable to global variable ****
percentage = (miliseconds / target) * 100;
console.log('p',percentage); // <-- You have to wait until the function has run
}
}
function moveLoadingBar() {
let loadingBar = document.getElementById("myBar");
let id = setInterval(frame, 1000);
function frame() {
if (percentage >= target) {
clearInterval(id);
} else {
loadingBar.style.width = percentage + "%";
}
}
}
//debugging
setup();
moveLoadingBar();
console.log(target);
#myProgress {
width: 100%;
background-color: #ddd;
}
#myBar {
width: 1%;
height: 30px;
background: linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%);
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="stylesheet.css">
<title>Hello world</title>
</head>
<body>
<h1>Progress Bar</h1>
<div id="myProgress">
<div id="myBar"></div>
</div>
<br>
</body>
<script src="index.js"></script>
</html>
moveLoadingbar is nested function inside Setup function. Which you cant access directly,and so it was throwing an error.
const target = 100; //percent
var percentage;
function setup() {
debugger;
//Set the start point for today at 09:00:00am
let start = new Date();
start.setHours(9, 00, 00);
setInterval(timeConversion, 1000);
function timeConversion() {
//Work out the difference in miliseconds from now to the start point.
let now = new Date();
let distance = now -= start;
// Time calculations for hours, minutes and seconds
let hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
let minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
let seconds = Math.floor((distance % (1000 * 60)) / 1000);
let miliseconds = distance;
//Percentage of day complete **** struggling to pass variable to global variable ****
percentage = (miliseconds / target) * 100;
}
}
function moveLoadingBar() {
let loadingBar = document.getElementById("myBar");
let id = setInterval(frame, 1000);
function frame() {
if (percentage >= target) {
clearInterval(id);
} else {
loadingBar.style.width = percentage + "%";
}
}
}
setup();
//moveLoadingbar(); // here is problem
console.log(percentage);
console.log(target);
#myProgress {
width: 100%;
background-color: #ddd;
}
#myBar {
width: 1%;
height: 30px;
background: linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1>Progress Bar</h1>
<div id="myProgress">
<div id="myBar"></div>
</div>
<br>
Hello I have been trying to figure something out for a couple days, I'm hoping someone may be able to shed some light on the situation.
I've been trying to code out a learning project. The goal is basically a gantt chart where I'd like to plot some events on eventually.
I am drawing out the timeline on a canvas, right now I have the "Seconds" lines being drawn 50px apart, with 4 shorter lines between them representing 200ms spaces.enter code here
var aTime = "00:1:00.0";
var h, m, s, ms, totalSeconds, thecanvas = null;
// within the loop at line 76 I'm trying ( i * secondsSpacing ) to get the X
//position to draw the lines for each second.
//Why would this not drawing the lines 50 pixels apart?
var secondsSpaceing = 50;
var spaceTime = 44;
var mousePositioning = { x:0, y:0};
var zoom1a = 1;
function drawStroke(sX, sY, eX, eY, color) {
thecontext.strokeStyle=color;
thecontext.lineWidth=1;
thecontext.beginPath();
thecontext.moveTo(sX,sY);
thecontext.lineTo(eX,eY);
thecontext.stroke();
}
function secToMinSec(seconds) {
var min = Math.floor(seconds / 60);
var sec = Math.ceil(seconds % 60);
sec = (sec < 10) ? "0" + sec : sec;
return new Array(min, sec);
}
var mouseXY = function(eve) {
if(!eve) var eve = window.event;
var totalOffsetX = 0;
var totalOffsetY = 0;
var canvasX = 0;
var canvasY = 0;
var canvas = this;
do{
totalOffsetX += canvas.offsetLeft;
totalOffsetY += canvas.offsetTop;
}
while(canvas = canvas.offsetParent)
canvasX = eve.pageX - totalOffsetX;
canvasY = eve.pageY - totalOffsetY;
return {'x':canvasX, 'y':canvasY}
}
$(document).ready(function() {
thecanvas = document.getElementById("thecanvas");
thecontext = thecanvas.getContext("2d");
HTMLCanvasElement.prototype.xy = mouseXY;
$(thecanvas).mousemove(function(e) {
mousePositioning = thecanvas.xy(e);
$("#output").html( "X = " + mousePositioning.x +
"<br> Y = " + mousePositioning.y );
});
var splitTimeStrMS = aTime.split('.');
var splitTimeStr = splitTimeStrMS[0].split(':');
h = parseInt(splitTimeStr[0]);
m = parseInt(splitTimeStr[1]);
s = parseInt(splitTimeStr[2]);
ms = parseFloat(splitTimeStrMS[1]);
var X = 60;
totalSeconds = (h * X * X) + (m * X) + s;
var divided = Math.ceil(totalSeconds / zoom1a);
var timeChartArray = new Array();
for(var i = 0; i <= divided; i++) {
timeChartArray.push(i * zoom1a);
}
var neededCanvasWidth = Math.ceil(timeChartArray.length * secondsSpaceing);
var timeStr = null;
var lineColor = "#000000";
if(neededCanvasWidth > ($("#thecanvas").attr("width"))) {
$("#thecanvas").attr("width", neededCanvasWidth);
thecontext.font="normal 12px Arial";
thecontext.fillStyle = lineColor;
for(var i = 0; i < timeChartArray.length; i++) {
//draw the line
var xline = parseFloat(i * secondsSpaceing);
drawStroke(xline, 0, xline, 8, lineColor);
//draw the time text
var timeStr = secToMinSec( timeChartArray[i] );
var timeFormatted = timeStr[0] + ":" + timeStr[1];
var timeXpos = (xline - 10);
if(timeFormatted != "0:00") {
thecontext.fillText(timeFormatted, timeXpos,24);
}
}
}
});
#canvasOut {position:relative; width:100%; width:700px; background:#222222;
overflow:visible; }
#thecanvas {position:relative; height:140px; background:#fff; }
<script
src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<div id="canvasOut">
<canvas width="200" id="thecanvas"></canvas>
</div>
<div id="output">
</div>
<div id="output2">
</div>
If you move the mouse over the one second mark, you will see it is at x:50, two second mark is x:100 but then the three second mark is x:149, the same pattern continues and I keep losing seconds. By the fifth second, it should be at x:250 but its lost two seconds and is x:248. I'm still trying to figure this out myself but hopeful someone can shed some light as it's becoming discouraging. Thanks for reading.
EDIT: the code snippet worked in the editor, but I noticed when I press the "Run snippet" button that it's not showing the mouse position as it did in the editor, and on jsFiddle.
Here is a link to the project on jsFiddle:
https://jsfiddle.net/4y0q2pdw/19/
Thanks again
Check this.I saw that every 1 sec the function subtracted 4px. So I replace the
var xline = parseFloat(i * secondsSpaceing);
with the
var xline =parseFloat((i * secondsSpaceing)+3.6*i);
Initially I set instead of 3.6 the integer 4 but the function added then 1 or 2px after the 8 seconds line.So to be more accurate I replace the 4 by 3.6 that is more accurate.
I have this JavaScript http://jsfiddle.net/ZDsMa/433/ to pick a random number. I put this on html div and I want to start scrambling the numbers with keypress instead of onClick, also to stop again with keypress:
var output, started, duration, desired;
// Constants
duration = 5000;
desired = '5';
// Initial setup
output = $('#output');
started = new Date().getTime();
// Animate!
animationTimer = setInterval(function() {
// If the value is what we want, stop animating
// or if the duration has been exceeded, stop animating
if (output.text().trim() === desired || new Date().getTime() - started > duration) {
clearInterval(animationTimer);
} else {
console.log('animating');
// Generate a random string to use for the next animation step
output.text(
''+
Math.floor(Math.random() * 10)+
Math.floor(Math.random() * 10)
);
}
}, 100);
You can wrap the animation in a function and then call it on keypress... something like this:
var output, started, duration, desired;
var sel = [];
// Constants
duration = 5000;
desired = '5';
// Initial setup
output = $('#output');
var keyPressed = false;
$(document).keypress(function(){
if (!keyPressed){
started = new Date().getTime();
scramble();
keyPressed = true;
}
else{
keyPressed = false;
}
});
// Animate!
var scramble = function(){
animationTimer = setInterval(function() {
// If the value is what we want, stop animating
// or if the duration has been exceeded, stop animating
if (output.text().trim() === desired || new Date().getTime() - started > duration || !keyPressed) {
while($.inArray(output.text().trim(),sel)>-1){
output.text(
''+
Math.floor(Math.random() * 10)+
Math.floor(Math.random() * 10)
);
}
clearInterval(animationTimer);
keyPressed = false;
sel.push(output.text());
$("#selected").text(sel);
} else {
// Generate a random string to use for the next animation step
output.text(
''+
Math.floor(Math.random() * 10)+
Math.floor(Math.random() * 10)
);
}
}, 100);
}
#output {
margin: 20px;
padding: 20px;
background: gray;
border-radius: 10px;
font-size: 80px;
width: 80px;
color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output">--</div>
<div id="selected"></div>