Execute function based on screen size - javascript

I need to execute a specific function based on screen size and screen size changes (Responsive)
So lets say I have 3 functions (For Example)
function red() {
$('div').css('background','#B60C0C')
.text('Screen Size RED');
console.log('RED');
}
function orange() {
$('div').css('background','#EBAE10')
.text('Screen Size ORANGE');
console.log('ORANGE');
}
function green() {
$('div').css('background','#83ba2b')
.text('Screen Size GREEN');
console.log('GREEN');
}
I need to execute function green() when the screen width size 500px or lower
And function orange() when the screen width size 501px to 850px
And function red() when the screen width size 851px or higher
I tried to use resize() but the problem is executing the function when resizing the browser for each pixel repeat executing the same function and this is a very bad way to perform
I need to execute the function when break the point of the screen width size
Ready to use code on jsfiddle http://jsfiddle.net/BaNRq/

Meh; here's a solution.
You could cache the lastBoundry determined to invoke the functions only when a change occurs.
// define the boundries
var bounds = [
{min:0,max:500,func:red},
{min:501,max:850,func:orange},
{min:851,func:green}
];
// define a resize function. use a closure for the lastBoundry determined.
var resizeFn = function(){
var lastBoundry; // cache the last boundry used
return function(){
var width = window.innerWidth; // get the window's inner width
var boundry, min, max;
for(var i=0; i<bounds.length; i++){
boundry = bounds[i];
min = boundry.min || Number.MIN_VALUE;
max = boundry.max || Number.MAX_VALUE;
if(width > min && width < max
&& lastBoundry !== boundry){
lastBoundry = boundry;
return boundry.func.call(boundry);
}
}
}
};
$(window).resize(resizeFn()); // bind the resize event handler
$(document).ready(function(){
$(window).trigger('resize'); // on load, init the lastBoundry
});
Fiddle: http://jsfiddle.net/BaNRq/3/

I actually wanted to put this as a comment to #Nirvana Tikku's answer, however I can't since I don't have 50 reputation, so I'll comment here + I'll add a my own solution so the answer space wouldn't be wasted.
Comment:
I'm sorry to (perhaps) "ruin the party", but either I didn't get the OP's question right or maybe I'm misunderstanding the solution.
Here's what I think the OP wanted: a way to execute a function dynamically based on screen size and without executing the function for each pixel repeat.
In the given solution, though it's quite difficult too see at first, the function do execute for each pixel. Try to put console.log('aaaa') in between the return function lines like so:
return function(){
console.log('aaaa')
var width = window.innerWidth; // get the window's inner width
Run the function, then hit F12 button (in Firefox) and resize the window you'll see it shoots up the entire resizing time (sorry can't upload images either - not enough rep').
I've just spent an entire day trying to replicate this solution on my own,
so that I'd be able to execute a function based on screen-size but without it 'listening' to every pixel along the way.
So far, it seems impossible (unless someone who reads this has a solution), in the meanwhile here's my code, which IMHO is way less complex.
Solution:
var widths = [0, 500, 850];
function resizeFn() {
if (window.innerWidth>=widths[0] && window.innerWidth<widths[1]) {
red();
} else if (window.innerWidth>=widths[1] && window.innerWidth<widths[2]) {
orange();
} else {
green();
}
}
window.onresize = resizeFn;
resizeFn();
see it works in https://jsfiddle.net/BaNRq/16/
BTW this is answer is practically the same as #Vainglory07 minus the jQuery

Using javascript, you may get the value of screen size. And from that you can call your custom functions to get what you want.
//function for screen resize
function screen_resize() {
var h = parseInt(window.innerHeight);
var w = parseInt(window.innerWidth);
if(w <= 500) {
//max-width 500px
// actions here...
red();
} else if(w > 500 && w <=850) {
//max-width 850px
// actions here...
orange();
} else {
// 850px and beyond
// actions here...
green();
}
}
I used window.innerHeight/innerWidth to get the height/width of screen without/disregarding the scrollbars.
// if window resize call responsive function
$(window).resize(function(e) {
screen_resize();
});
and on resize just call the function and also auto call the function on page.ready state.
// auto fire the responsive function so that when the user
// visits your website in a mall resolution it will adjust
// to specific/suitable function you want
$(document).ready(function(e) {
screen_resize();
});
Try to check the output here: OUTPUT :)
hope this helps..

If you're talking about the monitor's resolution settings, consider window.screen, for instance I am using 1280 x 1024 so mine reports
window.screen.height; // 1024
window.screen.width; // 1280
You could also use the avail prefix to ignore things like the task bar. If you just want to work out the browser's visible area then you would use clientHeight and clientWidth on the documentElement, i.e.
document.documentElement.clientWidth; // 1263 (the scrollbar eats up some)
document.documentElement.clientHeight; // 581 (lots lost here, e.g. to console)
As for your fires-too-often problem, introduce a rate limiter, e.g.
function rateLimit(fn, rate, notQueueable) {
var caninvoke = true, queable = !notQueueable,
ready, limiter,
queue = false, args, ctx;
notQueueable = null;
ready = function () { // invokes queued function or permits new invocation
var a, c;
if (queable && queue) {
a = args; c = ctx;
args = ctx = null; queue = false; // allow function to queue itself
fn.apply(c, a);
setTimeout(ready, rate); // wait again
} else
caninvoke = true;
}
limiter = function () { // invokes function or queues function
if (caninvoke) {
caninvoke = false;
fn.apply(this, arguments);
setTimeout(ready, rate); // wait for ready again
} else
args = arguments, ctx = this, queue = true;
};
return limiter;
}
var myRateLimitedFunction = rateLimit(
function () {console.log('foo');},
2e3 // 2 seconds
);
myRateLimitedFunction(); // logged
myRateLimitedFunction(); // logged after rate limit reached

Related

Does ClearRect on a canvase context slow down the code over time? [duplicate]

This question already has an answer here:
HTML5 Canvas performance very poor using rect()
(1 answer)
Closed 2 years ago.
I start the code, watch in dev window, get no errors. The image moves very quickly at first but, after a few seconds, it comes to a craw.
I checked on here but I can't figure it out. I'm a rookie so that could be the problem.
I've tried breaking it out into basic functional steps rather than any class, put "===" and "==" back and forth (cause I do not get the real difference between them), and changed from a "setInterval" to a "setTimeout" just in case I was calling the interval too soon.
I am very much a noob to Javascript and this is my first real work with canvas.
The HTML code simply adds the script with nothing else. The window load at the end of the script runs "startgame".
Thanks for anything you can help me with.
var winX=0;
var winY=0;
var scaleX=0;
var scaleY=0;
var bkcolor="#777777";
var ctx;
var objs=[];
var wallimg = new Image();
wallimg.src = 'wall.png';
var willy=new Image();
willy.src='willy.gif';
var player;
var gameActive=0;
var keyboard=[];
function startGame()
{
var i;
setWindow();
theBoard.start();
gameActive=1;
someting=new Obj(0,10,600,20,"PATTERN",wallimg);
someting.setimage(wallimg);
Obj.Wall(40,100,100,16,wallimg);
Obj.Wall(0,420,620,16,wallimg);
Obj.Wall(0,0,16,440,wallimg);Obj.Wall(584,0,16,440,wallimg);
player=new Obj(24,400,16,16,"PLAYER",willy);
player.setimage(willy);
player.gravity=1;
}
function setWindow()
{
winX = window.innerWidth|| document.documentElement.clientWidth|| document.body.clientWidth;
winY = window.innerHeight|| document.documentElement.clientHeight|| document.body.clientHeight;
winX=winX-4;
winY=winY-4;
scaleX=640/winX;
scaleY=480/winY;
if (gameActive==1) {
theBoard.canvas.width = 600/scaleX;
theBoard.canvas.height = 440/scaleY;
theBoard.canvas.style.left=""+20/scaleX+"px";
theBoard.canvas.style.top=""+20/scaleY+"px";
}
}
function setBackdrop(img)
{
var str="<img src='"+img+"' onclick='showCoords(event);' style='";
str=str+"width:"+winX+"px;height:"+winY+"px;'>";
document.getElementById('page').innerHTML=str;
document.getElementById('page').innerHTML=str;
currimage=img;
}
var theBoard = {
canvas : theCanvas=document.createElement("canvas"),
start : function() {
this.canvas.width = 600/scaleX;
this.canvas.height = 440/scaleY;
this.canvas.style.left=""+20/scaleX+"px";
this.canvas.style.top=""+20/scaleY+"px";
this.canvas.style.position="absolute";
this.canvas.tabIndex=1;
this.context = this.canvas.getContext("2d");
ctx=this.context;
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.canvas.style.backgroundColor=bkcolor;
setTimeout(updateGameArea, 40);
window.addEventListener('keydown', function (e) {
e.preventDefault();
keyboard=(keyboard||[]);
keyboard[e.keyCode]=(e.type=="keydown");
})
window.addEventListener('keyup', function (e) {
keyboard[e.keyCode]=(e.type=="keydown");
})
},
stop : function() {
},
restart:function() { this.interval = setTimeout(updateGameArea, 40);},
clear : function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function updateGameArea()
{
var i;
theBoard.clear();
if (keyboard && keyboard[37])
{
player.speed-=2; if (player.speed<-8) player.speed=-8;
}
else if (player.speed<0)
{
player.speed+=1;
}
if (keyboard && keyboard[39])
{
player.speed+=2; if (player.speed>8) player.speed=8;
}
else if (player.speed>0)
{
player.speed-=1;
}
if (player.gravity<1) player.gravity++;
if (keyboard && keyboard[38] && player.gravity>-1 && player.canjump==1){
player.gravity=-16;
player.dir=-6;
player.canjump=0;
}
if (player.gravity<4) {player.gravity=player.gravity+player.dir; player.dir+=4;if (player.dir>16) player.dir=16;}
if (player.gravity!=0)
{
player.y+=player.gravity;
if (checkWalls(player)==true)
{ player.y-=player.gravity;
if (player.gravity>0) player.canjump=1;
}
}
if (player.speed!=0)
{
player.x+=player.speed;
if (checkWalls(player)===true)
player.x-=player.speed;
}
for (i=0;i<objs.length;i++)
objs[i].draw();
setTimeout(updateGameArea, 10);
}
function checkWalls(obj)
{
var i;
for (i=0;i<objs.length;i++)
{
if (objs[i].type=="WALL")
if (obj.collision(objs[i])) {return true;}
}
return false;
}
class Obj {
constructor (x,y,w,h,t,img="") {
this.width=w;
this.height=h;
this.x=x;
this.y=y;
this.type=t;
this.imagemap=img;
this.speed=0;
this.gravity=0;
this.dir=0;
this.canjump=1;
this.pattern=0;
objs[objs.length]=this;
}
static Wall(x,y,w,h,img) {
var id=new Obj(x,y,w,h,"WALL",img);
return id;
}
draw()
{
if ((this.x/scaleX)<0 || (this.x/scaleX)>theBoard.canvas.width ||
(this.y/scaleY)<0 || (this.y/scaleY)>theBoard.canvas.height)
return;
switch (this.type){
case 'PATTERN':
case 'WALL':
{
if (this.pattern===0)
{ this.pattern=ctx.createPattern(this.imagemap,"repeat");}
ctx.rect(this.x/scaleX,this.y/scaleY,this.width/scaleX,this.height/scaleY);
ctx.fillStyle=this.pattern;
ctx.fill();
break;
}
case 'PLAYER':
ctx.drawImage(this.imagemap,0,0,this.width,this.height,this.x/scaleX,this.y/scaleY,this.width/scaleX,this.height/scaleY);
break;
}
}
setimage(img)
{
this.imagemap=img;
}
collision(wth) {
if (((this.x+this.width)>wth.x) && (this.x<(wth.x+wth.width))
&& ((this.y+this.height)>wth.y) && (this.y<(wth.y+wth.height)))
{return true;}
else return false;
}
}
window.onload=startGame();
As pointed out by #Kaiido, solution to your problem is here: HTML5 Canvas performance very poor using rect().
In short, just put your main loop code between beginPath and closePath without changing your theBoard.clear() method.
function updateGameArea()
{
var i;
theBoard.clear();
theBoard.context.beginPath();
...
theBoard.context.closePath();
requestAnimationFrame(updateGameArea);
}
Answer I originally wrote:
Resetting the dimensions to clear the canvas works better in your case, but it would induce performance issues.
clear : function() {
this.context.canvas.width = 600 / scaleX;
this.context.canvas.height = 440 / scaleY;
}
Also, use requestAnimationFrame as it eliminates any flicker that can happen when using setTimeout.
requestAnimationFrame(updateGameArea);
The following is a guess. I think you're running out of cycles and your frames are piling up on top of each other. At a glance, I don't see anything in your code that would cause a memory leak. Unless you look at the console memory graph and find out that you do, because you're adding listeners over and over or something like that. But simply clearing a canvas does not slow things down. It's basically the same as setting a bunch of values in an array.
However: Running heavy canvas operations within a setTimeout() can have a big toll on your CPU, if the CPU can't finish one operation before the next one enters the queue. Remember that timeouts are asynchronous. If your CPU throttles down and if the refresh rate you are specifying (40 milliseconds) is too short, then you will be left with a whole stack of redraws and clears that are waiting to go right after the last one, without giving the CPU any time to breathe.
Most Canvas animation packages have ways of dealing with this, by not just setting a timeout but waiting to make sure the last redraw is finished before triggering the next one in the call stack, and dropping a frame if necessary. At a bare minimum, you want to set a global variable like _redrawing=true before you do your redraw, and then set it to false when the redraw is finished, and ignore any call to setTimeout while it's still true. That will let you count how many frames you might be dropping. If you see this number going up over time, your CPU may be throttling as well. But do also check the memory graph and see if anything else is leaking.
Edit as correctly noted by #N3R4ZZuRR0 using requestAnimationFrame() will also avoid the timer problem. But you then need to measure the time between animation frames to figure out where things should actually be at that point in time. My suggestion of dropping frames here and there is primitive and most packages use requestAnimationFrame(), but it would help you identify whether your problem is with some other part of your code or with your frames building up in the timer.

How to be frugal with JS DOM calls for onscroll functions

What I'm trying to achieve:
If user has scrolled more than 24px from the top (origin), do something once.
If the user scrolls back within 24px of the top (origin), reverse that something once.
I have this code:
$("#box").scroll(function(){
var ofs = $(".title").offset().top;
if (ofs <= 24)
// Do something
else
// Reverse that something
})
As I understand it, this function runs every time the user scrolls, which can result in hundreds of calls to the DOM.
This isn't every resource efficient - Is there a more passive approach to this?
Both conditions are triggered repeatedly even for small scroll amounts - any way to execute the code just once and not repeat if the same condition is true?
What you are looking to do is either throttling the requests or something called "debounce". Throttling only allows a certain number of calls to whatever in a period of time, debounce only calls the function once a certain time after action has stopped.
This is a good link explaining it: https://css-tricks.com/the-difference-between-throttling-and-debouncing/
There are several libraries out there that will do this for you like Underscore and Lodash. You can roll your own as well and the premise is basically the following for debounce:
var timer;
$('#box').scroll(function(){
//cancel and overwrite timer if it exists already
// set timer to execute doWork after x ms
})
function doWork(){
//do stuff
}
You can also look into using requestAnimationFrame depending on browser support. requestAnimationFrame example and it looks like it's supported in most modern browsers and IE >= 10
In the code below, everytime the user scrolls above or below that 25px threshold, one of the conditions in the if ($boxAboveBelow) if-statement will be called.
var $box = $('#box');
var $boxAboveBelow = true; // true above, false below
$box.on('scroll', function() { // Throttle this function if needed
var newAboveBelow = $box.scrollTop() < 25;
if (newAboveBelow !== $boxAboveBelow) {
$boxAboveBelow = newAboveBelow;
if ($boxAboveBelow) {
// If the user scrolls back within 24px of the top (origin), reverse that something once.
} else {
// If user has scrolled more than 24px from the top (origin), do something once.
}
}
})
If you need those to only be called once ever, you can set Boolean variables to record if those conditions have ever been called.
var aboveCalled = false;
var belowCalled = false;
var $box = $('#box');
var $boxAboveBelow = true; // true above, false below
$box.on('scroll', function() { // Throttle this function if needed
var newAboveBelow = $box.scrollTop() < 25;
if (newAboveBelow !== $boxAboveBelow) {
$boxAboveBelow = newAboveBelow;
if ($boxAboveBelow) {
!aboveCalled && doScrollAboveStuff();
aboveCalled = true;
} else {
!belowCalled && doScrollBelowStuff();
belowCalled = true;
}
if (aboveCalled && belowCalled) {
$box.off('scroll'); // No need to keep listening, since both called
}
});

How can I change this into a loop instead of a recursive function?

So I have a piece of code like
var barlen = $('#SSWEprogressbar').width(),
$elems = $('[data-srcurl]'),
k = 0,
n = $elems.length;
LoadImage();
function LoadImage()
{
var $elem = $($elems[k]);
var img = new Image(),
url = $elem.attr('data-srcurl');
$(img).load(function(){
$('#SSWEloaderfront').attr('src',url);
$('#SSWEloadprogress').width((k+1)/n*barlen + "px");
var srctgt = $elem.attr('data-srctgt');
// change url to src attribute or background image of element
if ( srctgt == "srcattr" ){ $elem.attr('src',url); }
else if ( srctgt == "bgimg" ) { $elem.css('background-image',"url("+url+")"); }
// decide whether to exit the
if ( ++k == n ) { AllyticsSSWEPlayerShow(); }
else { LoadImage(); }
});
img.src = url;
}
and the reason I have it written that way is because load callback needs to be called before the stuff in the function can be executed again. If possible, I'd like to change this from a recursive function to a loop, but I don't know how to do that because there's no way to make a for or while loop "wait" before going on to the next iteration. Or is there?
As I mentioned in the comment you can easily resolve your problem, by using setTimeout(LoadImage, 100); in the else instead of calling the function directly. The 2nd parameter is the delay in ms.
If you understand why setTimeout(LoadImage, 0); is not stupid and not the same as calling the function directly then you understood setTimeout. It puts the function call in the queue, this means other events like clicks or keys that were pressed can be processed before the function is called again and the screen doesn't freeze. It's also impossible to reach max recursion like this, the depth is 1.

naturalWidth and naturalHeight returns 0 using onload event

I have read countless of answers of this issue and I came up with the following, but it doesn't work either.
function fitToParent(objsParent, tagName) {
var parent, imgs, imgsCant, a, loadImg;
//Select images
parent = document.getElementById(objsParent);
imgs = parent.getElementsByTagName(tagName);
imgsCant = imgs.length;
function scaleImgs(a) {
"use strict";
var w, h, ratioI, wP, hP, ratioP, imgsParent;
//Get image dimensions
w = imgs[a].naturalWidth;
h = imgs[a].naturalHeight;
ratioI = w / h;
//Get parent dimensions
imgsParent = imgs[a].parentNode;
wP = imgsParent.clientWidth;
hP = imgsParent.clientHeight;
ratioP = wP / hP;
//I left this as a test, all this returns 0 and false, and they shouldn't be
console.log(w);
console.log(h);
console.log(ratioI);
console.log(imgs[a].complete);
if (ratioP > ratioI) {
imgs[a].style.width = "100%";
} else {
imgs[a].style.height = "100%";
}
}
//Loop through images and resize them
var imgCache = [];
for (a = 0; a < imgsCant; a += 1) {
imgCache[a] = new Image();
imgCache[a].onload = function () {
scaleImgs(a);
//Another test, this returns empty, for some reason the function fires before aplying a src to imgCache
console.log(imgCache[a].src);
}(a);
imgCache[a].src = imgs[a].getAttribute('src');
}
}
fitToParent("noticias", "img");
To summarise, the problem is the event onload triggers before the images are loaded (or that is how I understand it).
Another things to add:
I don't know at first the dimensions of the parent nor the child,
because they varied depending of their position on the page.
I don't want to use jQuery.
I tried with another function, changing the onload event to
window, and it worked, but it takes a lot of time to resize because
it waits for everything to load, making the page appear slower,
that's how I came to the conclusion the problem has something to do
with the onload event.
EDIT:
I made a fiddle, easier to look at the problem this way
https://jsfiddle.net/whn5cycf/
for some reason the function fires before aplying a src to imgCache
Well, the reason is that you are calling the function immedeatly:
imgCache[a].onload = function () {
}(a);
// ^^^ calls the function
You call the function and assign undefined (the return value of that function) to .onload.
If you want to use an IIFE to capture the current value of a, you have to make it return a function and accept a parameter to which the current value of a is assigned to:
imgCache[a].onload = function (a) {
return function() {
scaleImgs(a);
};
}(a);
Have a look again at JavaScript closure inside loops – simple practical example .

Easel javascript structure explanation

I have found an API that'll make working with CANVAS a lot easier. It allows selection and modification of individual elements on the canvas very easily. It's EaselJS. The API doc is here. http://easeljs.com/docs/
I am Ok with the API so far. What confuses me is actually some javascript in there. The part that's in bold or within * *(couldn't get the formatting to work)
What kind of javascript structure is this?
(function(target){...content...})(bitmap)
and in the content, it references bitmap, which is something outside.
HERE IS THE CODE
for(var i = 0; i < 100; i++){
bitmap = new Bitmap(image);
container.addChild(bitmap);
bitmap.x = canvas.width * Math.random()|0;
bitmap.y = canvas.height * Math.random()|0;
bitmap.rotation = 360 * Math.random()|0;
bitmap.regX = bitmap.image.width/2|0;
bitmap.regY = bitmap.image.height/2|0;
bitmap.scaleX = bitmap.scaleY = bitmap.scale = Math.random()*0.4+0.6;
bitmap.mouseEnabled = true;
bitmap.name = "bmp_"+i;
(function(target) {
*bitmap.onPress = function(evt) *
{if (window.console && console.log) { console.log("press!"); }
target.scaleX = target.scaleY = target.scale*1.2;
container.addChild(target);
// update the stage while we drag this target
//Ticker provides a central heartbeat for stage to listen to
//At each beat, stage.tick is called and the display list re-rendered
Ticker.addListener(stage);
var offset = {x:target.x-evt.stageX, y:target.y-evt.stageY};
evt.onMouseMove = function(ev) {
target.x = ev.stageX+offset.x;
target.y = ev.stageY+offset.y;
if (window.console && console.log) { console.log("move!"); }
}
evt.onMouseUp = function() {
target.scaleX = target.scaleY = target.scale;
// update the stage one last time to render the scale change, then stop updating:
stage.update();
Ticker.removeListener(stage);
if (window.console && console.log) { console.log("up!"); }
}
** }})(bitmap); **
bitmap.onClick = function() { if (window.console && console.log) { console.log("click!"); } }
}
(function(target){...content...})(bitmap)
is creating a lexical scope for content so that any var or function declarations in content do not leak into the global scope. Inside content, target is just another name for
bitmap.
You can think of this as similar to
function init(target) { ...content... }
and then an immediate call to it passing bitmap as the actual value of the target parameter
but the first version interferes with the global scope even less -- it doesn't define init as a name in the global scope.
EDIT:
I think the purpose is not lexical scoping, but to make sure that the event handlers point to the right bitmap, instead of the last bitmap the for loop deals with.
init(bitmap);
See Event handlers inside a Javascript loop - need a closure? for more detail.

Categories

Resources