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
Working on a little "zombies" or "tag you're it" or "ew! you got cooties"-styled game where each AI object (a person, basically) runs around randomly. There is an initial object that is "it" or "infected" and as it moves about the screen and touches/overlaps/collides with another object it should change the touched object to the same color as the object that touched it. Newly infected objects can continue to infect other objects they randomly collide with, until - in principle - the whole population is the same color as the first infected object. (I'll worry about fancier AI where infected actively hunt nearby objects or healthy objects can avoid infected objects, later).
But after looking at various similar questions in StackOverflow that generally deal with 2 DIVs colliding, or use some sort of jQuery draggable detection trick, I'm still at a bit of a loss as to how to build upon those ideas to scale up a simple "if I am touching/overlapping/colliding with another object it should get infected too" that can be applied to a large number of elements on the page, say... less than 100 so as not to drag the browser down.
I basically get as far as determining position and widths/heights of the objects so that I know how much space they take, but then the brain goes 'bzzzzt' when trying to develop a function that checks over all the population for collisions.
Got the population moving around randomly without trouble - see JSFiddle https://jsfiddle.net/digitalmouse/5tvyjhjL/1/ for the related code. Affected function should be in the 'animateDiv()', seen below to make the stackoverflow question asking editor happy that I included some code in my question. :)
function animateDiv($target) {
var newq = makeNewPosition($target.parent());
var oldq = $target.offset();
var speed = calcSpeed([oldq.top, oldq.left], newq);
// I believe collision should be dealt with here,
// just before moving an object
$target.animate({
top: newq[0],
left: newq[1]
}, speed, function () {
animateDiv($target);
});
}
Any hints, tricks, adaptations, or code snippets that push me in the right direction are appreciated.
a quick, down and dirty solution (there are more complex algorithms) would be to use:
document.elementFromPoint(x, y);
It gets the element at the position specified. The full spec can be found here.
Assuming your 'zombies' are rectangular, you could call this for each corner, and if you get a hit, that isn't the background or the element you're checking, you've got a collision...
EDIT:
An alternate method, even 'downer and dirtier' than above, but stupidly quick, would be to get the centre points of the two objects to check, then find their absolute displacements in X and Y. If the differences are less than the sum of half their widths and heights then they are overlapping. It's by no means pix perfect, but it should be able to handle a large number objects really quickly.
EDIT 2:
First off, we need to get the centres of each object (to check)
// Values for main object
// pop these in vars as we'll need them again in a sec...
hw = object.style.width >> 1; // half width of object
hh = object.style.height >> 1; // (bit shift is faster than / 2)
cx = object.style.left + hw; // centre point in x
cy = object.style.top + hh; // and in y
// repeat for secondary object
If you don't know / store the width and height you can use:
object.getBoundingClientRect();
which returns a 'rect' object with the fields left, top, right and bottom.
Now we check proximity...
xDif = Math.abs(cx - cx1); // where cx1 is centre of object to check against
if(xDif > hw + hw1) return false; // there is no possibility of a collision!
// if we get here, there's a possible collision, so...
yDif = Math.abs(cy - cy1);
if(yDif > hh + hh1) return false; // no collision - bug out.
else {
// handle collision here...
}
Danny
Probably a knockout newbie question. I just started learning knockout this week.
I have two observables - width and height. These values are initially set by grabbing the width and height of a clicked element, so no calculation is needed for this part. The issue is that, once the initial values are captured, I want to maintain the aspect ratio for all future changes. So if the user updates the width, I want the height to update in accordance with the aspect ratio (original width / original height). So when a user changes the width, the height must change, and vice versa.
I have tried this using subscribers, but this causes an infinite loop as you might expect, as any change to the width requires a change in height and vice versa. I have looked into computed observables and extenders, but I am not sure either one is the right answer, though this may be due to my limited experience with knockout.
If necessary, I can provide some code, but thought I'd try to keep this conceptual if possible. Just something to point me down the right path.
Thanks for any help!
Like with any circular dependency, you have to break the cycle. Rather than having two mutually dependent observables, make them dependent on one or more common observables. Observables that can maintain that ratio.
e.g.,
function Proportional (width, height) {
var _ratio = width / height,
_width = ko.observable(width),
_height = ko.observable(height);
this.width = ko.dependentObservable({ read: _width, write: setWidth });
this.height = ko.dependentObservable({ read: _height, write: setHeight });
function setWidth(width) {
_width(width);
_height(width / _ratio);
}
function setHeight(height) {
_height(height);
_width(height * _ratio);
}
}
function ViewModel(data) {
var p = new Proportional(data.width, data.height);
this.width = p.width;
this.height = p.height;
}
fiddle
var tf = new Text(letter, font, color);
var tfContainer = new Container();
tfContainer.addChild(tf);
How can I find out what are the dimensions of the 'tfContainer'?
I know I can use tf.getMeasuredWidth() and tf.getMeasuredLineHeight() but I'd rather use more general approach. Besides that doesn't return accurate measurements.
#Akonsu is correct, there is no support for width and height, largely because calculating it is very expensive, especially in vectors and groups with transformations on children. We are considering it, but there is no concrete plans for it yet.
-Lanny (gskinner.com)
there is no such functionality in easel.js. I read somewhere the they were planning to add it but it is not there yet as far as I know.
yes, adding width and height to the DisplayObject is a must. there could be method calculateSize(), and only when you try to get the size, to be called, if the size is invalidated and needs recalculation.
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