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.org/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.com/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,
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.
What 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, composed 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 composed of native code - this means that it's not an ordinary Javascript function. Functions composed 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 complicated, and probably opaque to one without experience in the languages used.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With