Skip to content

Commit 786c776

Browse files
committed
Implemented better responsive image functionality.
Better readme
1 parent e33b90b commit 786c776

File tree

2 files changed

+198
-48
lines changed

2 files changed

+198
-48
lines changed

README.md

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
CSS Element Queries
2-
===================
1+
# CSS Element Queries
2+
33

44
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/marcj/css-element-queries?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
55

@@ -15,43 +15,53 @@ Features:
1515
- no interval/timeout detection. Truly event-based through integrated ResizeSensor class.
1616
- no CSS modifications. Valid CSS Syntax
1717
- all CSS selectors available. Uses regular attribute selector. No need to write rules in HTML.
18-
- supports and tested in webkit, gecko and IE(7/8/9/10/11).
18+
- supports and tested in webkit, gecko and IE(7/8/9/10/11)
1919
- `min-width`, `min-height`, `max-width` and `max-height` are supported so far
2020
- works with any layout modifications: HTML (innerHTML etc), inline styles, DOM mutation, CSS3 transitions, fluid layout changes (also percent changes), pseudo classes (:hover etc.), window resizes and more
2121
- no Javascript-Framework dependency (works with jQuery, Mootools, etc.)
22+
- Works beautiful for responsive images without FOUC
2223

2324
More demos and information: http://marcj.github.io/css-element-queries/
2425

25-
Example
26-
-------
26+
## Examples
27+
28+
### Element Query
2729

2830
```css
29-
.widget-name {
30-
padding: 25px;
31+
.widget-name h2 {
32+
font-size: 12px;
3133
}
32-
.widget-name[max-width="200px"] {
33-
padding: 0;
34+
35+
.widget-name[min-width~="400px"] h2 {
36+
font-size: 18px;
3437
}
35-
.widget-name[min-width="500px"] {
38+
39+
.widget-name[min-width~="900px"] h2 {
3640
padding: 55px;
41+
text-align: center;
42+
font-size: 24px;
3743
}
3844

39-
/* responsive images */
40-
.responsive-image img {
41-
width: 100%;
45+
.widget-name[min-width~="700px"] h2 {
46+
font-size: 34px;
47+
color: red;
4248
}
49+
```
4350

44-
.responsive-image[max-width^='400px'] img {
45-
content: url(demo/image-400px.jpg);
46-
}
51+
```html
52+
<div class="widget-name">
53+
<h2>Element responsiveness FTW!</h2>
54+
</div>
55+
```
4756

48-
.responsive-image[max-width^='1000px'] img {
49-
content: url(demo/image-1000px.jpg);
50-
}
57+
### Responsive image
5158

52-
.responsive-image[min-width='1000px'] img {
53-
content: url(demo/image-full.jpg);
54-
}
59+
```html
60+
<div data-responsive-image>
61+
<img data-src="http://placehold.it/350x150"/>
62+
<img min-width="350" data-src="http://placehold.it/700x300"/>
63+
<img min-width="700" data-src="http://placehold.it/1400x600"/>
64+
</div>
5565
```
5666

5767
Include the javascript files at the bottom and you're good to go. No custom javascript calls needed.
@@ -61,6 +71,13 @@ Include the javascript files at the bottom and you're good to go. No custom java
6171
<script src="src/ElementQueries.js"></script>
6272
```
6373

74+
## See it in action:
75+
76+
Here live http://marcj.github.io/css-element-queries/.
77+
78+
![Demo](http://marcj.github.io/css-element-queries/images/css-element-queries-demo.gif)
79+
80+
6481
## Module Loader
6582

6683
If you're using a module loader you need to trigger the event listening or initialization yourself:
@@ -77,13 +94,12 @@ EQ.listen();
7794
EQ.init();
7895
```
7996

80-
Issues
81-
------
97+
## Issues
8298

8399
- So far does not work on `img` and other elements that can't contain other elements. Wrapping with a `div` works fine though (See demo).
84100
- Adds additional hidden elements into selected target element and forces target element to be relative or absolute.
85101

86102

87-
License
88-
-------
89-
MIT license. Copyright [Marc J. Schmidt](http://marcjschmidt.de/).
103+
## License
104+
105+
MIT license. Copyright [Marc J. Schmidt](https://twitter.com/MarcJSchmidt).

src/ElementQueries.js

Lines changed: 155 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*/
2020
var ElementQueries = this.ElementQueries = function() {
2121

22-
this.withTracking = false;
22+
var trackingActive = false;
2323
var elements = [];
2424

2525
/**
@@ -130,6 +130,8 @@
130130
}
131131

132132
for (var k in attributes) {
133+
if(!attributes.hasOwnProperty(k)) continue;
134+
133135
if (attrValues[attributes[k]]) {
134136
this.element.setAttribute(attributes[k], attrValues[attributes[k]].substr(1));
135137
} else {
@@ -155,7 +157,7 @@
155157
}
156158
element.elementQueriesSetupInformation.call();
157159

158-
if (ElementQueries.instance.withTracking && elements.indexOf(element) < 0) {
160+
if (trackingActive && elements.indexOf(element) < 0) {
159161
elements.push(element);
160162
}
161163
}
@@ -174,7 +176,7 @@
174176
else allQueries[mode][property][value] += ','+selector;
175177
}
176178

177-
function executeQueries() {
179+
function getQuery() {
178180
var query;
179181
if (document.querySelectorAll) query = document.querySelectorAll.bind(document);
180182
if (!query && 'undefined' !== typeof $$) query = $$;
@@ -184,21 +186,135 @@
184186
throw 'No document.querySelectorAll, jQuery or Mootools\'s $$ found.';
185187
}
186188

189+
return query;
190+
}
191+
192+
/**
193+
* Start the magic. Go through all collected rules (readRules()) and attach the resize-listener.
194+
*/
195+
function findElementQueriesElements() {
196+
var query = getQuery();
197+
187198
for (var mode in allQueries) if (allQueries.hasOwnProperty(mode)) {
188-
for (var property in allQueries[mode]) if (allQueries[mode].hasOwnProperty(property)) {
189-
for (var value in allQueries[mode][property]) if (allQueries[mode][property].hasOwnProperty(value)) {
190-
var elements = query(allQueries[mode][property][value]);
191-
for (var i = 0, j = elements.length; i < j; i++) {
192-
setupElement(elements[i], {
193-
mode: mode,
194-
property: property,
195-
value: value
196-
});
197-
}
199+
200+
for (var property in allQueries[mode]) if (allQueries[mode].hasOwnProperty(property)) {
201+
for (var value in allQueries[mode][property]) if (allQueries[mode][property].hasOwnProperty(value)) {
202+
var elements = query(allQueries[mode][property][value]);
203+
for (var i = 0, j = elements.length; i < j; i++) {
204+
setupElement(elements[i], {
205+
mode: mode,
206+
property: property,
207+
value: value
208+
});
209+
}
210+
}
198211
}
199-
}
212+
200213
}
214+
}
215+
216+
/**
217+
*
218+
* @param {HTMLElement} element
219+
*/
220+
function attachResponsiveImage(element) {
221+
var children = [];
222+
var rules = [];
223+
var sources = [];
224+
var defaultImageId = 0;
225+
var lastActiveImage = -1;
226+
var loadedImages = [];
201227

228+
for (var i in element.children) {
229+
if(!element.children.hasOwnProperty(i)) continue;
230+
231+
if (element.children[i].tagName.toLowerCase() === 'img') {
232+
children.push(element.children[i]);
233+
234+
var minWidth = element.children[i].getAttribute('min-width') || element.children[i].getAttribute('data-min-width');
235+
//var minHeight = element.children[i].getAttribute('min-height') || element.children[i].getAttribute('data-min-height');
236+
var src = element.children[i].getAttribute('data-src') || element.children[i].getAttribute('url');
237+
238+
sources.push(src);
239+
240+
var rule = {
241+
minWidth: minWidth
242+
};
243+
244+
rules.push(rule);
245+
246+
if (!minWidth) {
247+
defaultImageId = children.length - 1;
248+
element.children[i].style.display = 'block';
249+
} else {
250+
element.children[i].style.display = 'none';
251+
}
252+
}
253+
}
254+
255+
lastActiveImage = defaultImageId;
256+
257+
function check() {
258+
var imageToDisplay = false, i;
259+
260+
for (i in children){
261+
if(!children.hasOwnProperty(i)) continue;
262+
263+
if (rules[i].minWidth) {
264+
if (element.offsetWidth > rules[i].minWidth) {
265+
imageToDisplay = i;
266+
}
267+
}
268+
}
269+
270+
if (!imageToDisplay) {
271+
//no rule matched, show default
272+
imageToDisplay = defaultImageId;
273+
}
274+
275+
if (lastActiveImage != imageToDisplay) {
276+
//image change
277+
278+
if (!loadedImages[imageToDisplay]){
279+
//image has not been loaded yet, we need to load the image first in memory to prevent flash of
280+
//no content
281+
282+
var image = new Image();
283+
image.onload = function() {
284+
children[imageToDisplay].src = sources[imageToDisplay];
285+
286+
children[lastActiveImage].style.display = 'none';
287+
children[imageToDisplay].style.display = 'block';
288+
289+
loadedImages[imageToDisplay] = true;
290+
291+
lastActiveImage = imageToDisplay;
292+
};
293+
294+
image.src = sources[imageToDisplay];
295+
} else {
296+
children[lastActiveImage].style.display = 'none';
297+
children[imageToDisplay].style.display = 'block';
298+
lastActiveImage = imageToDisplay;
299+
}
300+
}
301+
}
302+
303+
element.resizeSensor = new ResizeSensor(element, check);
304+
check();
305+
306+
if (trackingActive) {
307+
elements.push(element);
308+
}
309+
}
310+
311+
function findResponsiveImages(){
312+
var query = getQuery();
313+
314+
var elements = query('[data-responsive-image],[responsive-image]');
315+
for (var i = 0, j = elements.length; i < j; i++) {
316+
attachResponsiveImage(elements[i]);
317+
}
202318
}
203319

204320
var regex = /,?[\s\t]*([^,\n]*?)((?:\[[\s\t]*?(?:min|max)-(?:width|height)[\s\t]*?[~$\^]?=[\s\t]*?"[^"]*?"[\s\t]*?])+)([^,\n\s\{]*)/mgi;
@@ -249,14 +365,17 @@
249365
}
250366
}
251367

368+
var defaultCssInjected = false;
369+
252370
/**
253371
* Searches all css rules and setups the event listener to all elements with element query rules..
254372
*
255373
* @param {Boolean} withTracking allows and requires you to use detach, since we store internally all used elements
256374
* (no garbage collection possible if you don not call .detach() first)
257375
*/
258376
this.init = function(withTracking) {
259-
this.withTracking = withTracking;
377+
trackingActive = typeof withTracking === 'undefined' ? false : withTracking;
378+
260379
for (var i = 0, j = document.styleSheets.length; i < j; i++) {
261380
try {
262381
readRules(document.styleSheets[i].cssRules || document.styleSheets[i].rules || document.styleSheets[i].cssText);
@@ -266,7 +385,17 @@
266385
}
267386
}
268387
}
269-
executeQueries();
388+
389+
if (!defaultCssInjected) {
390+
var style = document.createElement('style');
391+
style.type = 'text/css';
392+
style.innerHTML = '[responsive-image] > img, [data-responsive-image] {overflow: hidden; padding: 0; } [responsive-image] > img, [data-responsive-image] > img { width: 100%;}';
393+
document.getElementsByTagName('head')[0].appendChild(style);
394+
defaultCssInjected = true;
395+
}
396+
397+
findElementQueriesElements();
398+
findResponsiveImages();
270399
};
271400

272401
/**
@@ -275,14 +404,13 @@
275404
* (no garbage collection possible if you don not call .detach() first)
276405
*/
277406
this.update = function(withTracking) {
278-
this.withTracking = withTracking;
279-
this.init();
407+
this.init(withTracking);
280408
};
281409

282410
this.detach = function() {
283411
if (!this.withTracking) {
284412
throw 'withTracking is not enabled. We can not detach elements since we don not store it.' +
285-
'Use ElementQueries.withTracking = true; before domready.';
413+
'Use ElementQueries.withTracking = true; before domready or call ElementQueryes.update(true).';
286414
}
287415

288416
var element;
@@ -310,12 +438,18 @@
310438
*/
311439
ElementQueries.detach = function(element) {
312440
if (element.elementQueriesSetupInformation) {
441+
//element queries
313442
element.elementQueriesSensor.detach();
314443
delete element.elementQueriesSetupInformation;
315444
delete element.elementQueriesSensor;
316-
console.log('detached');
445+
446+
} else if (element.resizeSensor) {
447+
//responsive image
448+
449+
element.resizeSensor.detach();
450+
delete element.resizeSensor;
317451
} else {
318-
console.log('detached already', element);
452+
//console.log('detached already', element);
319453
}
320454
};
321455

0 commit comments

Comments
 (0)