joicomponents
Version:
Design patterns to build native web components
113 lines (100 loc) • 3.11 kB
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();
/*media query listener*/
this.__size = undefined;
this._sizeSettings = undefined;
this._viewportObserver = this.viewportChanged.bind(this);
this._mql = undefined;
postConstructionCallback(function () {
if (!this._mql) //only triggers at startup if no size-setting attribute was given at startup
this.setUpMQL();
}.bind(this));
/*event listener*/
this._onScrollListener = this.onScroll.bind(this);
}
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.setUpMQL();
}
setUpMQL() {
//remove old mql listeners (if any)
if (this._mql) {
for (let mq of this._mql)
mq.removeListener(this._viewportObserver);
}
//add new mql listeners
this._mql = [];
const settings = this.getAttribute("size-settings");
this._sizeSettings = settings ? JSON.parse(settings) : defaultSizes;
for (let value of Object.values(this._sizeSettings)) {
let mq = window.matchMedia('(max-width: ' + value + 'px)');
mq.addListener(this._viewportObserver);
this._mql.push(mq);
}
this.viewportChanged();
}
viewportChanged() {
const sorted = Object
.entries(this._sizeSettings)
.sort(([aKey, aValue], [bKey, bValue]) => aValue <= bValue)
.filter(([key, value]) => value <= window.innerWidth);
if (!sorted.length) {
this.__size = null;
this.removeAttribute("_size");
} else {
this.__size = sorted[sorted.length - 1][0];
this.setAttribute("_size", this.__size);
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>