joicomponents
Version:
Design patterns to build native web components
130 lines (105 loc) • 4.22 kB
Markdown
# Pattern: ResizeAttribute
The ResizeAttribute is a pattern for controlling the layout and behavior of a web component
depending on the viewport size, very similar to MediaQueryAttribute. But, instead of using
the MediaQuery interface, the ResizeAttribute pattern listens only for the `resize` event.
Currently, due to flaws in the MediaQuery library in Chrome, the ResizeAttribute pattern is safer
and thus more efficient than the MediaQueryAttribute pattern. It also yields much simpler code.
## Implementation
A web component that implements the ResizeAttribute pattern consists of:
* `size-settings`: a JsonAttribute describing the different thresholds for the media queries.
The `size-settings` is given on the form `{"XS": 0, "S": 600, "M": 800, "L": 1024}`.
The MediaQueryAttribute has a static, default settings object to fall back on.
* `_size`: a StubbornAttribute that reflects the current state of the web component
(`.__size` property). Every time the viewport passes one of the thresholds specified in
`size-settings`, the web component updates the `_size` attribute.
* `resize` event listener. For all changes of the viewport, check against the current `size-setting`
and `__size` prop. If the current size of the viewport would require the web component to update the
`_size` StubbornAttribute, then do so.
<code-demo src="demo/ResizeSmallMediumLarge.html"></code-demo>
```html
<script>
function postConstructionCallback(cb) {
if (document.readyState === "loading") {
window.addEventListener("DOMContentLoaded", cb);
} else {
Promise.resolve().then(function () {
Promise.resolve().then(cb)
});
}
}
const defaultSizes = {
S: 0,
M: 400,
L: 800
};
class SmallMediumLarge extends HTMLElement {
constructor() {
super();
this.__size = undefined;
this._sizeSettings = defaultSizes;
this._viewportObserver = this.viewportChanged.bind(this);
postConstructionCallback(this._viewportObserver);
/*event listener*/
this._onScrollListener = this.onScroll.bind(this);
}
connectedCallback() {
window.addEventListener("resize", this._viewportObserver);
}
disconnectedCallback() {
window.removeEventListener("resize", this._viewportObserver);
}
static get observedAttributes() {
return ["_size", "size-settings"];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "_size" && newValue !== this.__size)
this.setAttribute("_size", this.__size);
if (name === "size-settings")
this._sizeSettings = newValue ? JSON.parse(newValue) : defaultSizes;
}
viewportChanged() {
const sorted = Object
.entries(this._sizeSettings)
.sort(([aKey, aValue], [bKey, bValue]) => aValue <= bValue)
.filter(([key, value]) => value <= window.innerWidth);
const newSize = sorted.length ? sorted[sorted.length - 1][0] : null;
if (this.__size === newSize)
return;
this.__size = newSize;
this.__size === null ? this.removeAttribute("_size") : this.setAttribute("_size", this.__size);
/* setup listeners */
if (this.__size === "S")
window.addEventListener("scroll", this._onScrollListener);
else
window.removeEventListener("scroll", this._onScrollListener);
}
onScroll() {
console.log(this.id, "only observes scroll when its _size is small");
}
}
customElements.define("small-medium-large", SmallMediumLarge);
</script>
<style>
small-medium-large {
display: block;
height: 60%;
}
small-medium-large[_size="S"] {
border-bottom: 5px solid red;
}
small-medium-large[_size="M"] {
border-right: 5px solid orange;
}
small-medium-large[_size="L"] {
border-top: 5px solid green;
}
</style>
<small-medium-large id="one">
default sizes
</small-medium-large>
<small-medium-large id="two" size-settings='{"S": 0, "M": 200, "L": 400}'>
custom sizes
</small-medium-large>
```
## References
* [Stackoverflow: viewport dimensions](https://stackoverflow.com/questions/1248081/get-the-browser-viewport-dimensions-with-javascript#answer-8876069)