-
Notifications
You must be signed in to change notification settings - Fork 10
/
watched-box.js
115 lines (99 loc) · 3.63 KB
/
watched-box.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/**
* @module WatchedBox
* @description
* A custom element for ResizeObserver observation, accepting any CSS length units
* @property {string} widthBreaks=1024px Comma-separated list of CSS `width` values
* @property {string} heightBreaks=768px Comma-separated list of CSS `height` values
* @property {string} prefix=null A string to prefix each generated class
*/
export default class WatchedBox extends HTMLElement {
constructor() {
super();
// Convert supplied CSS length to pixels
// for comparison in the observer
this.getPixels = value => {
// A 'test' element is required for measurement
let test = document.createElement('div');
Object.assign(test.style, {
// Absolute positioning to free element for resizing
// and avoid potential jumps/jank
position: 'absolute',
width: value
});
this.appendChild(test);
// The value needed is offsetWidth
let pixels = test.offsetWidth;
this.removeChild(test);
return pixels;
}
this.getPrefix = () => this.prefix ? `${this.prefix}-` : '';
this.toggleClasses = (watched, dimension, value, contentRect) => {
const length = dimension === 'w' ? contentRect.width : contentRect.height;
// contentRect values are in pixels, hence
// the use of the `getPixels` conversion function
const q = length <= this.getPixels(value);
watched.target.classList.toggle(`${this.getPrefix()}${dimension}-lte-${value}`, q);
watched.target.classList.toggle(`${this.getPrefix()}${dimension}-gt-${value}`, !q);
}
this.observe = () => {
this.ro = new ResizeObserver(entries => {
// We only need the custom element itself: the first entry
const watched = entries[0];
const contentRect = watched.contentRect;
// Take the supplied widths, from the width attribute value
const widths = this.widthBreaks.replace(/ /g,'').split(',');
widths.forEach(width => {
this.toggleClasses(watched, 'w', width, contentRect);
});
// Take the supplied height, from the height attribute value
const heights = this.heightBreaks.replace(/ /g,'').split(',');
heights.forEach(height => {
this.toggleClasses(watched, 'h', height, contentRect);
});
// Orientation classes to mimic the orientation @media query
const ratio = contentRect.width / contentRect.height;
watched.target.classList.toggle(`${this.getPrefix()}landscape`, ratio > 1);
watched.target.classList.toggle(`${this.getPrefix()}portrait`, ratio < 1);
watched.target.classList.toggle(`${this.getPrefix()}square`, ratio == 1);
});
this.ro.observe(this);
}
this.unobserve = () => {
this.ro.unobserve(this);
}
}
get widthBreaks() {
return this.getAttribute('widthBreaks') || '1024px';
}
set widthBreaks(val) {
return this.setAttribute('widthBreaks', val);
}
get heightBreaks() {
return this.getAttribute('heightBreaks') || '768px';
}
set heightBreaks(val) {
return this.setAttribute('heightBreaks', val);
}
get prefix() {
return this.getAttribute('prefix') || null;
}
set prefix(val) {
return this.setAttribute('prefix', val);
}
static get observedAttributes() {
return ['widthBreaks', 'heightBreaks'];
}
connectedCallback() {
this.observe();
}
attributeChangedCallback() {
if (this.ro) {
this.unobserve();
}
this.observe();
}
}
// If ResizeObserver and Custom Elements are supported, initialize
if ('ResizeObserver' in window && 'customElements' in window) {
customElements.define('watched-box', WatchedBox);
}