Skip to content

Commit

Permalink
Merge pull request Snugug#31 from Snugug/polyfill-refactor
Browse files Browse the repository at this point in the history
Polyfill refactor
  • Loading branch information
Snugug committed Jan 5, 2015
2 parents e92cf4a + 611476a commit 5c8f480
Show file tree
Hide file tree
Showing 15 changed files with 304 additions and 81 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules
.vendor
.bundle
.sass-cache
coverage/
3 changes: 1 addition & 2 deletions Gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ require('./tasks/karma')(gulp);
// Dist Tasks
//////////////////////////////
require('./tasks/dist')(gulp, [
'build/**/*.js',
'!build/**/*.min.js'
'build/**/eq.js'
]);

//////////////////////////////
Expand Down
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
# eq.js [![Build Status](https://travis-ci.org/Snugug/eq.js.svg)](https://travis-ci.org/Snugug/eq.js) [![Coverage Status](https://img.shields.io/coveralls/Snugug/eq.js.svg)](https://coveralls.io/r/Snugug/eq.js?branch=1.x.x) [![Code Climate](https://codeclimate.com/github/Snugug/eq.js/badges/gpa.svg)](https://codeclimate.com/github/Snugug/eq.js) [![Bower version](https://badge.fury.io/bo/eq.js.svg)](http://badge.fury.io/bo/eq.js)
### Element queries, fast and light

## WARNING: 1.5.0 does not work with IE8; I totally forgot about it while writing the CSS feature. When I have resolved the issue, I will pull 1.5.0 and push 1.6.0

Element queries are the "holy grail" of responsive web design, allowing you to create a single component that can be dropped into any position in any layout and have them respond appropriately. Unfortunately, due to some hard-to-deal-with chicken-and-egg cases, especially involving inline elements, it's unlikely that element queries will make it into browsers any time soon.

**eq.js** aims to be a relatively easy to use drop-in solution to JavaScript powered element queries. Weighing in at about 3.3KB minified, around 1.3KB gzipped, and requiring no external dependencies, **eq.js** sets itself apart through size, speed, and ease of use. Simply drop **eq.js** on to your site and set the `eq-pts` attribute of your element (or set your points in Sass) and you're ready to go!
**eq.js** aims to be a relatively easy to use drop-in solution to JavaScript powered element queries. Weighing in at about 2.5KB minified, around 1.1KB gzipped, and requiring no external dependencies, **eq.js** sets itself apart through size, speed, and ease of use. Simply drop **eq.js** on to your site and set the `eq-pts` attribute of your element (or set your points in Sass) and you're ready to go!

## Installation

Expand Down Expand Up @@ -151,7 +149,7 @@ add_import_path "bower_components/eq.js/sass"

**eq.js** uses [`document.querySelectorAll()`](http://caniuse.com/queryselector) and provides polyfills for [`requestAnimationFrame()`](http://caniuse.com/requestanimationframe) and [`Object.getPrototypeOf`](http://stackoverflow.com/a/15851520/703084). It has been tested in the following browsers:

* IE9+ (IE8+ with [domready](https://github.com/ded/domready) available for `DOMContentLoaded` support)
* IE8+ (see below for notes)
* Firefox 3.5+
* Chrome
* Safari
Expand All @@ -165,7 +163,17 @@ add_import_path "bower_components/eq.js/sass"
* Firefox for Android
* IE Mobile

**Caveats**: On the current test site in IE8, the correct attributes get applied and the correct CSS gets applied (check in the developer tools, be sure to refresh the HTML after you've loaded the page or it'll appear as if they haven't!), but the correct paint doesn't get applied. I'm not entirely sure this is why, I guess this is due to the number of nodes, but really I've got no idea why it doesn't repaint properly.
### A note on IE8/Older Browser Support

There are two files provided; `eq.min.js` and `eq.polyfilled.min.js`. The later includes the polyfills needed to run **eq.js** in older browsers that are missing some newer JavaScript niceties (yes, this includes IE8+). While this allows for a drop-in solution using just what's provided here, a better solution (and where a bunch of the polyfills come from), consider using something like a [polyfill service](https://github.com/Financial-Times/polyfill-service) for a more robust and well-rounded solution.

The specific polyfills included are as follows:

* [`Object.getPrototypeOf`](http://kangax.github.io/compat-table/es5/#Object.getPrototypeOf)
* [`window.requestAnimationFrame`](http://caniuse.com/#feat=requestanimationframe)
* [`Event.DOMContentLoaded`](http://caniuse.com/#feat=domcontentloaded)
* [`window.getComputedStyle`](http://caniuse.com/#feat=getcomputedstyle)
* [`Array.prototype.forEach`](http://kangax.github.io/compat-table/es5/#Array.prototype.forEach)

## Technical Mumbo Jumbo

Expand Down
7 changes: 2 additions & 5 deletions bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eq.js",
"version": "1.5.0",
"version": "1.6.0",
"authors": [
"Sam Richard <snugug@gmail.com>"
],
Expand All @@ -27,8 +27,5 @@
"index.html",
"package.json",
"sass/style.scss"
],
"dependencies": {
"domready": "*"
}
]
}
69 changes: 6 additions & 63 deletions build/eq.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* eqjs.query - Runs through all nodes and finds their widths and points
* eqjs.nodeWrites - Runs through all nodes and writes their eq status
*/
(function (eqjs, domready) {
(function (eqjs) {
'use strict';

function EQjs() {
Expand All @@ -20,55 +20,6 @@
this.callback = undefined;
}

/** @{polyfills} **/
/*
* Object.getPrototypeOf Polyfill
* From http://stackoverflow.com/a/15851520/703084
*/
if (typeof Object.getPrototypeOf !== 'function') {
Object.getPrototypeOf = ''.__proto__ === String.prototype ? function (object) {
return object.__proto__;
}
: function (object) {
// May break if the constructor has been tampered with
return object.constructor.prototype;
};
}

/*
* Request Animation Frame Polyfill
*
* Written by Erik Möller and Paul Irish
* From http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
*/
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
}

if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function (callback, element) {
element = element;
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function () {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}

if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function (id) {
clearTimeout(id);
};
}

/** {polyfills}@ **/

/*
* Add event (cross browser)
* From http://stackoverflow.com/a/10150042
Expand Down Expand Up @@ -284,18 +235,10 @@
*
* Fires on document load; for HTML based EQs
*/
if (domready) {
domready(function () {
eqjs.refreshNodes();
eqjs.query(undefined, true);
});
}
else {
addEvent(window, 'DOMContentLoaded', function () {
eqjs.refreshNodes();
eqjs.query(undefined, true);
});
}
addEvent(window, 'DOMContentLoaded', function () {
eqjs.refreshNodes();
eqjs.query(undefined, true);
});

/*
* Window Loaded
Expand Down Expand Up @@ -325,4 +268,4 @@
} else {
window.eqjs = eqjs;
}
})(window.eqjs, window.domready);
})(window.eqjs);
224 changes: 224 additions & 0 deletions build/polyfills.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
/**
* Polyfills for eq.js
**/
(function () {
/*
* Object.getPrototypeOf Polyfill
* From http://stackoverflow.com/a/15851520/703084
*/
if (typeof Object.getPrototypeOf !== 'function') {
Object.getPrototypeOf = ''.__proto__ === String.prototype ? function (object) {
return object.__proto__;
}
: function (object) {
// May break if the constructor has been tampered with
return object.constructor.prototype;
};
}

/*
* Request Animation Frame Polyfill
*
* Written by Erik Möller and Paul Irish
* From http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
*/
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
}

if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function (callback, element) {
element = element;
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function () {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}

if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function (id) {
clearTimeout(id);
};
}

/**
* DOMContentLoaded Polyfill
*
* Adapted from the Financial Times polyfill service
* https://github.com/Financial-Times/polyfill-service/blob/master/polyfills/Event.DOMContentLoaded/polyfill.js
**/
if (!('addEventListener' in window)) {
document.attachEvent('onreadystatechange', function() {
if (document.readyState === 'complete') {
document.dispatchEvent(new Event('DOMContentLoaded', {
bubbles: true
}));
}
});
}

/**
* getComputedStyle Polyfill
*
* Adapted from the Financial Times polyfill service
* https://github.com/Financial-Times/polyfill-service/blob/master/polyfills/getComputedStyle/polyfill.js
**/
if (!('getComputedStyle' in window)) {
(function (global) {
function getComputedStylePixel(element, property, fontSize) {
var
// Internet Explorer sometimes struggles to read currentStyle until the element's document is accessed.
value = element.document && element.currentStyle[property].match(/([\d\.]+)(%|cm|em|in|mm|pc|pt|)/) || [0, 0, ''],
size = value[1],
suffix = value[2],
rootSize;

fontSize = !fontSize ? fontSize : /%|em/.test(suffix) && element.parentElement ? getComputedStylePixel(element.parentElement, 'fontSize', null) : 16;
rootSize = property == 'fontSize' ? fontSize : /width/i.test(property) ? element.clientWidth : element.clientHeight;

return suffix == '%' ? size / 100 * rootSize :
suffix == 'cm' ? size * 0.3937 * 96 :
suffix == 'em' ? size * fontSize :
suffix == 'in' ? size * 96 :
suffix == 'mm' ? size * 0.3937 * 96 / 10 :
suffix == 'pc' ? size * 12 * 96 / 72 :
suffix == 'pt' ? size * 96 / 72 :
size;
}

function setShortStyleProperty(style, property) {
var
borderSuffix = property == 'border' ? 'Width' : '',
t = property + 'Top' + borderSuffix,
r = property + 'Right' + borderSuffix,
b = property + 'Bottom' + borderSuffix,
l = property + 'Left' + borderSuffix;

style[property] = (style[t] == style[r] && style[t] == style[b] && style[t] == style[l] ? [ style[t] ] :
style[t] == style[b] && style[l] == style[r] ? [ style[t], style[r] ] :
style[l] == style[r] ? [ style[t], style[r], style[b] ] :
[ style[t], style[r], style[b], style[l] ]).join(' ');
}

// <CSSStyleDeclaration>
function CSSStyleDeclaration(element) {
var
style = this,
currentStyle = element.currentStyle,
fontSize = getComputedStylePixel(element, 'fontSize'),
unCamelCase = function (match) {
return '-' + match.toLowerCase();
},
property;

for (property in currentStyle) {
Array.prototype.push.call(style, property == 'styleFloat' ? 'float' : property.replace(/[A-Z]/, unCamelCase));

if (property == 'width') {
style[property] = element.offsetWidth + 'px';
} else if (property == 'height') {
style[property] = element.offsetHeight + 'px';
} else if (property == 'styleFloat') {
style.float = currentStyle[property];
} else if (/margin.|padding.|border.+W/.test(property) && style[property] != 'auto') {
style[property] = Math.round(getComputedStylePixel(element, property, fontSize)) + 'px';
} else if (/^outline/.test(property)) {
// errors on checking outline
try {
style[property] = currentStyle[property];
} catch (error) {
style.outlineColor = currentStyle.color;
style.outlineStyle = style.outlineStyle || 'none';
style.outlineWidth = style.outlineWidth || '0px';
style.outline = [style.outlineColor, style.outlineWidth, style.outlineStyle].join(' ');
}
} else {
style[property] = currentStyle[property];
}
}

setShortStyleProperty(style, 'margin');
setShortStyleProperty(style, 'padding');
setShortStyleProperty(style, 'border');

style.fontSize = Math.round(fontSize) + 'px';
}

CSSStyleDeclaration.prototype = {
constructor: CSSStyleDeclaration,
// <CSSStyleDeclaration>.getPropertyPriority
getPropertyPriority: function () {
throw new Error('NotSupportedError: DOM Exception 9');
},
// <CSSStyleDeclaration>.getPropertyValue
getPropertyValue: function (property) {
return this[property.replace(/-\w/g, function (match) {
return match[1].toUpperCase();
})];
},
// <CSSStyleDeclaration>.item
item: function (index) {
return this[index];
},
// <CSSStyleDeclaration>.removeProperty
removeProperty: function () {
throw new Error('NoModificationAllowedError: DOM Exception 7');
},
// <CSSStyleDeclaration>.setProperty
setProperty: function () {
throw new Error('NoModificationAllowedError: DOM Exception 7');
},
// <CSSStyleDeclaration>.getPropertyCSSValue
getPropertyCSSValue: function () {
throw new Error('NotSupportedError: DOM Exception 9');
}
};

// <Global>.getComputedStyle
global.getComputedStyle = function getComputedStyle(element) {
return new CSSStyleDeclaration(element);
};
})(this);
}

/**
* Array.prototype.forEach Polyfill
*
* Adapted from the Financial Times polyfill service
* https://github.com/Financial-Times/polyfill-service/blob/master/polyfills/Array.prototype.forEach/polyfill.js
**/

if (!Array.prototype.forEach) {
Array.prototype.forEach = function forEach(callback) {
if (this === undefined || this === null) {
throw new TypeError(this + 'is not an object');
}

if (!(callback instanceof Function)) {
throw new TypeError(callback + ' is not a function');
}

var
object = Object(this),
scope = arguments[1],
arraylike = object instanceof String ? object.split('') : object,
length = Math.max(Math.min(arraylike.length, 9007199254740991), 0) || 0,
index = -1,
result = [],
element;

while (++index < length) {
if (index in arraylike) {
callback.call(scope, arraylike[index], index, object);
}
}
};
}
}());
Binary file removed dist/eq.gz.js
Binary file not shown.
Loading

0 comments on commit 5c8f480

Please sign in to comment.