I have a javascript function that my game loops through (hopefully) 60 times a second that controls input, drawing, etc.
The way it is currently coded it seems to be always be around 52, noticeably lower than 60 fps, and it even dips to 25-30 fps even when nothing else is happening
function loop() {
setTimeout(function () {
requestAnimationFrame(loop);
time += (1000 / 60);
if (time % 600 == 0) {
oldtick = tick;
tick += 1;
time = 0;
aiMovement();
combat();
}
context.clearRect(0, 0, c.width, c.height);
drawMap();
playerInput();
movePlayer();
drawEntities();
drawPopups();
var thisLoop = new Date;
var fps = 1000 / (thisLoop - lastLoop);
lastLoop = thisLoop;
context.drawImage(cursor, mouse.x, mouse.y, 16, 16);
context.fillStyle = "#ffff00";
context.fillText("FPS: " + Math.floor(fps) + " Time: " + Math.floor(time) + " tick: " + tick, 10, 450);
context.fillText("Gold: " + gold, 10, 460);
//requestAnimationFrame(loop);
}, 1000 / 60);
}
if I remove the setTimeout and the first requestAnimationFrame from the top and uncomment the reuqestAnimationFrame at the bottom and remove the other setTimeout things, the FPS improves to 58 but rapidly changes between 58 and 62, again, not statically 60. Does it have something to do with 1000/60 is not a whole number? How would people using requestAnimationFrame achieve 60 fps if this was true?
Don`t use setTimeout or setInterval for animation.
The problem is that you are calling a timer event from within the request animation event. Remove the timeout and just use requestAnimationFrame.
function loop(time){ // microsecond timer 1/1,000,000 accuracy in ms 1/1000th
// render code here
requestAnimationFrame(loop);
// or render code here makes no diff
}
requestAnimationFrame(loop); // to start
RequestAnimationFrame (rAF) is always in sync (unless the browser has vertical sync turned off). The next frame will be presented in 1/60th, 2/60th, 3/60th etc... of a second. You will not get 52frame per second using rAF, rather 60fps, 30fps, 15fps, etc...
The Demo below show the difference in use.
Because requestAnimationFrame uses some smarts to time the animation they can not both run at the same time so click on the canvas to start it.
You can also add a load to simulate rendering. There is a 14ms load and a 28 ms load. The 28ms load is design to mess up rAF as it will on many machines flick between 30 and 60 frames per second. The point is to show that rAF can only have 60, 30, 20,.. etc frames per second.
var ctx1 = can1.getContext("2d");
var ctx2 = can2.getContext("2d");
var ctx3 = can3.getContext("2d");
var lastTime1 = 0;
var lastTime2 = 0;
var lastTime3 = 0;
var frameFunction = frame1;
var frameText = "";
var drag = false;
var loadAmount = 14;
var stats = [{
data : [],
pos : 0,
add(val){
this.data[(this.pos ++) % 150] = val;
}
},{
data : [],
pos : 0,
add(val){
this.data[(this.pos ++) % 150] = val;
}
},{
data : [],
pos : 0,
add(val){
this.data[(this.pos ++) % 150] = val;
}
}
];
for(let i = 0; i < 150; i += 1){
stats[0].add(0);
stats[1].add(0);
stats[2].add(0);
}
setupContext(ctx1);
setupContext(ctx2);
setupContext(ctx3);
drawFrameTime(ctx1,0);
drawFrameTime(ctx2,0);
drawFrameTime(ctx3,0);
can1.addEventListener("click",()=>frameFunction = frame1);
can2.addEventListener("click",()=>frameFunction = frame2);
can3.addEventListener("click",()=>frameFunction = frame3);
load.addEventListener("click",()=>{
if(drag){
drag = false;
load.value = "Add load.";
}else{
drag = true;
load.value = "Remove load.";
}
});
loadPlus.addEventListener("click",()=>{
if(loadAmount === 14){
loadAmount = 28;
loadPlus.value = "28ms";
}else{
loadAmount = 14;
loadPlus.value = "14ms";
}
});
function CPULoad(){
if(drag){
var stopAt = performance.now() + loadAmount;
while(performance.now() < stopAt);
}
}
function setupContext(ctx){
ctx.font = "64px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
}
function drawStats(ctx,stat){
ctx.setTransform(1,0,0,1,0,64);
ctx.strokeStyle = "red";
ctx.strokeRect(-1,16.666,152,0);
ctx.strokeStyle = "black";
ctx.beginPath();
var i = stat.pos + 149;
var x = 0;
ctx.moveTo(x,stat.data[(i++) % 150]);
while(x ++ < 150 && stat.data[i % 150] !== undefined) {
ctx.lineTo(x,stat.data[(i++) % 150]);
}
ctx.stroke();
}
function drawFrameTime(ctx,time){
ctx.fillStyle = "black";
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
if(time > 0){
ctx.fillStyle = drag ? "red" : "black";
ctx.setTransform(1,0,0,1,ctx.canvas.width / 2,ctx.canvas.height *0.25);
ctx.fillText(time,0,0);
ctx.setTransform(0.4,0,0,0.4,ctx.canvas.width / 2,ctx.canvas.height * 0.75);
ctx.fillText(Math.round(1000 / Number(time)) + "fps",0,0);
}else{
ctx.setTransform(0.4,0,0,0.4,ctx.canvas.width / 2,ctx.canvas.height * 0.75);
ctx.fillText("Click to Start.",0,0);
}
ctx.fillStyle = "black";
ctx.setTransform(0.2,0,0,0.2,ctx.canvas.width / 2,ctx.canvas.height * 0.9);
ctx.fillText(frameText,0,0);
if(drag){
ctx.fillStyle = "red";
ctx.setTransform(0.2,0,0,0.2,ctx.canvas.width / 2,ctx.canvas.height * 0.5);
ctx.fillText("Load " + loadAmount + "ms",0,0);
}
}
function frame1(time){
requestAnimationFrame(frameFunction);
frameText = "Using rAF.";
var frameTime = time - lastTime1;
lastTime1 = time;
stats[0].add(frameTime);
drawFrameTime(ctx1,frameTime.toFixed(2));
drawStats(ctx1,stats[0]);
CPULoad()
}
function frame2() {
setTimeout(function () {
frameText = "Using rAF & setTimeout.";
var time = performance.now();
var frameTime = time - lastTime2;
stats[1].add(frameTime);
lastTime2 = time;
drawFrameTime(ctx2, frameTime.toFixed(2));
drawStats(ctx2,stats[1]);
CPULoad();
requestAnimationFrame(frameFunction);
}, 1000 / 60);
}
function frame3() {
setTimeout(frameFunction,1000/60);
frameText = "SetTimeout by itself.";
var time = performance.now();
var frameTime = time - lastTime3;
stats[2].add(frameTime);
lastTime3 = time;
drawFrameTime(ctx3, frameTime.toFixed(2));
drawStats(ctx3,stats[2]);
CPULoad();
}
requestAnimationFrame(frameFunction);
body {
font-family : arial ;
}
canvas {
border : 1px solid black;
}
div {
text-align : center;
}
<div><h2>RequestAnimationFrame (rAF)</h2>
rAF V rAF & setTimeout V setTimeout<br>
<canvas id = can1 width = 150></canvas>
<canvas id = can2 width = 150></canvas>
<canvas id = can3 width = 150></canvas><br>
Click the frame to set the current test.<br>
The left frame is using rAF alone, the middle using setTimeout and rAf, and the rigth frame uses setTimeout alone.<br>
Click <input type="button" id=load value="add Load"></input> to simulate a rendering load of around <input type="button" id=loadPlus value="14ms" title="click to change CPU load between 14 and 28ms"></input> <br>
Try draging and selecting this text and see how it effects the different methods.<br>
rAF is by far the most stable of the 3.<br>
</div>
The functions purpose isnt to have 60 FPS, but to draw while a frame is beeing drawn and make the performance better. No computer will stay perfectly at 60 FPS.
Also, why is your requestAnimationFrame IN the timeout?
Related
I made this red line in JavaScript that goes to closest target (balloon 1 to 3) to the player but I need to make it so that it moves like a laser starting from player position into the target position. I thought about multiple ways of implementing this with no luck.
function Tick() {
// Erase the sprite from its current location.
eraseSprite();
for (var i = 0; i < lasers.length; i++) {
lasers[i].x += lasers[i].direction.x * laserSpeed;
lasers[i].y += lasers[i].direction.y * laserSpeed;
//Hit detection here
}
function detectCharClosest() {
var ballon1char = {
x: balloon1X,
y: balloon1Y
};
var ballon2char = {
x: balloon2X,
y: balloon2Y
};
var ballon3char = {
x: balloon3X,
y: balloon3Y,
};
ballon1char.distanceFromPlayer = Math.sqrt((CharX - balloon1X) ** 2 + (CharY - balloon1Y) ** 2);
ballon2char.distanceFromPlayer = Math.sqrt((CharX - balloon2X) ** 2 + (CharY - balloon2Y) ** 2);
ballon3char.distanceFromPlayer = Math.sqrt((CharX - balloon3X) ** 2 + (CharY - balloon3Y) ** 2);
var minDistance = Math.min(
ballon1char.distanceFromPlayer,
ballon2char.distanceFromPlayer,
ballon3char.distanceFromPlayer);
console.log(ballon1char);
console.log(ballon2char);
console.log(ballon3char);
for (let i = 0; i < 3; i++) {
if (minDistance == ballon1char.distanceFromPlayer)
return ballon1char
if (minDistance == ballon2char.distanceFromPlayer)
return ballon2char
if (minDistance == ballon3char.distanceFromPlayer)
return ballon3char
}
}
function loadComplete() {
console.log("Load is complete.");
canvas = document.getElementById("theCanvas");
ctx = canvas.getContext("2d");
myInterval = self.setInterval(function () { Tick() }, INTERVAL);
myInterval = self.setInterval(function () { laserTicker(detectCharClosest()) }, 2000);
function laserTicker(balloon) {
//gets the closest ballon to go to
laserDo(balloon);
}
function laserDo(balloon) {
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = "#F44336"; // "red";
ctx.moveTo(CharX + 16, CharY + 16);
ctx.lineTo(balloon.x, balloon.y);
// lasers.push({x: })
ctx.stroke();
}
I didn't put all of my code here so If something doesn't make sense please tell me. I'm still new to JavaScript and learning it. One way I thought I could make this work was by taking the distance between the player and the target and dividing it by the speed on the x and y axis then changing having it start from the player position and keeps on adding up on both axis until it reaches the target. That didn't work out though. If you have any suggestions then please tell me.
Thanks
i'm making a stopwatch that is working, but when it changes, the text overlaps itself because it is drawing the text repeatedly. If i remove the strokeText and fillText from the interval, then it does not change. It stays the same. How can i make the function undo itself, or delete the text at the beginning of the interval?
function drawTimer() {
var fontSize = 15;
graph.lineWidth = playerConfig.textBorderSize;
graph.fillStyle = playerConfig.textColor;
graph.strokeStyle = playerConfig.textBorder;
graph.miterLimit = 1;
graph.lineJoin = 'round';
graph.textAlign = 'right';
graph.textBaseline = 'middle';
graph.font = 'bold ' + fontSize + 'px sans-serif';
var gameTimeMinutes = 0;
var gameTimeSeconds = 1;
var gameTime = "";
function addTime() {
gameTimeSeconds += 1;
if (gameTimeSeconds < 10) {
gameTime = gameTimeMinutes + " : 0" + gameTimeSeconds;
} else {
gameTime = gameTimeMinutes + " : " + gameTimeSeconds;
}
if (gameTimeSeconds == 60) {
gameTimeSeconds = 0;
gameTimeMinutes++;
}
graph.strokeText(gameTime, 50, 50);
graph.fillText(gameTime, 50, 50);
}
setInterval(addTime, 1000);
}
Code explanation: gameTimeSeconds is the seconds on the right of the colon, minutes is on the left, and its saying if the seconds is not double digits, then show the zero on the left of it. It also says to add one to minutes when seconds reach 60. Then, write the time. Ex: 0:01, 0:20, 1:20, etc. . Thanks!
I assume you just need to clear your canvas on each interval: context.clearRect(0, 0, canvas.width, canvas.height);
You need to draw each 'layer' every frame for anything that might overlap.
For example, draw the back of the stopwatch, then the texton each frame.
I.E. whatever is drawing the text and background needs to happen inside of addTime
I have a case where I want to draw 3 arc lines and erase them.
First Arc CA should be drawn progressively and then it should be erased progressively. Then arc AB should be drawn and erased and then arc BC should do the same. And then repeat.
My approach:
Using canvas and JS:
I started with canvas, but the anti-aliasing does not effect here. So I thought may be SVG will be better.
var currentEndAngle = 0;
var currentStartAngle = 0;
var currentColor = 'black';
var lineRadius = 300;
var lineWidth = 5;
setInterval(draw, 5);
function draw() {
var can = document.getElementById('canvas1'); // GET LE CANVAS
var canvas = document.getElementById("canvas1");
var context = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius;
var width;
var startAngle = currentStartAngle * Math.PI;
var endAngle = (currentEndAngle) * Math.PI;
currentStartAngle = currentEndAngle - 0.01;
currentEndAngle = currentEndAngle + 0.01;
if (Math.floor(currentStartAngle / 2) % 2) {
currentColor = "white";
radius = lineRadius - 1;
width = lineWidth + 3;
} else {
currentColor = "black";
radius = lineRadius;
width = lineWidth;
}
var counterClockwise = false;
context.beginPath();
context.arc(x, y, radius, startAngle, endAngle, counterClockwise);
context.lineWidth = width;
// line color
context.strokeStyle = currentColor;
context.stroke();
/************************************************/
}
body {
text-align: center;
background: blue;
}
#canvas1 {
width: 500px;
height: 500px;
margin: 0 auto;
}
<canvas id="canvas1" width="700" height="700"></canvas>
Using SVG and CSS
The SVG approach looks smoother. But I don't understand how I can modify the dasharray, dashoffset and radius of circle to get 3 arcs animating.
circle {
fill: transparent;
stroke: black;
stroke-width: 2;
stroke-dasharray: 250;
stroke-dashoffset: 0;
animation: rotate 5s linear infinite;
}
#keyframes rotate {
0% {
stroke-dashoffset: 500;
}
100% {
stroke-dashoffset: 0;
}
}
<svg height="400" width="400">
<circle cx="100" cy="100" r="40" />
</svg>
So if anyone can help me extend the code or give guidance on how I can create three arcs from the svg circle and how the dasharray, dashoffset and radius should be set?
In case you have a better solution then the above 2 approaches then please let me know.
I have also tried to use the drawsvg plugin from GSAP and I guess that might be easier but I am not allowed to use the 'drawsvg' plugin for my project.
For the canvas version, as stated in comments, your antialiasing problem is that you are redrawing over and over on the same pixels.
To avoid this, clear your whole canvas every frame and redraw everything.
For your requested animation, you would have to store both your start angle and your end angle. Then you'll increment one after the other, while swithing when you've passed the division size threshold.
Here is an annotated snippet that will make things more clear I hope.
// settings
var divisions = 3;
var duration = 3000; // in ms
var canvas = document.getElementById("canvas1");
var context = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = (canvas.width / 7) * 2;
context.lineWidth = 4;
// init
var currentSplit = 0;
var splitAngle = (Math.PI * 2) / divisions;
var splitTime = (duration / (divisions*2)); // how much time per split per end
var angles = [0,0]; // here we store both start and end angle
var current = 0;
var startTime = performance.now();
draw();
function draw(currentTime) {
// first convert the elapsed time to an angle
var timedAngle = ((currentTime - startTime) / splitTime) * splitAngle;
// set the current end to this timed angle + the current position on the circle
angles[current] = timedAngle + (splitAngle * currentSplit);
if (timedAngle >= splitAngle) { // one split is done for this end
// it should not go farther than the threshold
angles[current] = (splitAngle * (currentSplit + 1));
current = +(!current) // switch which end should move
startTime = currentTime; // reset the timer
if(!current){ // we go back to the start
currentSplit = (currentSplit + 1) % divisions; // increment our split index
}
}
if(angles[1] > Math.PI*2){ // we finished one complete revolution
angles[0] = angles[1] = current = 0; // reset everything
}
// at every frame we clear everything
context.clearRect(0, 0, canvas.width, canvas.height);
// and redraw
context.beginPath();
context.arc(x, y, radius, angles[0], angles[1], true);
context.stroke();
requestAnimationFrame(draw); // loop at screen refresh rate
}
body {
text-align: center;
}
#canvas1 {
width: 250px;
height: 150px;
}
<canvas id="canvas1" width="500" height="300"></canvas>
You don't really want to modify stroke-dashoffset, because that just shifts the dash patter around the circle.
You have to modify the dash array values anyway, so you might as well just do it all by animating the values in the dash array.
Your circle has radius 40, so the circumference is 251.33. Meaning that each of your three arc has a length of 83.78.
For each of the three stages, we grow the "on" part of the dash from 0 to 83.78. Then we shrink it back down again, while simultaneously growing the previous gap from 83.78 to 167.55. That is so that the tail gets pushed around to the end.
That works for the first two steps, but since the dash pattern starts and ends at the 3 o'clock position (and doesn't wrap through that point), we have to do the tail push for the last stage by using an extra empty dash pair at the start. We grow the gap on that one from 0 to 83.78 instead.
circle {
fill: transparent;
stroke: black;
stroke-width: 2;
animation: rotate 5s linear infinite;
}
#keyframes rotate {
0% { stroke-dasharray: 0 0 0 83.78 0 83.78 0 83.78; }
16.7% { stroke-dasharray: 0 0 0 83.78 83.78 0 0 83.78; }
33.3% { stroke-dasharray: 0 0 0 167.55 0 0 0 83.78; }
50% { stroke-dasharray: 0 0 0 83.78 0 83.78 83.78 0; }
66.6% { stroke-dasharray: 0 0 0 83.78 0 167.55 0 0; }
83.3% { stroke-dasharray: 0 0 83.78 0 0 83.78 0 83.78; }
100% { stroke-dasharray: 0 83.78 0 0 0 83.78 0 83.78; }
}
<svg height="400" width="400">
<circle cx="100" cy="100" r="40" />
</svg>
Javascript extends HTML
Canvas, (or CSS, HTML, SVG) combined with javascript always wins out over CSS, SVG, HTML alone because Javascript is far more adaptable. HTML, CSS and SVG are declarative languages, while JavaScript is a fully functional imperative language that can do anything any other programing language can do.
You use javascript to add to the HTML, CSS, SVG functionality, effectively declaring new behaviour for these languages.
Once you have defined the Javascript functionality you can forget about the javascript and use the HTML, CSS, or SVG calling upon the new behaviours as needed.
In this case all elements with the class name "segmentedProgress" will become an animated progress. You can set up as many properties as you like to control the behaviour and add them to the element's data attribute.
eg
<div class="segmentedProgress"></div>
<!-- showing defaults as above element will be setup -->
<div class="segmentedProgress"
data-angle-steps = 3 <!-- number of segments. (integers only) -->
data-speed = 1000 <!-- Time per segment in ms -->
data-easing = "1.2" <!-- easing power -->
data-line-width = "0.1" <!-- as fraction of radius -->
data-radial-size = "0.33" <!-- as fraction of shortest dimension -->
data-color = "black" <!-- colour of line -->
></div>
As long as the Javascript has been included the progress will automatically appear on the page for each element that is correctly configured. If you have your server setup to recognise page content dependencies then the above is all you need to do to add the behaviour to the page as the server will add what is needed to make it run.
The javascript
It does not take much javascript to implement. You find all the elements that have the appropriate class name and add them to an array of progress items. Then animate them as needed.
document.addEventListener("load", function(){
var elements = [...document.body.querySelectorAll(".segmentedProgress")];
if(elements.length === 0){ // exit if nothing found
return;
}
// singleton to isolate from onload
(function(){
const error = 0.01; // Math too perfect causes zero len arc to draw nothing. Error makes sure there is always some length in the drawn arc
const items = []; // array of progress items
// each progress item defaults
var defaults = {
angleSteps : 3, // number of segments. (integers only)
speed : 1000, // Time per segment in ms
easing : 1.2, // easing power where 1 = no easing 2 = normal quadratic easing 1/2= inverse quadratic easing
lineWidth : 0.1, // as fraction of radius
radialSize : 0.33,// as fraction of shortest dimension
color : "black", // colour of line
complete : false, // not used
resize () { // resize the canvas and set size dependent vars
this.bounds = this.element.getBoundingClientRect();
this.w = this.canvas.width = this.bounds.width;
this.h = this.canvas.height = this.bounds.height;
this.canvas.style.top = (this.bounds.top + scrollY) + "px";
this.canvas.style.left = (this.bounds.left + scrollX) + "px";
this.pos = { x : this.w / 2, y : this.h / 2}; // position of circle
this.radius = Math.min(this.w, this.h) * this.radialSize; // radius of circle
// set canvas state constants
this.ctx.lineCap = "round";
},
update (time) { // updates and renders
var segStart, segProgress, pp, ctx, ang;
ctx = this.ctx; // alias to this.ctx
// clear the canvas
ctx.clearRect(0, 0, this.w, this.h);
// get current selment angle
ang = Math.PI * 2 / this.angleSteps, // Radians per segment
// set the time at the correct speed
time /= this.speed;
// get the segment start position in radians
segStart = Math.floor(time % this.angleSteps) * ang;
// get the unit progress of this stage doubled for grow and shrink stages
var segProgress = (time % 1) * 2;
var pp = segProgress % 1; // pp partial progress
pp = (pp ** this.easing) / ((pp ** this.easing) + (1 - pp) ** this.easing); // add some easing
ctx.beginPath();
// first half of progress is growth
if(segProgress <= 1){
ctx.arc(this.pos.x, this.pos.y, this.radius, segStart, segStart + pp * ang + error);
}else{
// second half of progress is shrink
ctx.arc(this.pos.x, this.pos.y, this.radius, segStart + pp * ang - error, segStart + ang);
}
ctx.strokeStyle = this.color;
ctx.lineWidth = this.radius * this.lineWidth;
ctx.stroke();
}
}
// create prgress item for each found element
elements.forEach(element => {
var pItem = {...defaults}; // progress item
pItem.element = element;
// get any element setting that overwrite the defaults
Object.keys(defaults).forEach(key => {
if(typeof defaults[key] !== "function"){
if(element.dataset[key] !== undefined){
pItem[key] = element.dataset[key];
if(! isNaN(element.dataset[key])){
pItem[key] = Number(pItem[key]);
}
}
}
});
pItem.canvas = document.createElement("canvas");
pItem.ctx = pItem.canvas.getContext("2d");
pItem.canvas.style.position = "absolute";
pItem.resize();
items.push(pItem);
element.appendChild(pItem.canvas);
});
elements.length = 0; // let go of elements
// change size on resize
window.addEventListener("resize", () =>{
items.forEach(pItem => pItem.resize());
});
// start the animation
requestAnimationFrame(update);
// main update loop
function update (time) {
items.forEach(pItem => {
pItem.update(time);
});
requestAnimationFrame(update);
}
}());
}());
As a demo
//document.addEventListener("load",()=>{
;(function(){
var elements = [...document.body.querySelectorAll(".segmentedProgress")];
if (elements.length === 0) { return }
(function () {
const error = 0.001; // Math too perfect causes zero len arc to draw nothing. Error makes sure there is always some length in the drawn arc
const items = []; // array of progress items
var defaults = {
angleSteps : 3, // number of segments. (integers only)
speed : 1000, // Time per segment in ms
easing : 1.2, // easing power where 1 = no easing 2 = normal quadratic easing 1/2= inverse quadratic easing
lineWidth : 0.1, // as fraction of radius
radialSize : 0.33,// as fraction of shortest dimension
color : "black", // colour of line
complete : false, // not used
resize () { // resize the canvas and set size dependent vars
this.bounds = this.element.getBoundingClientRect();
this.w = this.canvas.width = this.bounds.width;
this.h = this.canvas.height = this.bounds.height;
this.canvas.style.top = (this.bounds.top + scrollY) + "px";
this.canvas.style.left = (this.bounds.left + scrollX) + "px";
this.pos = { x : this.w / 2, y : this.h / 2}; // position of circle
this.radius = Math.min(this.w, this.h) * this.radialSize; // radius of circle
this.ctx.lineCap = "round";
},
update (time) { // updates and renders
var segStart, segProgress, pp, ctx, ang;
ctx = this.ctx; // alias to this.ctx
ctx.clearRect(0, 0, this.w, this.h);
ang = Math.PI * 2 / this.angleSteps, // Radians per segment
time /= this.speed;
segStart = Math.floor(time % this.angleSteps) * ang;
var segProgress = (time % 1) * 2;
var pp = segProgress % 1; // pp partial progress
// babel can not handle the following line even though most
// browsers can
// pp = (pp ** this.easing) / ((pp ** this.easing) + (1 - pp) ** this.easing); // add some easing
// to cover babel error
pp = Math.pow(pp,this.easing) / (Math.pow(pp,this.easing) + Math.pow(1 - pp, this.easing)); // add some easing
ctx.beginPath();
if(segProgress <= 1){
ctx.arc(this.pos.x, this.pos.y, this.radius, segStart, segStart + pp * ang + error);
}else{
ctx.arc(this.pos.x, this.pos.y, this.radius, segStart + pp * ang - error, segStart + ang);
}
ctx.strokeStyle = this.color;
ctx.lineWidth = this.radius * this.lineWidth;
ctx.stroke();
}
}
elements.forEach(element => {
var pItem = {...defaults}; // progress item
pItem.element = element;
Object.keys(defaults).forEach(key => {
if(typeof defaults[key] !== "function"){
if(element.dataset[key] !== undefined){
pItem[key] = element.dataset[key];
if(! isNaN(element.dataset[key])){
pItem[key] = Number(pItem[key]);
}
}
}
});
pItem.canvas = document.createElement("canvas");
pItem.ctx = pItem.canvas.getContext("2d");
pItem.canvas.style.position = "absolute";
pItem.resize();
items.push(pItem);
element.appendChild(pItem.canvas);
});
elements.length = 0;
window.addEventListener("resize", () =>{ items.forEach(pItem => pItem.resize()) });
requestAnimationFrame(update);
function update (time) {
items.forEach(pItem => { pItem.update(time) });
requestAnimationFrame(update);
}
}());
}());
.segmentedProgress {
width : 100px;
height : 100px;
}
.big {
width : 200px;
height : 200px;
}
.large {
width : 512px;
height : 512px;
background : #4AF;
}
4 segment fast.
<div class="segmentedProgress" data-color="red" data-speed ="250" data-line-width="0.3" data-angle-steps=4 ></div>
Default Progress
<div class="segmentedProgress" ></div>
Big progress
<div class="big segmentedProgress" data-color="blue" data-speed ="2500" data-line-width="0.3" data-angle-steps=2 ></div>
60 Seconds two overlap
<div class="large segmentedProgress" data-color="white" data-speed ="1000" data-line-width="0.02" data-angle-steps=60 >
<div class="large segmentedProgress" data-color="white" data-speed ="1000" data-line-width="0.02" data-angle-steps=2 data-radial-size = "0.34">
</div>
For a canvas game, how can I load a canvas fill and text before the window alert? Even by just a few milliseconds.
There is a collision that occurs between two players.
Immediately after, the canvas should fill as colour and text should be displayed.
The problem is the alert appears before this happens.
The alert, when OK pressed, should reload the page - I have found that setTimeout does not work because of the location.reload inside of it.
Quick (nasty) example of how it works currently: JSFiddle
//collision
if (x < object.x - 50 + 60 &&
x + width > object.x - 50 &&
y < object.y - 60 + 60 &&
height + y > object.y - 60) {
//fill before alert
ctx.fillRect(0,0,2000,2000);
ctx.strokeText("You only reached a score of " + score + ", you lose!\nPress 'OK' to try again?", 250, 290);
ctx.fillText("You only reached a score of " + score + ", you lose!\nPress 'OK' to try again?",250,290);
//end game alert
if(!alert("You reached a score of ...")){
location.reload();
}}
setTimeout perfectly works, check this code out
HTML
<canvas id="canvas"></canvas>
<button id="buttonID">click here</button>
Javascript
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
ctx.fillStyle=gradient;
var my_gradient=ctx.createLinearGradient(0,0,0,170);
my_gradient.addColorStop(0,"#7E6189");
my_gradient.addColorStop(1,"#FFFFFF");
ctx.fillStyle=my_gradient;
ctx.lineWidth = 5;
ctx.font="30px Verdana";
var gradient=ctx.createLinearGradient(0,0,canvas.width,0);
gradient.addColorStop("0","magenta");
gradient.addColorStop("0.5","blue");
gradient.addColorStop("1.0","red");
ctx.strokeStyle = 'black';
//irrelevant above
var x = 1 //collision or any true statement
// when the event occures
// create the gredient
document.querySelector('#buttonID').onclick = function() {
ctx.fillRect(0,0,2000,2000);
ctx.strokeText("You only reached a score of ...", 250, 290);
ctx.fillText("You only reached a score of ...",250,290);
setTimeout(function() {
if(!alert("You only reached a score of ...")){
location.reload();
}
}, 300); // chenge the millis to whatever you want
}
i am using an event to execute the code because the permanent alets are annoying
This should work.
var x = 1 //collision or any true statement
if (x == 1) {
ctx.fillRect(0, 0, 2000, 2000);
ctx.strokeText("You only reached a score of ...", 250, 290);
ctx.fillText("You only reached a score of ...", 250, 290);
setTimeout(function(){
alert("You only reached a score of ...");
location.reload();
}, 10);
}
https://jsfiddle.net/ajrwpmb3/10/
Alerts does not return any value. they block the complete code execution. The problem here was that browsers tend to do bulk updates to DOM for changes done inside a function. Since in your code changes to canvas as done and immediately browser thread is blocked by alert, therefore the changes are not applied. in my example i have added 10 ms to break the event of canvas update and alert into 2 blocks in browser's event queue, this allows browser to complete event 1 i.e. canvas update first and then execute 2nd event i.e. show alert which blocks the code.
You can validate this concept by using the timeout value as 0ms.
window.alert, confirm, and prompt should die..
So many answers but I will add one more.
DON'T USE ALERTS... ever... please!!!, for the sake of all the users of your site and because if they are like me the very first time I see an alert on a site I turn them off. The fact that users can block/stop these alerts should be more than reason enough to not use them, because relying on alerts will break your game for pedantic people like me.
A much better Alert
Your best option is to create your own non blocking, customized alert. Create a div set its style to position: absolute, center it to the page, then add an ok button and hide the whole thing.
Give the alert div the id="myAlert" and when you need it just unhide it and wait for the button to be clicked, when the button is clicked hide the alert.
For your game you should add some pause logic, and when the alert needs to be shown just pause the game and show the alert. When the alert has been clicked unpause the games and do what is needed.
The demo just shows how you can implement a simple custom alert and pause a game.
// alert function handles the display of a custom alert
var alert = (function(){
var firstRun = true;
var callback;
function alertCallback(){ // handle the click event
if(typeof callback === "function"){
callback();
}
myAlert.className = "alert hideAlert";
}
function alert(message,callbackO){
myAletMessage.textContent = message;
myAlert.className = "alert showAlert";
if(firstRun){
firstRun = false;
myAlertButton.addEventListener("click",alertCallback)
}
callback = callbackO; // set the callback
}
return alert;
})()
var canvas,ctx;
function addCanvas(){
canvas = document.createElement("canvas");
canvas.style.top = "0px";
canvas.style.left = "0px";
canvas.style.position = "absolute";
document.body.appendChild(canvas);
ctx = canvas.getContext("2d");
}
function resizeCanvas(){
if(canvas === undefined){
addCanvas();
}
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx.font = "32px arial black";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "red";
}
resizeCanvas();
window.addEventListener("resize",resizeCanvas);
// The following code just shows a pretend game with a paused function
// The paused is used when the alert is called.
var paused = false;
var x,y,t;
function update2(timer){
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.globalAlpha = 1; // reset alpha
if(paused){
if(t > 0){
var sc = t / 10 + 0.1;
ctx.setTransform(sc,0,0,sc,x,y);
ctx.fillText("BANG!",0,0);
}
var sc = Math.sin(timer /100) * 0.1 + 1.0;
ctx.fillStyle = "Green";
ctx.setTransform(sc,0,0,sc,canvas.width/2,canvas.height/2);
ctx.fillText("Paused!",0,0);
ctx.fillStyle = "Red";
}else{
if(t > 0){
var sc = t / 10 + 0.1;
ctx.setTransform(sc,0,0,sc,x,y);
ctx.fillText("BANG!",0,0);
t -= 1;
}else{
if(Math.random() < 0.1){
x = (Math.random() * (canvas.width -200))+100;
y = (Math.random() * (canvas.height -200))+100;
t= 10;
}
}
if(Math.random() < 0.01){
alert("A 1 in 100 random event paused the game!",function(){paused = false;});
paused = true;
}
}
requestAnimationFrame(update2);
}
requestAnimationFrame(update2);
.alert {
position : absolute;
z-index : 100;
left : 35%;
width : 30%;
top : 10%;
background: #fff;
border: 1px #000 solid;
text-align: center;
padding: 6px;
}
.hideAlert {
display : none;
}
.showAlert {
display : block;
}
<div class="alert hideAlert" id="myAlert">
<div id="myAletMessage"></div><br>
<input id="myAlertButton" type="button" value="OK got it."></input>
</div>
I am trying to benchmark how many times iOS natively can draw between frame refresh compared to HTML5/JavaScript.
Using this JavaScript I get a number for how may times the browser can draw during the 33 ms (30 Hz):
var ctx = document.getElementById('canvas').getContext('2d');
var img = document.getElementById('img');
var draw = function(load) {
var angle = 0.01;
ctx.clearRect(0,0,400,400);
ctx.save();
ctx.translate(200,200);
for (var i=0; i<load; i++) {
ctx.rotate(angle);
ctx.drawImage(img, 0, 0);
}
ctx.restore();
};
var t, previousTime;
var drawLoad = 1;
var slowCount = 0;
var maxSlow = 10;
var requestAnimationFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame;
t = previousTime = Date.now();
var tick = function() {
var maximumFrameTime = 1000/30; // 30 FPS
t = Date.now();
var elapsed = t - previousTime;
previousTime = t;
if (elapsed < maximumFrameTime || slowCount < maxSlow) {
if (elapsed < maximumFrameTime) {
drawLoad+=10;
} else {
slowCount++;
}
draw(drawLoad);
requestAnimationFrame(tick);
} else {
// found maximum sustainable load at 30 FPS
document.getElementById('res').innerHTML = ("could draw "+(drawLoad)+" in " + maximumFrameTime + " ms");
}
};
requestAnimationFrame(tick);
See http://jsfiddle.net/tbhZs/117/ for complete code with HTML-markup.
I have fiddled around in objective-c without finding a fair way to get a comparable number. Is there a way to produce a comparable benchmark natively in iOS?
Try instruments tool. you can measure the frames per socond via the core animation instrument.