I have a chrome app that I want to correctly resize (dimensions proportional to the screen width) on resolution change. I have written a function that redraws the app with the correct dimensions, now I want to execute it only when it needs to.
A resolution change causes screen.width to change, but (probably unsurprisingly since they relate to different things) the "resize" event is not fired, and as far as I can tell no event is fired.
I know about the Proxy object () so I wrote some code which detects when a variable is set and executes my callback, this seemed to work but not in the instance of resolution change.
So I searched online and tried this (which is essentially the answer provided in several stackoverflow questions on a similar topic, and indeed what I initially produced, albeit lacking the abstraction that the newish Proxy object offers).
This also seems to work (in the sense if I say manually set screen.width after having done screen.watch("width", () => console.log("hello")); and then do screen.width = 100; my callback is executed). But not in the instance of resolution change (in fact, perhaps most importantly, assigning this watcher seems to prevent screen.width getting assigned).
I have three questions
1) What is going on when I assign a watcher/proxy that's messing things up.
2) How could I find out what the browser is doing at this level of detail (what sends the trigger to change screen.width? I guess it is the OS, what does this look like)
3) Is there a better way to achieve what I was initially going for (the chrome app resizing).
Mostly I am interested in question 1) and don't care much about 3) any more.
To replicate my issue,
- open a new tab in firefox or chrome
- go to the developer console.
- check screen.width, change resolution, observe that screen.width changes
- Copy and paste the code from
- Do screen.watch("width", (id, oldval, newval) => {console.log("hello"); return newval;});
- Do screen.width = 100; and observe that hello is logged and screen.width is set to 100
- Change resolution, observe that screen.width is not set.
Edit - As revealed after Bertrand's answer, the resize event may actually fire on resolution change, but this seems to be as a response to the resolution change forcing the boundary of the the window to get smaller, if the window is small then screen.width can change without firing a resize event.
I have a chrome app that I want to correctly resize (dimensions proportional to the screen width) on resolution change. I have written a function that redraws the app with the correct dimensions, now I want to execute it only when it needs to.
A resolution change causes screen.width to change, but (probably unsurprisingly since they relate to different things) the "resize" event is not fired, and as far as I can tell no event is fired.
I know about the Proxy object (https://developer.mozilla/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) so I wrote some code which detects when a variable is set and executes my callback, this seemed to work but not in the instance of resolution change.
So I searched online and tried this https://gist.github./eligrey/384583 (which is essentially the answer provided in several stackoverflow questions on a similar topic, and indeed what I initially produced, albeit lacking the abstraction that the newish Proxy object offers).
This also seems to work (in the sense if I say manually set screen.width after having done screen.watch("width", () => console.log("hello")); and then do screen.width = 100; my callback is executed). But not in the instance of resolution change (in fact, perhaps most importantly, assigning this watcher seems to prevent screen.width getting assigned).
I have three questions
1) What is going on when I assign a watcher/proxy that's messing things up.
2) How could I find out what the browser is doing at this level of detail (what sends the trigger to change screen.width? I guess it is the OS, what does this look like)
3) Is there a better way to achieve what I was initially going for (the chrome app resizing).
Mostly I am interested in question 1) and don't care much about 3) any more.
To replicate my issue,
- open a new tab in firefox or chrome
- go to the developer console.
- check screen.width, change resolution, observe that screen.width changes
- Copy and paste the code from https://gist.github./eligrey/384583
- Do screen.watch("width", (id, oldval, newval) => {console.log("hello"); return newval;});
- Do screen.width = 100; and observe that hello is logged and screen.width is set to 100
- Change resolution, observe that screen.width is not set.
Edit - As revealed after Bertrand's answer, the resize event may actually fire on resolution change, but this seems to be as a response to the resolution change forcing the boundary of the the window to get smaller, if the window is small then screen.width can change without firing a resize event.
Share edited Apr 12, 2019 at 20:30 Countingstuff asked Apr 12, 2019 at 16:09 CountingstuffCountingstuff 7837 silver badges14 bronze badges 6-
The simplest solution would be to have a timer, and inspect
screen.width
say once per 10 sec. – georg Commented Apr 12, 2019 at 16:12 - Adding an event listner to the windows resize event doesn't work? Normally it works fine in chrome. – Mark Baijens Commented Apr 12, 2019 at 16:12
- @georg I have considered that but am not happy with that as a solution (I need the response to be very quick). The reason I don't necessarily care too much about the chrome app any more is I have a back up plan for what I needed it for that will definitely work. – Countingstuff Commented Apr 12, 2019 at 16:16
- @Mark Baijens that's correct, I am happy with window.addEventListener("resize", f) and have used it before, but it seems resolution change is not the same as resize, I just tried it again on chrome and it did not work. If it is relevant I am using Windows 10. – Countingstuff Commented Apr 12, 2019 at 16:16
- In chrome there is Resize Observer – pmkro Commented Apr 12, 2019 at 16:19
2 Answers
Reset to default 5 +50What is going on when I assign a watcher/proxy that's messing things up.
The issue is that the width
property is actually a getter, on Screen.prototype
. It's not an ordinary value on window.screen
:
console.log(window.screen.width);
console.log(window.screen.hasOwnProperty('width'));
// The property exists on the prototype instead, and is a getter:
const descriptor = Object.getOwnPropertyDescriptor(Screen.prototype, 'width');
console.log(descriptor);
If the width
property were an ordinary property, posed of just a plain value, which was set by the browser via eval
ing
window.screen.width = 1500
then a proxy or the Object.watch
polyfill would work, because you would be able to intercept the assignment to the .width
property. This is why you see that, after assigning your own setter to window.screen.width
:
Do
screen.width = 100;
and observe that hello is logged and screen.width is set to 100
It shows you hello
- you're invoking the setter that you previously assigned to the screen
property of window
. In contrast, because the native built-in property is not a plain value which gets assigned to by the browser, your hello
does not get logged when the screen changes. The situation is a bit similar to what's going on in this example snippet:
const obj = (() => {
let privateVal = 'propOriginal';
// privateVal changes after 500ms:
setTimeout(() => {
console.log('Changing privateVal to propChanged');
privateVal = 'propChanged';
}, 500);
// Return an object which has a getter, which returns privateProp:
return {
get privateProp() {
return privateVal;
}
};
})();
// At this point, if one only has a reference to `obj`,
// there is no real way to watch for changes to privateVal:
console.log(obj.privateProp);
// Assigning a custom setter to obj.privateProp
// will only result in observing attempted assignments to obj.privateProp
// but will not be able to observe the change to privateVal:
Object.defineProperty(obj, 'privateProp', { set(newVal) {
console.log('Observed change: new value is ' + newVal);
}});
setTimeout(() => {
obj.privateProp = 1000;
}, 2500);
As you can see, the setter function added to obj
in the outer scope cannot capture the change to privateVal
. This isn't exactly what's happening with window.screen
, but it's similar; you do not have access to the underlying code structure that results in changes to the value returned by calling the built-in getter.
The built-in getter function on screen
is posed of native code - this means that it's not an ordinary Javascript function. Functions posed of native code are always functions provided by the browser (or whatever environment the code is running in); they often cannot be emulated by any Javascript functions you write yourself. For example, only the window.history
object can perform history actions like history.back()
; if window.history
gets overwritten, and you don't have a saved reference to it or any other history
objects, there's no way to write your own function that can do history.back()
, because .back()
invokes privileged native code that requires an interface between the visible Javascript and the browser engine.
Similarly, the built-in window.screen
getter, when called, returns a value not directly observable by plain Javascript. Without polling, the only other way to watch for a change is by listening for the resize
event, as covered in the other answer. (This resize
event, similar to the underlying value returned by window.screen
, is managed by browser internals not observable otherwise.)
If the resize
event does not get fired in response to a change in resolution - for example, if the browser window is small enough already that a change is not required for the smaller resolution - then the resolution change does not result in any event being fired, which means there's unfortunately no way to listen for it, aside from polling. (You can try it for yourself, it doesn't look like any other events are triggered on resolution change)
One could probably write (or tweak) a browser engine which listens for a resolution change from the OS and dispatches a Javascript event when found, but doing so would require knowledge far beyond Javascript - feel free to browse Chromium's source code for details, but it's pretty plicated, and probably opaque to one without experience in the languages used.
According to Mozilla, screen resize event is only fired on window
object. Therefore, you should add listener to window
not to screen
:
window.addEventListener('resize', yourfunc)
Tested under Windows 10, it works like a charm with Chrome. Worth to say that the actual screen size puted by the browser use either the screen resolution and the zoom.
For instance, if you're on 1920x1080, zoom 100% :
- Passing to 3840x2160, zoom 200% will output a browser window of 1920x1080 and
resize
seems not triggered - Passing to zoom 150% will output a browser window of 1280x720 and
resize
is triggered
Nevertheless, there's a pitfall. Screen resolution update will only be detected if it actually triggers a browser window resize. That is not so obvious given the windowed/fullscreen initial state of the browser window and the smaller/bigger screen size evolution.