I wanted to detect value changes on the display
CSS property by changes on the class
attribute, and I came up with the below snippet.
I know getComputedStyle
returns a read-only live CSSStyleDeclaration
object, but as the object updates automatically when the element's styles are changed, I've assumed it assigns its properties somehow.
But it didn't call the getter and the setter. Why does this happen and then how does it assign its properties when it's read-only?
let parent = document.querySelector(".parent");
let child = parent.querySelector(".child");
let style = getComputedStyle(child);
let display = Symbol("display");
style[display] = style.display;
Object.defineProperty(style, "display", {
get() {
console.log("getter");
return style[display];
},
set(value) {
console.log("setter", value);
style[display] = value;
}
});
let button = document.querySelector("button");
button.addEventListener("click", () => {
child.classList.toggle("hide");
});
.child {
height: 100px;
background-color: #80a0c0;
}
.hide {
display: none;
}
<button>Toggle</button>
<div class="parent">
<div class="child"></div>
</div>
Host-provided objects are not required to play nice. :-) (Well, there are some requirements even on host-provided objects.)
Although on Chrome and related the object claims that the display
property is a simple data property (on Firefox and Legacy Edge, it's much more reasonable, an accessor property on the prototype):
let style = getComputedStyle(document.querySelector(".child"));
let kind = "own";
do {
const descr = Object.getOwnPropertyDescriptor(style, "display");
if (descr) {
console.log(kind + " property:", descr);
break;
}
if (kind === "own") {
kind = "prototype";
} else {
kind += "'s prototype";
}
style = Object.getPrototypeOf(style);
} while (style);
.child {
height: 100px;
background-color: #80a0c0;
}
.hide {
display: none;
}
<button>Toggle</button>
<div class="parent">
<div class="child"></div>
</div>
...it's lying. :-) (On Chrome.) Your is successfully creating an accessor property on the object, and then the object stops reflecting the changes to the underlying display
property of the element (since it's just updating the property you've named with a symbol):
let parent = document.querySelector(".parent");
let child = parent.querySelector(".child");
let style = getComputedStyle(child);
let display = Symbol("display");
style[display] = style.display;
Object.defineProperty(style, "display", {
get() {
console.log("getter");
return style[display];
},
set(value) {
console.log("setter", value);
style[display] = value;
}
});
let button = document.querySelector("button");
button.addEventListener("click", () => {
child.classList.toggle("hide");
console.log("style.display: " + style.display);
});
.child {
height: 100px;
background-color: #80a0c0;
}
.hide {
display: none;
}
<button>Toggle</button>
<div class="parent">
<div class="child"></div>
</div>
You can replicate that same behavior with a Proxy
:
const realStyle = {
display: "block"
};
const style = new Proxy(realStyle, {
get(target, propName) {
return target[propName];
},
set(target, propName, value, receiver) {
if (propName in target) {
return;
}
return Reflect.set(target, propName, value, receiver);
}
});
console.log("descriptor for `display`:");
console.log(Object.getOwnPropertyDescriptor(style, "display"));
console.log("style.display = " + style.display);
console.log("Setting it to 'none'");
style.display = "none";
console.log("style.display = " + style.display);
const d = Symbol("display");
style[d] = style.display;
Object.defineProperty(style, "display", {
get() {
console.log("getter");
return style[d];
},
set(value) {
console.log("setter");
style[d] = value;
}
});
console.log("style.display = " + style.display);
console.log("Setting it to 'none'");
style.display = "none";
console.log("style.display = " + style.display);
.as-console-wrapper {
max-height: 100% !important;
}
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