Performance of CSS Flex vs. manual JavaScript computation - javascript

I have a browser-based system which consists of, among other modular components, an <iframe> container which is nested with other <iframe> for - currently - up to three levels. A given webpage may be embedded within multiple nested frames simultaneously. The end-users' screen resolutions and the nested frames' sizes can vary.
It is therefore important for element sizes, paddings, margins etc. to be defined in relative terms. To this end, I have identified two approaches: Either I use CSS Flex wherever possible and compute with JavaScript manually for the rest, or do the reverse and compute wherever possible. Here's an example of the computation-focused approach for one of my more complex pages to be embedded in the frames:
// Tile size-dependent CSS
const RATIO = 0.618;
// Amount of space to use in view
var viewHeight = window.innerHeight;
var viewWidth = window.innerWidth;
var viewVertSpace = viewHeight * 0.8;
var viewHoriSpace = viewWidth * 0.8;
// Position and sizing for each overall column
var colWidth = Math.round(viewHoriSpace * 0.5);
var colSpace = Math.round(viewVertSpace) - 2; // Deduct 2px bottom border
// Sizing of column 1 elements
var summaryHeight = colSpace * 0.5;
var mainRowHeight = summaryHeight * RATIO;
var mainRowSize = Math.round(mainRowHeight - 10); // Deduct 5px vertical padding per side
var subTextSize = Math.round((summaryHeight - mainRowHeight) * (1 - RATIO));
var diffIconSize = Math.round((mainRowSize - subTextSize) * RATIO);
// Sizing of column 2 elements
var horiSpace = colWidth * RATIO; // Leave some space on both sides
var chartWidth = horiSpace - (horiSpace * RATIO);
var innerBarWidth = chartWidth * (1 - RATIO);
var targetArrowWidth = subTextSize * 0.5;
There is a performance constraint on the system's loading time, one which has been failed during the first deployment to the test server. I have been continuously optimising the code (part of which involved implementing lazy initialisation and ordered loading to prevent too many simultaneous HTTP calls) and this is one area I'm looking at. I have read that extensive use of CSS Flex in more complex applications can have a significant performance impact but I wonder if relying on manual computation via JavaScript to set absolute pixel sizes is actually better?

While specific implementations may vary, here are some general things to consider:
You will not be able to control when the CSS causes your elements to resize, with JavaScript, you can make some decisions such as setting timeouts or establishing minimum values to trigger a change. However, any such solutions will be blocking any other JavaScript you may wish to be running in the same time frame. Similarly, any other JavaScript you have running will block this code. Using CSS Flexbox will require you to check on which browser-specific implementation details apply to your use cases (the same is of course true in your JavaScript).
In my experience, CSS flexbox has been faster than any JavaScript solutions that attempt to address the same concerns, I cannot guarantee that this is a universal truth though.
You should also consider code maintenance when implementing a solution. If your JavaScript is full of magic numbers and strange conditionals, it might be easier to maintain a CSS solution (assuming you do not fill it with magic numbers and strange conditionals as well, which I find easier to avoid with a Flexbox).
I'm sorry I can't give you a "use this every time answer", but hopefully this will help you make good decisions given the constrains that exist

Related

Why there's white line appears in HTML canvas between two shape?

Why there's white line appears in JS canvas between two shape?
I'm making a game with JS / TS (I'm using MacBook Pro), with HTML5 canvas, and there's a unexpected white line appear between two shapes in safari browser:
but I run exactly same code in chrome, everything is fine:
So why this is happened? And how can I fix it?
code I'm using to render
CONTEXT.drawImage(
CACHES.get(this.materialURL),
(this.rect.x - camera.location.x) * GRID_W,
(this.rect.y - camera.location.y) * GRID_H,
GRID_W,
GRID_H,
);
Render artifacts
More info?
There are many reasons this can happen. Most are the result of rounding errors. Sometimes the error is in JavaScript, other times it occurs in the rendering.
There are subtle differences in the JS engines (resulting from hardware, OS, driver and or engine implementations) that can result in rendering artifact that differ across devices.
There are major differences in rendering implementations even on the same browser, same OS, and using the same hardware, depending on setup (flags).
Where your artifacts are coming from I can only guess at without a lot more information. Even how you captured the example images can change the solution.
Things to try
Try using nearest pixel lookup by setting 2D context smoothing off
ctx.imageSmoothingEnabled = false;
To turn back on use
ctx.imageSmoothingEnabled = true;
Use software rendering (CPU) by setting the willReadFrequently flag when getting the context.
const ctx = canvas.getContext("2d", {willReadFrequently: true});
Note this can slow things down a lot
Turn off canvas alpha (to stop BG appearing at seams) using context option alpha
const ctx = canvas.getContext("2d", {alpha: false});
Ensure that the source image resolution matches the render size.
In other words does
const img = CACHES.get(this.materialURL);
const isSameRes = img.width === GRID_W && img.height === GRID_H;
isSameRes should equal true?
Note use naturalWidth and naturalHeight if img is an instance of Image
Extend the source image by 1 px on each edge copying the edge pixels as shown in next image. This will prevent transparent edge pixels bleeding into rendering result.
Then render the inner original image as shown below
const img = CACHES.get(this.materialURL);
ctx.drawImage(
img,
1, 1, img.width - 2, img.height - 2,
(this.rect.x - camera.location.x) * GRID_W,
(this.rect.y - camera.location.y) * GRID_H,
GRID_W,
GRID_H,
);
Note this will add in tiny bit of overhead.
Ensure integer coordinates by flooring coordinates and forcing constants to be integers.
// When defining GRID_W and GRID_H (assuming positive integer values)
// Force internal type to int32 by using bitwise operation on values
// Note this may not do anything
const GRID_W = 32 | 0;
const GRID_H = 32 | 0;
// Render using floored coordinates.
ctx.drawImage(
CACHES.get(this.materialURL),
Math.floor((this.rect.x - camera.location.x) * GRID_W),
Math.floor((this.rect.y - camera.location.y) * GRID_H),
GRID_W,
GRID_H,
);
More
There are many more options but without the needed information I would be wasting your time.

get device PPI in javascript

How to get real PPI (pixels per inch) device resolution with javascript?
some examples of device and value expected:
iMac 27-inch: 109ppi
iPad: 132ppi
19inch 1440x900 screen: 89ppi
...
Running a native application directly on top of the operating system is the only surefire way to acquire the physical characteristics of the client monitor stored in the EDID. No mainstream browser engine currently offers this information through any web API and will likely not in the foreseeable future.
However there are several ways to approximate the density to varying levels of accuracy.
All modern browsers give hints to the pixel density via attributes like devicePixelRatio, deviceXDPI which basically tell you how much zoom the client has going on (versus 1.0x Operating System default zoom). If you're targeting only a few devices like the Apple line then you might be able to tell which one is which, although Apple doesn't leave a scrap of a clue to discern a iPad mini from a regular iPad.
Another alternative is using device databases or proprietary software that analyze the client's "user agent string" to achieve a hit-or-miss guess of the device and then looking up the DPI associated with that device if it exists in their big database. Expensive proprietary systems might have higher accuracy by leveraging complex data mining algorithms but regardless any system like this would need constant manual updating and will still remain open to client manipulation since they can just change their user agent string ("view this website in desktop mode")
It's really an unfortunate situation not having this information available. I've spent countless hours researching ANY POSSIBLE WAY to make a PPI aware Web Application.
Maybe one day someone will be able to convince the folks at WebKit or Mozilla or Microsoft to allow people to create PPI aware Web apps for augmented reality and such... Sigh
In pure JS:
function calcScreenDPI() {
const el = document.createElement('div');
el.style = 'width: 1in;'
document.body.appendChild(el);
const dpi = el.offsetWidth;
document.body.removeChild(el);
return dpi;
}
console.log(calcScreenDPI());
You can create an element of 1in of fixed height, then look at its height in pixels
createDpiTestElements()
var dpi = getDpi()
Because
[dpi] = [px/in]
So let
p be an object's length in pixels [px]
i be that object's length in inches [in]
and d be the searched DPI/PPI for that object, in pixels per inches [dpi]
We get
d = p/i
So if we choose
i = 1
we get
d = p
JS Code
/**
* #function
* #inner
*/
function createDpiTestElements () {
var getDpiHtmlStyle = 'data-dpi-test { height: 1in; left: -100%; position: absolute; top: -100%; width: 1in; }'
var head = document.getElementsByTagName('head')[0]
var getDPIElement = document.createElement('style')
getDPIElement.setAttribute('type', 'text/css')
getDPIElement.setAttribute('rel', 'stylesheet')
getDPIElement.innerHTML = getDpiHtmlStyle
head.appendChild(getDPIElement)
var body = document.getElementsByTagName('body')[0]
var dpiTestElement = document.createElement('data-dpi-test')
dpiTestElement.setAttribute('id', 'dpi-test')
body.appendChild(dpiTestElement)
}
/**
* Evaluate the DPI of the device's screen (pixels per inche).
* It creates and inpect a dedicated and hidden `data-dpi-test` DOM element to
* deduct the screen DPI.
* #method
* #static
* #returns {number} - The current screen DPI, so in pixels per inch.
*/
function getDpi () {
return document.getElementById('dpi-test').offsetHeight
}
In a Chrome Extension
Related, but not an exact answer to the poster's question:
You can get the DPI of your display (and a lot of other information), if you're developing a Chrome Extension with the proper permissions using chrome.system.display.getInfo:
const info = await chrome.system.display.getInfo()
console.log(info.dpiX, info.dpiY)
It returns a DisplayUnitInfo object that tells you everything you might need to know.
Addendum to answers above for plain JS DOM
Also, it's worth noting that you can get an approximate pixel density combining pieces of information provided above. However, it's unlikely this will be useful for anything but logging purposes, IMO. I say this because any pixel values you use in your DOM/JS code aren't going to be "device pixel values". They'll likely have a multiplier applied.
The trick is to alter #nikksan's answer above and multiply by window.devicePixelRatio
Note that it is going to be the same answer every single time on each device, so it's probably only worth doing once.
function calcScreenDPI() {
// Create a "1 inch" element to measure
const el = document.createElement('div');
el.style.width = '1in';
// It has to be appended to measure it
document.body.appendChild(el);
// Get it's (DOM-relative) pixel width, multiplied by
// the device pixel ratio
const dpi = el.offsetWidth * devicePixelRatio;
// remove the measurement element
el.remove();
return dpi;
}
Here is what works for me (but didn't test it on mobile phones):
Then I put in the .js: screenPPI = document.getElementById('ppitest').offsetWidth;
This got me 96, which corresponds to my system's ppi.

Javascript stage scale calculator

I've searched far and wide throughout the web thinking that somebody may have had a similar need, but have come short. I'm needing to create a calculator that will adjust the size of a stage for draggable objects based on a Width and Height field (in feet).
I'm needing to maintain a max width and height that would, ideally, be set in a variable for easy modification. This max width and height would be set in pixels. I would set dimensions of the draggable items on the stage in "data-" attributes, I imagine. I'm not looking to match things up in terms of screen resolutions.
What's the best way to approach this? I'm pretty mediocre at math and have come up short in being able to create the functions necessary for scaling a stage of objects and their container like this.
I'm a skilled jQuery user, so if it makes sense to make use of jQuery in this, that'd be great. Thanks in advance.
There are at least a couple of ways to scale things proportionately. Since you will know the projected (room) dimensions and you should know at least one of the scaled dimensions (assuming you know the width of the stage), you can scale proportionately by objectLengthInFeet / roomWidthInFeet * stageWidthInPixels.
Assuming a stage width of 500 pixels for an example, once you know the room dimensions and the width of the stage:
var stageWidth = 500,
roomWidth = parseFloat($('#width').val(), 10) || 0, // default to 0 if input is empty or not parseable to number
roomHeight = parseFloat($('#height').val(), 10) || 0, // default to 0 if input is empty or not parseable to number
setRoomDimensions = function (e) {
roomWidth = parseFloat($('#width').val(), 10);
roomHeight = parseFloat($('#height').val(), 10);
},
feetToPixels = function feetToPixels(feet) {
var scaled = feet / roomWidth * stageWidth;
return scaled;
};
Here's a demo: http://jsfiddle.net/uQDnY/

Most efficient scrolling text

I need to create very CPU efficient scrolling text as smoothly as possible. The reason the performance is so key is that I'm also recording from the user's microphone at the same time. I've tried two things so far:
scroller = setInterval(scroll_words, 100);
function scroll_words ()
{
var words= document.getElementById("words");
var speed = document.getElementById("word_speed").value;
var total_height = word.children.length * 18;
words.scrollTop += 0.1 * 18 * speed;
}
This one is noticeably choppy, and it causes significant errors in recording (skipping, or blank spots). Here's my second attempt:
var words = document.getElementById("words");
var speed = document.getElementById("word_speed").value;
words.style.webkitTransition = ((18 * words.children.length)/speed)+"s all linear";
words.style.webkitTransform = "translate(0px, -"+(18 * words.children.length)+"px)";
This is less harsh on performance (and a lot smoother, since it can do subpixel movement), but it still causes noticeable errors in recording on some computers, especially ones with onboard video.
Is there a way to do this without putting much load on the CPU?
There are two quick solutions you can try:
Use 3D transformations to force modern browsers to use GPU acceleration
Split your text in chunks and only animate the visible chunks, instead of the whole thing. Your chunks should be something like this: [p1p2] [p2p3] [p3p4] etc, 2 pages each.

Detecting the system DPI/PPI from JS/CSS?

I'm working on a kind of unique app which needs to generate images at specific resolutions according to the device they are displayed on. So the output is different on a regular Windows browser (96ppi), iPhone (163ppi), Android G1 (180ppi), and other devices. I'm wondering if there's a way to detect this automatically.
My initial research seems to say no. The only suggestion I've seen is to make an element whose width is specified as "1in" in CSS, then check its offsetWidth (see also How to access screen display’s DPI settings via javascript?). Makes sense, but iPhone is lying to me with that technique, saying it's 96ppi.
Another approach might be to get the dimensions of the display in inches and then divide by the width in pixels, but I'm not sure how to do that either.
<div id='testdiv' style='height: 1in; left: -100%; position: absolute; top: -100%; width: 1in;'></div>
<script type='text/javascript'>
var devicePixelRatio = window.devicePixelRatio || 1;
dpi_x = document.getElementById('testdiv').offsetWidth * devicePixelRatio;
dpi_y = document.getElementById('testdiv').offsetHeight * devicePixelRatio;
console.log(dpi_x, dpi_y);
</script>
grabbed from here http://www.infobyip.com/detectmonitordpi.php. Works on mobile devices! (android 4.2.2 tested)
I came up with a way that doesn't require the DOM... at all
The DOM can be messy, requiring you to append stuff to the body without knowing what stuff is going on with width: x !important in your stylesheet. You would also have to wait for the DOM to be ready to use...
/**
* Binary search for a max value without knowing the exact value, only that it can be under or over
* It dose not test every number but instead looks for 1,2,4,8,16,32,64,128,96,95 to figure out that
* you thought about #96 from 0-infinity
*
* #example findFirstPositive(x => matchMedia(`(max-resolution: ${x}dpi)`).matches)
* #author Jimmy Wärting
* #see {#link https://stackoverflow.com/a/35941703/1008999}
* #param {function} fn The function to run the test on (should return truthy or falsy values)
* #param {number} start=1 Where to start looking from
* #param {function} _ (private)
* #returns {number} Intenger
*/
function findFirstPositive (f,b=1,d=(e,g,c)=>g<e?-1:0<f(c=e+g>>>1)?c==e||0>=f(c-1)?c:d(e,c-1):d(c+1,g)) {
for (;0>=f(b);b<<=1);return d(b>>>1,b)|0
}
var dpi = findFirstPositive(x => matchMedia(`(max-resolution: ${x}dpi)`).matches)
console.log(dpi)
There is the resolution CSS media query — it allows you to limit CSS styles to specific resolutions:
http://www.w3.org/TR/css3-mediaqueries/#resolution
However, it’s only supported by Firefox 3.5 and above, Opera 9 and above, and IE 9. Other browsers won’t apply your resolution-specific styles at all (although I haven’t checked non-desktop browsers).
Here is what works for me (but didn't test it on mobile phones):
<body><div id="ppitest" style="width:1in;visible:hidden;padding:0px"></div></body>
Then I put in the .js: screenPPI = document.getElementById('ppitest').offsetWidth;
This got me 96, which corresponds to my system's ppi.
DPI is by definition tied to the physical size of the display. So you won't be able to have the real DPI without knowing exactly the hardware behind.
Modern OSes agreed on a common value in order to have compatible displays: 96 dpi. That's a shame but that's a fact.
You will have to rely on sniffing in order to be able to guess the real screen size needed to compute the resolution (DPI = PixelSize / ScreenSize).
I also needed to display the same image at the same size at different screen dpi but only for Windows IE. I used:
<img src="image.jpg" style="
height:expression(scale(438, 192));
width:expression(scale(270, 192))" />
function scale(x, dpi) {
// dpi is for orignal dimensions of the image
return x * screen.deviceXDPI/dpi;
}
In this case the original image width/height are 270 and 438 and the image was developed on 192dpi screen. screen.deviceXDPI is not defined in Chrome and the scale function would need to be updated to support browsers other than IE
The reply from #Endless is pretty good, but not readable at all,
this is a similar approche with fixed min/max (it should be good ones)
var dpi = (function () {
for (var i = 56; i < 2000; i++) {
if (matchMedia("(max-resolution: " + i + "dpi)").matches === true) {
return i;
}
}
return i;
})();
matchMedia is now well supported and should give good result, see http://caniuse.com/#feat=matchmedia
Be careful the browser won't give you the exact screen dpi but only an approximation
function getPPI(){
// create an empty element
var div = document.createElement("div");
// give it an absolute size of one inch
div.style.width="1in";
// append it to the body
var body = document.getElementsByTagName("body")[0];
body.appendChild(div);
// read the computed width
var ppi = document.defaultView.getComputedStyle(div, null).getPropertyValue('width');
// remove it again
body.removeChild(div);
// and return the value
return parseFloat(ppi);
}
(From VodaFone)
Reading through all these responses was quite frustrating, when the only correct answer is: No, it is not possible to detect the DPI from JavaScript/CSS. Often, the operating system itself does not even know the DPI of the connected screens (and reports it as 96 dpi, which I suspect might be the reason why many people seem to believe that their method of detecting DPI in JavaScript is accurate). Also, when multiple screens are connected to a device forming a unified display, the viewport and even a single DOM element can span multiple screens with different DPIs, which would make these calculations quite challenging.
Most of the methods described in the other answers will almost always result in an output of 96 dpi, even though most screens nowadays have a higher DPI. For example, the screen of my ThinkPad T14 has 157 dpi, according to this calculator, but all the methods described here and my operating system tell me that it has 96 dpi.
Your idea of assigning a CSS width of 1in to a DOM element does not work. It seems that a CSS inch is defined as 96 CSS pixels. By my understanding, a CSS pixel is defined as a pixel multiplied by the devicePixelRatio, which traditionally is 1, but can be higher or lower depending on the zoom level configured in the graphical interface of the operating system and in the browser.
It seems that the approach of using resolution media queries produces at least some results on a few devices, but they are often still off by a factor of more than 2. Still, on most devices this approach also results in a value of 96 dpi.
I think your best approach is to combine the suggestion of the "sniffer" image with a matrix of known DPIs for devices (via user agent and other methods). It won't be exact and will be a pain to maintain, but without knowing more about the app you're trying to make that's the best suggestion I can offer.
Can't you do anything else? For instance, if you are generating an image to be recognized by a camera (i.e. you run your program, swipe your cellphone across a camera, magic happens), can't you use something size-independent?
If this is an application to be deployed in controlled environments, can you provide a calibration utility? (you could make something simple like print business cards with a small ruler in it, use it during the calibration process).
I just found this link: http://dpi.lv/. Basically it is a webtool to discover the client device resolution, dpi, and screen size.
I visited on my computer and mobile phone and it provides the correct resolution and DPI for me. There is a github repo for it, so you can see how it works.
Generate a list of known DPI:
https://stackoverflow.com/a/6793227
Detect the exact device. Using something like:
navigator.userAgent.toLowerCase();
For example, when detecting mobile:
window.isMobile=/iphone|ipod|ipad|android|blackberry|opera mini|opera mobi|skyfire|maemo|windows phone|palm|iemobile|symbian|symbianos|fennec/i.test(navigator.userAgent.toLowerCase());
And profit!
Readable code from #Endless reply:
const dpi = (function () {
let i = 1;
while ( !hasMatch(i) ) i *= 2;
function getValue(start, end) {
if (start > end) return -1;
let average = (start + end) / 2;
if ( hasMatch(average) ) {
if ( start == average || !hasMatch(average - 1) ) {
return average;
} else {
return getValue(start, average - 1);
}
} else {
return getValue(average + 1, end);
}
}
function hasMatch(x) {
return matchMedia(`(max-resolution: ${x}dpi)`).matches;
}
return getValue(i / 2, i) | 0;
})();
Maybe I'm a little bit steering off this topic...
I was working on a html canvas project, which was intended to provide a drawing canvas for people to draw lines on. I wanted to set canvas's size to 198x280mm which is fit for A4 printing.
So I started to search for a resolution to convert 'mm' to 'px' and to display the canvas suitably on both PC and mobile.
I tried solution from #Endless ,code as:
const canvas = document.getElementById("canvas");
function findFirstPositive(b, a, i, c) {
c=(d,e)=>e>=d?(a=d+(e-d)/2,0<b(a)&&(a==d||0>=b(a-1))?a:0>=b(a)?c(a+1,e):c(d,a-1)):-1
for (i = 1; 0 >= b(i);) i *= 2
return c(i / 2, i)|0
}
const dpi = findFirstPositive(x => matchMedia(`(max-resolution: ${x}dpi)`).matches)
let w = 198 * dpi / 25.4;
let h = 280 * dpi / 25.4;
canvas.width = w;
canvas.height = h;
It worked well on PC browser, showing dpi=96 and size was 748x1058 px;work well on PC
However turned to mobile devices, it was much larger than I expected: size: 1902x2689 px.can't work on mobile
After searching for keywords like devicePixelRatio, I suddenly realize that, I don't actually need to show real A4 size on mobile screen (under which situation it's actually hard to use), I just need the canvas's size fit for printing, so I simply set the size to:
let [w,h] = [748,1058];
canvas.width = w;
canvas.height = h;
...and it is well printed:well printed

Categories

Resources