How to constantly move SVG paths without losing performance - javascript

I am trying to do a mobile game where I draw 6 SVG paths and I move then through the screen (from top to bottom) constantly. I am manipulating the paths with a simple javascript that updates some variables value and use them to set the attribute "d" of the paths. Like the example below:
setInterval(scrollPaths, 17); // ~ 60 fps
function scrollPaths() {
// leftYPoints is an array of points defined earlier and scrollSpeed is an integer value (e.g. 2)
for (var i = 0; i < leftYPoints.length; i++) leftYPoints[i] += scrollSpeed;
// then I change the paths attribute
var pathAttribute = "M"+ leftXPoints[0] + leftYPoints[0]
+ " L" + leftXPoints[1] + leftYPoints[1];
document.getElementById("leftpath").setAttribute("d", pathAttribute);
document.getElementById("righpath").setAttribute("d", pathAttribute);
... // continue to do that with the other paths, changing some variables only
}
The javascript itself runs very fast (scrollPaths takes about 5ms every time) and runs perfectly on the browser. But when I test the script in the mobile browser it seems that theres is a lot of lag on the paths. You can see that the paths are not scrolling smoothly. So I tried to decrease the value of scrollSpeed to a very small value but that did not solve it. So I thought the problem was related to the rendering method of the mobile browser or something like that. I tried to find some answers but nothing solved my problem. Then I found AmeliaBR`s answer here How do you move an SVG around a webpage without triggering slow redraws? where she says that it is better to use the transform attribute because the browser will understand it as it should just move some content that was already rendered instead of re-calculating the whole layout. So I tried to do that like this:
var newYPos = 1;
setInterval(scrollPaths, 17); // ~ 60 fps
function scrollPaths() {
// increased the position of the paths
newYPos += 1;
// then I did the transform of the paths with a group <g>
document.getElementById("pathsgroup").setAttribute("transform", "translate(0," + value + ")");
}
But unfortunately the result was not very effective. The javascript ran a little faster but the lag effect of the paths is still happening. So I am here, asking:
Does anyone knows what is happening?
Is there a better way to do that?
Or the problem is that mobile browsers are not ready for that yet (they still lack performance)?
Not sure if it helps but I tested it on a Nexus 5 with Chrome (supposed to have a very good performance).
Thanks.

Maybe try storing the "pathsgroup" element as a variable so you don't have to keep using getElement every iteration?
var newYPos = 1;
var pathsGroup = document.getElementById("pathsgroup");
setInterval(scrollPaths, 17); // ~ 60 fps
function scrollPaths() {
// increased the position of the paths
newYPos += 1;
// then I did the transform of the paths with a group <g>
pathsGroup.setAttribute("transform", "translate(0," + value + ")");
}

Related

Optimizing Konva.js for Many Images

I'm currently tiling many PNG images on several stacked FastLayers with Konva.js. The PNGs contain opacity, and they do not require dragging or hitboxes. The tiles are replaced often, and this seems to work well for medium-sized grids with dimensions of around 30x30. Once the tiles start growing to around 100x100, or even 60x60, the performance begins to slow when replacing individual tiles.
I've started to work on "chunking" tiles, i.e., adding tiles into smaller FastLayer groups. For example, a single 100x100 FastLayer would be divided into several 10x10 FastLayers. When a single tile changes, the idea is that only that chunk should should re-render, ideally speeding up the rendering time overall.
Is this is a good design to attempt, or should I try a different approach? I've looked over the performance tips in the Konva.js documentation, but I haven't seen anything directly relevant to this case.
So, after some research and tinkering, I've discovered the fastest way to render ~4000 images.
Don't use React components for Konva.js. I use React to structure my app, but I've skipped using an intermediate library for Konva.js rendering. Using React Components for the canvas will halve your performance.
Cache common images. I use a simple LRU cache to reuse HTMLImageElement objects.
Reuse Konva.js nodes (Konva.Image) whenever possible. My implementation is rendering a grid of images. The locations do not change, but the images may. Before, I would destroy() a node, and the add another. The destroy() causes an additional render, which creates jank for your users. Instead, I just use the image() method in combination with id() and name() to find and replace images at grid coordinates.
My app allows users to paint long strokes across the grid. This works OK in small strokes, when only using the literal mouse events. For long strokes, this does not work for two reasons. First, the OS and browser throttle the mouse events, giving you intermittent mouse events. Second, being in the middle of a render will give the same side effect. Instead, the software now detects long strokes, and "fills in" the missing coordinates that the user intended to draw between the intermittent mouse events.
Render at intervals. Since my grid can change often, I decided to sample the grid information 24 times a second, rather than allowing each tile change to queue up a batchDraw(). The underlying implementation is using RxJS to poll a Redux store once every 42ms, and only queues a batchDraw() if something has changed.
Caching definitely helps performance, but so does hiding. Konva doesn't (or didn't last I researched this) do any view culling. Below is code I used to hide the island shapes in my Konva strategy game.
stage.on('dragmove', function() {
cullView();
});
function cullView() {
var boundingX = ((-1 * (stage.x() * (1/zoomLevel)))-(window.innerWidth/2))-degreePixels;
var boundingY = ((-1 * (stage.y() * (1/zoomLevel)))-(window.innerHeight/2))-degreePixels;
var boundingWidth = (2 * window.innerWidth * (1/zoomLevel)) + (2*degreePixels);
var boundingHeight = (2 * window.innerHeight * (1/zoomLevel)) + (2*degreePixels);
var x = 0;
var y = 0;
for (var i = 0; i < oceanIslands.length; i++) {
x = oceanIslands[i].getX();
y = oceanIslands[i].getY();
if (((x > boundingX) && (x < (boundingX + boundingWidth))) && ((y > boundingY) && (y < (boundingY + boundingHeight)))) {
if (!oceanIslands[i].visible()) {
oceanIslands[i].show();
oceanIslands[i].clearCache();
if (zoomLevel <= cacheMaxZoom) {
oceanIslands[i].cache();
}
}
} else {
oceanIslands[i].hide();
}
}

Faster way of scaling text in browser? (help interpret test)

I need to scale lots of text nodes in the browser (support of all modern desktop and mobile browsers).
If I am right there are two options that offer good performance: scaling text objects in Canvas or scaling text nodes in the DOM using transform:matrix.
I have created a scenario to test both versions but the results are inconclusive. Uncomment testDOM() or testCanvas() function to start the test. (I am using JQuery and CreateJS framework because it was convenient. It is possible to use vanilla JS but I don't think that is the bottleneck here). (It matters what portion of the screen you actually see so please switch to full screen view in codepen).
http://codepen.io/dandare/pen/pEJyYG
var WIDTH = 500;
var HEIGHT = 500;
var COUNT = 200;
var STEP = 1.02;
var MIN = 0.1;
var MAX = 10;
var stage;
var canvas;
var bg;
var canvasTexts = [];
var domTexts = [];
var domMatrix = [];
var dom;
function testDOM() {
for (var i = 0; i < COUNT; i++) {
var text = $("<div>Hello World</div>");
var scale = MIN + Math.random() * 10;
var matrix = [scale, 0, 0, scale, Math.random() * WIDTH, Math.random() * HEIGHT];
text.css("transform", "matrix(" + matrix.join(',') + ")");
domTexts.push(text);
domMatrix.push(matrix);
}
dom = $('#dom');
dom.append(domTexts);
setTimeout(tickDOM, 1000);
}
function tickDOM() {
for (var i = 0; i < domTexts.length; i++) {
var text = domTexts[i];
var matrix = domMatrix[i];
var scale = matrix[0];
scale *= STEP;
if (scale > MAX)
scale = MIN;
matrix[0] = matrix[3] = scale;
text.css("transform", "matrix(" + matrix.join(',') + ")");
}
requestAnimationFrame(tickDOM);
}
function testCanvas() {
$('#dom').hide();
stage = new createjs.Stage('canvas');
createjs.Touch.enable(stage);
createjs.Ticker.timingMode = createjs.Ticker.RAF;
canvas = stage.canvas;
devicePixelRatio = window.devicePixelRatio || 1;
stage.scaleX = devicePixelRatio;
stage.scaleY = devicePixelRatio;
console.log('devicePixelRatio = ' + devicePixelRatio);
stage.mouseMoveOutside = true;
stage.preventSelection = false;
stage.tickEnabled = false;
stage.addChild(bg = new createjs.Shape());
bg.graphics.clear();
bg.graphics.f('#F2F2F2').drawRect(0, 0, 2 * WIDTH, HEIGHT);
canvas.width = 2 * WIDTH * devicePixelRatio;
canvas.height = HEIGHT * devicePixelRatio;
canvas.style.width = 2 * WIDTH + 'px';
canvas.style.height = HEIGHT + 'px';
stage.update();
for (var i = 0; i < COUNT; i++) {
var text = new createjs.Text("Hello World", "10px", "#333333");
text.scaleX = text.scaleY = MIN + Math.random() * 10;
text.x = Math.random() * WIDTH;
text.y = Math.random() * HEIGHT;
stage.addChild(text);
canvasTexts.push(text);
}
stage.update();
setTimeout(tickCanvas, 1000);
}
function tickCanvas() {
for (var i = 0; i < canvasTexts.length; i++) {
var text = canvasTexts[i];
text.scaleX = text.scaleY *= STEP;
if (text.scaleX > MAX)
text.scaleX = text.scaleY = MIN;
}
stage.update();
requestAnimationFrame(tickCanvas);
}
testDOM();
//testCanvas();
My questions:
Is it possible to improve the performance of my tests? Am I doing something wrong?
The first 5-10 seconds are significantly slower but I don't understand why. Does the browser somehow cashes the text objects after some time? If yes, is the test unusable for real world scenario testing where the objects don't zoom in a loop for longer period of time?
According to the Chrome Profiling tool the DOM version leaves 40% more idle time (is 40% more faster) than the Canvas version but the Canvas animation looks much smoother (after the initial 5-10 seconds of lagging), how should I interpret the Profiling tool results?
In the DOM version I am trying to hide the parent of the text nodes before I apply the transformations and then unhide it but it probably does not matter because transform:matrix on absolutely positioned element does not cause reflow, am I right?
The DOM text nodes have some advantages over the Canvas nodes like native mouse over detection with cursor: pointer or support for decorations (you can not have underlined text in Canvas). Anything else I should know?
When setting the transform:matrix I have to create a string that the compiler must to parse back to numbers, is there a more efficient way of using transform:matrix?
Q.1
Is it possible to improve the performance of my tests? Am I doing
something wrong?
Yes and no. (yes improve and no nothing inherently wrong (ignoring jQuery))
Performance is browser, and device dependent, for example Firefox handles objects better than arrays, while Chrome prefers arrays. There is a long list of differences just for the javascript.
Then the rendering is a dependent on the hardware, How much memory, what capabilities, and the particular drivers. Some hardware hates state changes, while others handle them at full speed. Limiting state changes can improve the speed on one machine while the extra code complexity will impact devices that don't need the optimisation.
The OS also plays a part.
Q.2
The first 5-10 seconds are significantly slower but I don't understand
why. Does the browser somehow cashes the text objects after some time?
If yes, is the test unusable for real world scenario testing where the
objects don't zoom in a loop for longer period of time?
Performance testing in Javascript is very complicated and as a whole application (like your test) is not at all practical.
Why slow?
Many reasons, moving memory to the display device, javascript optimising compilers that run while the codes runs and will recompile if it sees fit, this impacts the performance Un-optimised JS is SLOOOOOWWWWWWWW... and you are seeing it run unoptimised.
As well. In an environment like code pen you are also having to deal with all its code that runs in the same context as yours, it has memory, dom, cpu, GC demands in the same environment as yours and thus your code can not be said to be isolated and profiling results accurate.
Q.3
According to the Chrome Profiling tool the DOM version leaves 40% more
idle time (is 40% more faster) than the Canvas version but the Canvas
animation looks much smoother (after the initial 5-10 seconds of
lagging), how should I interpret the Profiling tool results?
That is the nature of requestAnimationFrame (rAF), it will wait till the next frame is ready before it calls your function. Thus if you run 1ms past 1/60th of a second you have missed the presentation of the current display refresh and rAF will wait till the next is due 1/60th minus 1ms before presentation and the next request is called. This will result in ~50% idle time.
There is not much that can be done than make you render function smaller and call it more often, but then you will get extra overhead with the calls.
rAF can be called many times during a frame and will present all renders during that frame at the same time. That way you will not get the overrun idle time if you keep an eye on the current time and ensure you do not overrun the 1/60th second window of opportunity.
Q.4
In the DOM version I am trying to hide the parent of the text nodes
before I apply the transformations and then unhide it but it probably
does not matter because transform:matrix on absolutely positioned
element does not cause reflow, am I right?
Reflow will not be triggered until you exit the function, hiding the parent at the start of a function and then unhiding it at the end will not make much difference. Javascript is blocking, that means nothing will happen while you are in a function.
Q.5
The DOM text nodes have some advantages over the Canvas nodes like
native mouse over detection with cursor: pointer or support for
decorations (you can not have underlined text in Canvas). Anything
else I should know?
That will depend on what the intended use is. DOM offers a full API for UI and presentation. Canvas offers rendering and pixel manipulation. The logic I use is if it takes more code to do it via DOM then canvas, then it is a canvas job and visa versa
Q.6
When setting the transform:matrix I have to create a string that the
compiler must to parse back to numbers, is there a more efficient way
of using transform:matrix?
No. That is the CSS way.

JavaScript function is only working the first time I call it?

I was wondering whether you guys could help me troubleshoot an issue I'm having. Hopefully identifying the problem won't require you look into the documentation of the graphics package I'm using, but if it does, here you go: http://raphaeljs.com/reference.html#Element.transform.
I have the following block of code
window.setInterval(function()
{
mycirc.transform("t1,1");
}, 500);
which of course should call the function mycirc.transform("t1,1") every half-second. That function is supposed to translate the x and y coordinates of mycirc each by 1 unit (look at Element.transform([tstr]) on http://raphaeljs.com/reference.html#Element.transform).
However, when I test my page, mycirc gets translated once and then the subsequent calls have no effect. I used console.log(...) to test and make sure:
window.setInterval(function()
{
var bb = mycirc.getBBox();
console.log("coords before transformation: " + bb.x + "," + bb.y);
mycirc.transform("t1,1");
var bb = mycirc.getBBox();
console.log("coords after transformation: " + bb.x + "," + bb.y);
}, 500);
yields
coords before transformation: 120.98508107696858,106 jsfunctions.js:411
coords after transformation: 121.98508107696858,107 jsfunctions.js:414
coords before transformation: 121.98508107696858,107 jsfunctions.js:411
coords after transformation: 121.98508107696858,107 jsfunctions.js:411
etcetera.
Any idea why this might be?
(I tried to look through the source code for the graphics package, but it's unreadable because of no whitespace.)
your code
mycirc.transform("t1,1");
isn't relative to current state. It just transforms from original state to t1,1 and then from t1,1 to t1,1 etc.
You should calculate transformation every time.
EDIT: So it would need a global variable, incremented every time like:
var xyPos = 1;
window.setInterval(function()
{
mycirc.transform("t"+xyPos+","+xyPos);
xyPos++;
}, 500);
In the usage section of the docs you linked, it shows how to prepend and append transformations. This implies to me that you code will just reset the transformation to the same thing every time. I have never used this graphics library, so I can't say for sure, but try something like the following and see if it works:
mycirc.transform("t1,1");
mycirc.transform("...t1,1");
mycirc.transform("...t1,1");
mycirc.transform("...t1,1");
I believe that will apply the same transform 4 times. Of course, you will then need to convert this logic into an interval for your use.
I think this is expected behavior. Quoting the docs:
Adds transformation to the element which is separate to other
attributes, i.e. translation doesn’t change x or y of the rectange.
Try:
var amount = 1;
window.setInterval(function()
{
mycirc.transform("t" + [amount, amount].join(','));
amount++;
}, 500);

Sprite Animation using a for loop

I am trying to create an animation using a sprite sheet and a for loop to manipulate the background position until it has reached the total number or rows in the sheet. Ideally a reset back to the initial position would be practical, but I cannot even get the animation itself to trigger...
With the current function, no errors occur and the background position in my CSS does not change. I even recorded using Chrome DevTools Timeline and there was nothing either then everything related to my page loading. I have also tried using "background-position-y" as well as a simpler value rather then the math I currently have in place.
This is my function:
$(document).load(function() {
var $height= 324;
var $rows= 34;
for(var i=0; i<$rows; i++){
setTimeout(function() {
$('#selector').css("background-position", "0px ", "0" - ($height*i) + "px");
}, 10);
}
});
I hate to ask a question that is similar to previous issues, but I cannot seem to find another individual attempting sprite sheet animation with a for loop, so I suppose it is it's own problem.
p.s. I didn't include a snippet of my HTML and CSS because it is pretty standard and I don't see how that could be the problem. That being said, I am all ears to any potential thoughts!
I am completely revamping my answer
This issue is that the for() loop is not affected by the setTimeout so the function needs to be written on our own terms, not with a loop
Working Fiddle
Here it is..
var $height= 5;
var $rows= 25;
var i = 1; // Starting Point
(function animateMe(i){
if(i<=$rows){ // Test if var i is less than or equal to number of rows
var newHeight = 0-($height*i)+"px"; // Creat New Height Position
console.log(i); //Testing Purposes - You can Delete
$('#selector').css({"background-position": "0px "+ newHeight}); // Set New Position
i++; // Increment by 1 (For Loop Replacement)
setTimeout(function(){animateMe(i)}, 1000); // Wait 1 Second then Trigger Function
};
})(0);
Here is your solution
First Change
$(document).load() To $(document).ready()
And Change .css Syntex as
$('#selector').css("background-position",'0px '+(0 - ($height*i))+'px');
Here is fiddle Check it ihad implemented it on my recent project http://jsfiddle.net/krunalp1993/7HSFH/
Hope it helps you :)

How to increase a CSS value by the value of a JS variable?

I have several fixed position divs with the same class at varying distances from the left edge of the window, and I'd like to increase/decrease that distance by an equal amount on each div when a certain action happens (in this case, the window being resized). I've tried positioning them with CSS and percentages rather than pixels, but it doesn't quite do the job.
Is there a way to store the position of each of those divs in an array and then add/subtract a given amount of pixels?
Here's what I've tried so far - I'm still getting my head around JS so this could be really bad for all I know, but here goes:
roomObjects = $('.object-pos');
var objectCount = 0;
for ( var objectCount = 0; objectCount < 10; objectCount++;) {
roomObjects = rooomObjects[objectCount];
console.log(roomObjects.css("background-position").split(" "));
}
Do you mind sharing why percentages wouldn't work? Usually that's what I would recommend if you're wanting the page to scale correctly on window resizes. I guess if you really wanted to you could do something like:
$(window).resize(function() {
$('#whateverdiv').style.whateverproperty = $('#whateverdiv').style.whateverproperty.toString() + (newPosition - oldPosition);
oldPosition = newPosition;
}
this is obviously not the complete code, but you should be able to fill in the blanks. You'll have to set the oldPosition variable on page load with the original position so that the function works the first time.
edit: you'll also have to strip off the units from the x.style.property string, so that you'll be able to add the value to it
A problem you might well be facing is that when retrieving the current left or top properties, they are returned as a string, with px of % on the end. Try running a parseInt() on the returned values to get a number, then you might well be able to add to the values. Just be sure, when reassigning, that you concatenate "px" or "%" on the end as appropriate.
You could use a bit of jQuery :
var el = $("#id");
var top = el.css("top");
el.css("top", top * 1.2); // increase top by 20%
saves mucking around in the DOM
This might be useful if you want to position things relatively: http://docs.jquery.com/UI/Position

Categories