-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathaspecty.js
244 lines (153 loc) · 6.21 KB
/
aspecty.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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
/*
# Aspecty
## version 0.1.1
Aspecty is a CSS reprocessor that adds support for an aspect-ratio property using JS. This plugin allows you to define a desired aspect-ratio for an element, based on its rendered width on the page.
For any element with an aspect ratio defined, event listeners will be added to reprocess the styles on the following events:
- `mouseenter`
- `mouseleave`
- `mousedown`
- `mouseup`
- `focus`
- `blur`
By default, Aspecty will reprocess aspect ratios by watching the following events:
- `load`
- `resize`
- `input`
- `click`
To run Aspecty whenever you want, use the `aspecty.load()` function in JS.
The aspect ratio property can be used in CSS with the property name `--aspect-ratio` and a ratio, expressed as width and height as unitless numbers, separated by a slash `/`:
--aspect-ratio: width/height;
You can use it in CSS like this:
div {
background: lime;
--aspect-ratio: 16/9;
}
Aspecty will look through the document for any element matching the selector (in this case `div`) and create a new rule with a `height` value calculated based on each matching element's `offsetWidth` divided by the aspect ratio defined in CSS.
- https://github.com/tomhodgins/cssplus
Author: Tommy Hodgins
License: MIT
*/
// Uses Node, AMD or browser globals to create a module
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD: Register as an anonymous module
define([], factory)
} else if (typeof module === 'object' && module.exports) {
// Node: Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node
module.exports = factory()
} else {
// Browser globals (root is window)
root.aspecty = factory()
}
}(this, function() {
const aspecty = {}
aspecty.style = ''
aspecty.count = 0
aspecty.load = () => {
// Find (or create) style tag to populate
const style_tag = document.querySelector('[data-aspecty-style]') || (() => {
const tag = document.createElement('style')
tag.setAttribute('data-aspecty-style', '')
document.head.appendChild(tag)
return tag
})()
// Reset plugin styles and element count
aspecty.style = ''
aspecty.count = 0
// Reset count on [data-aspecty] elements in DOM
Array.from(document.querySelectorAll('[data-aspecty]'), tag => {
tag.setAttribute('data-aspecty', '')
})
aspecty.findRules()
// Populate style tag with style
if (style_tag.innerHTML !== `\n${aspecty.style}\n`) {
style_tag.innerHTML = `\n${aspecty.style}\n`
}
}
aspecty.findRules = () => {
// For each stylesheet
Array.from(document.styleSheets, sheet => {
// For each rule
sheet.cssRules && Array.from(sheet.cssRules, rule => {
aspecty.process(rule)
})
})
}
aspecty.process = rule => {
// If rule is a qualified rule, process it
if (rule.type === 1) {
aspecty.style += aspecty.transform(rule)
}
// If rule is an at-rule, find all qualified rules inside
if (rule.type === 4) {
let css_rules = ''
// Remember media query text
let mediaText = rule.media.mediaText
// If there are qualified rules, find all rules
rule.cssRules && Array.from(rule.cssRules, mediaRule => {
css_rules += aspecty.transform(mediaRule)
})
// If there is at least one new rule, wrap in at-rule with media text
if (css_rules.length > 0) {
aspecty.style += ` @media ${mediaText} {\n${css_rules.replace(/^(.*)$/gmi,' $1')}\n }\n`
}
}
}
aspecty.transform = rule => {
let newRule = ''
let selector = rule.selectorText.replace(/(.*)\s{/gi, '$1')
let ruleText = rule.cssText.replace(/.*\{(.*)\}/gi, '$1')
let elWidth = 0
let width = 0
let height = 0
let specificity = ''
// For each rule, search for `-aspect-ratio`
ruleText.replace(/(--aspect-ratio:\s*)(\d\.*\d*\s*\/\s*\d\.*\d*)(\s*\!important)*\s*(?:;|\})/i, (string, property, value, important) => {
// Extract width, height, and !important from value
width = parseInt(value.split('/')[0])
height = parseInt(value.split('/')[1])
specificity = important || ''
let selectorList = (selector.split(','))
selectorList.map(partial => {
// For each element matching this selector
Array.from(document.querySelectorAll(partial), (tag, i) => {
elWidth = tag.offsetWidth || 0
// If the matching element has a non-zero width
if (elWidth) {
// Increment the plugin element count
aspecty.count++
// Create a new selector for our new CSS rule
let newSelector = `${partial}[data-aspecty~="${aspecty.count}"]`
// If element has no preexisting attribute, add event listeners
if (!tag.getAttribute('data-aspecty')) {
tag.addEventListener('mouseenter', aspecty.load)
tag.addEventListener('mouseleave', aspecty.load)
tag.addEventListener('mousedown', aspecty.load)
tag.addEventListener('mouseup', aspecty.load)
tag.addEventListener('focus', aspecty.load)
tag.addEventListener('blur', aspecty.load)
}
// Mark matching element with attribute and plugin element count
let currentAttr = tag.getAttribute('data-aspecty')
tag.setAttribute('data-aspecty', `${currentAttr} ${aspecty.count}`)
// Height for new rule from offsetWidth, divided by aspect ratio
let newHeight = elWidth / (width/height)
// Create new `height` declaration with new value and !important text
let newRuleText = `height: ${newHeight}px${specificity};`
// And add our new rule to the rule list
newRule += `\n/* ${selector} */\n${newSelector} {\n ${newRuleText}\n}\n`
}
})
})
})
return newRule
}
// Update every `load`, `resize`, `input`, and `click`
window.addEventListener('load', aspecty.load)
window.addEventListener('resize', aspecty.load)
window.addEventListener('input', aspecty.load)
window.addEventListener('click', aspecty.load)
return aspecty
}))