Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mediaqueries support #12

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
.DS_Store
node_modules/
tags
.idea
4 changes: 2 additions & 2 deletions Gruntfile.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = function(grunt) {
var project = {
files: ['src/*.js']
}
};

grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
Expand Down Expand Up @@ -49,7 +49,7 @@ limitations under the License.\n\

watch: {
options: {
atBegin: true,
atBegin: true
},
js: {
files: project.files,
Expand Down
60 changes: 60 additions & 0 deletions demos/mediaQueries-demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">

<style>
#container {
width: 600px;
font-size: 12px;
}

#polygon-shape-outside {
width: 200px;
height: 200px;
float: left;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200px' height='200px'><polygon points='0,0 142,33 89,141 0,200' fill='rgba(0,0,255, 0.5)'/></svg>");
shape-outside: polygon(0px 0px, 142px 33px, 89px 141px, 0px 200px);
}

@media (min-width: 640px) and (max-width: 960px) {
#polygon-shape-outside {
width: 300px;
height: 300px;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='300px' height='300px'><polygon points='0,0 242,133 189,241 0,300' fill='rgba(0,255,0, 0.5)'/></svg>");
shape-outside: polygon(0px 0px, 242px 133px, 189px 241px, 0px 300px);
shape-margin: 10px;
}
}

@media (min-width: 961px) {
#polygon-shape-outside {
width: 400px;
height: 400px;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='400px' height='400px'><polygon points='0,0 342,233 289,341 0,400' fill='rgba(255,0,0, 0.5)'/></svg>");
shape-outside: polygon(0px 0px, 342px 233px, 289px 341px, 0px 400px);
shape-margin: 20px;
}
}

.footer {
display: inline-block;
position: absolute;
bottom: 0; right: 0;
padding: .5em;
}
</style>
</head>
<body>
<div id="container">
<div id="polygon-shape-outside"></div>
<p>Pelicans are a genus of large water birds comprising the family Pelecanidae. They are characterised by a long beak and large throat pouch used for catching prey and draining water from the scooped up contents before swallowing. They have predominantly pale plumage, the exceptions being the Brown and Peruvian Pelicans. The bills, pouches and bare facial skin of all species become brightly coloured before the breeding season. The eight living pelican species have a patchy global distribution, ranging latitudinally from the tropics to the temperate zone, though they are absent from interior South America as well as from polar regions and the open ocean. Fossil evidence of pelicans dates back at least 30 million years, to the remains of a beak very similar to that of modern species recovered from Oligocene strata in France.</p>

<p>Long thought to be related to frigatebirds, cormorants, tropicbirds, gannets and boobies, pelicans are now known instead to be most closely related to the Shoebill and Hamerkop, and are placed in the order Pelecaniformes. Ibises, spoonbills and herons are more distant relatives, and have been classified in the same order. Pelicans frequent inland and coastal waters where they feed principally on fish, catching them at or near the water surface. Gregarious birds, they often hunt cooperatively and breed colonially. Four white-plumaged species tend to nest on the ground, and four brown or grey-plumaged species nest mainly in trees.</p>
</div>

<div class='footer'>text <a href='http://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License'>cc-sa</a> via <a href='http://wikipedia.org'>Wikipedia</a></div>

<script src='../shapes-polyfill.js'></script>
</body>
</html>
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "shapes-polyfill",
"version": "0.0.0",
"version": "0.0.1",
"description": "A Polyfill for CSS Shapes",
"main": "index.js",
"scripts": {
Expand All @@ -14,6 +14,10 @@
"Bear Travis",
"Hans Muller"
],
"contributors": [{
"name": "Cédric Saunier",
"email": "cedric.sa@gmail.com"
}],
"license": "BSD",
"readmeFilename": "README.md",
"devDependencies": {
Expand Down
178 changes: 165 additions & 13 deletions shapes-polyfill.js
Original file line number Diff line number Diff line change
Expand Up @@ -1127,12 +1127,16 @@ function createShapeGeometry(shapeValue, whenReady) {

function ShapeInfo(element) {
this.metrics = new Metrics(element);

var queryStep = this.getQueryStep(element.getAttribute('data-shape-size-mediaqueries').split('|'));

var parserSettings = {
metrics: this.metrics,
shapeOutside: element.getAttribute('data-shape-outside'),
shapeMargin: element.getAttribute('data-shape-margin'),
shapeImageThreshold: element.getAttribute('data-shape-image-threshold')
shapeOutside: element.getAttribute('data-shape-outside').split('|')[queryStep],
shapeMargin: element.getAttribute('data-shape-margin').split('|')[queryStep],
shapeImageThreshold: element.getAttribute('data-shape-image-threshold').split('|')[queryStep]
};

this.shapeValue = new ShapeValue(parserSettings);

var self = this;
Expand All @@ -1150,6 +1154,24 @@ ShapeInfo.prototype.onReady = function(callback) {
this.callback = callback;
};

ShapeInfo.prototype.getQueryStep = function(steps) {
var i = 0,
step = 0,
documentWidth = document.documentElement.clientWidth,
minWidth,
maxWidth;
for(i; i < steps.length; i++) {
minWidth = steps[i].split(',')[0].split('(')[1];
minWidth = minWidth === "null" ? 0 : Number(minWidth);
maxWidth = steps[i].split(',')[1].split(')')[0];
maxWidth = maxWidth === "null" ? documentWidth : Number(maxWidth);
if(documentWidth >= minWidth && documentWidth <= maxWidth) {
step = i;
}
}
return step;
};

ShapeInfo.prototype.leftExclusionEdge = function(line) { // { top, bottom, left, right }
return this.geometry ? this.geometry.leftExclusionEdge(line.top, line.bottom) : line.left;
};
Expand Down Expand Up @@ -1375,10 +1397,43 @@ Polyfill.prototype.run = function(settings) {
this.stylesLoaded = true;

new StylePolyfill(function(rules) {

var _this = this,
els,
queriesTab,
queryRule,
queryIndex;

rules.forEach(function(rule) {
var els = document.querySelectorAll(rule.selector);
for (var i = 0; i < els.length; i++)
els[i].setAttribute('data-' + rule.property, rule.value);
els = document.querySelectorAll(rule.selector);

for (var i = 0; i < els.length; i++){
// if this is new element, we init all the value to null
if(!els[i].hasAttribute('data-shape-outside')) {
els[i].setAttribute('data-shape-outside', 'null');
els[i].setAttribute('data-shape-margin', 'null');
els[i].setAttribute('data-shape-image-threshold', 'null');
els[i].setAttribute('data-shape-size-mediaqueries', '(null,null)');
}

queriesTab = els[i].getAttribute('data-shape-size-mediaqueries').split('|');
queryRule = '('+rule.minWidth+','+rule.maxWidth+')';
queryIndex = queriesTab.indexOf(queryRule);

if(queryIndex === -1) {
// mediaquery doesn't exist, we create a new entry
els[i].setAttribute('data-shape-outside', els[i].getAttribute('data-shape-outside') + '|null');
els[i].setAttribute('data-shape-margin', els[i].getAttribute('data-shape-margin') + '|null');
els[i].setAttribute('data-shape-image-threshold', els[i].getAttribute('data-shape-image-threshold') + '|null');
els[i].setAttribute('data-shape-size-mediaqueries', els[i].getAttribute('data-shape-size-mediaqueries') + '|' + queryRule);
// we update specific entry
_this.setPropertyForQuery(els[i], rule, queriesTab.length);
} else {
// mediaquery exist, we update specific entry
_this.setPropertyForQuery(els[i], rule, queryIndex);
}

}
});

self.run(settings);
Expand All @@ -1394,8 +1449,9 @@ Polyfill.prototype.run = function(settings) {
}

var els = document.querySelectorAll('[data-shape-outside]');
for (var i = 0; i < els.length; i++)
for (var i = 0; i < els.length; i++){
this.polyfill(els[i], settings);
}
};

Polyfill.prototype.teardown = function() {
Expand Down Expand Up @@ -1899,35 +1955,131 @@ StyleLoader.prototype.onComplete = function() {

function StylePolyfill(callback) {
this.callback = callback || function() {};
this.eminpx = this.getEmValue();
var self = this;
new StyleLoader(function(stylesheets) {
self.onStylesLoaded(stylesheets);
});
}

StylePolyfill.prototype.setPropertyForQuery = function(elem, rule, index) {
var propertyQueriesTab = elem.getAttribute('data-' + rule.property).split('|');
propertyQueriesTab[index] = rule.value;
elem.setAttribute('data-' + rule.property, propertyQueriesTab.join('|'));
};

// thanks to Scott Jehl getEmValue() function he wrote for Respond.js (https://github.com/scottjehl/Respond)
StylePolyfill.prototype.getEmValue = function() {
var ret,
div = window.document.createElement('div'),
body = window.document.body,
docElem = window.document.documentElement,
originalHTMLFontSize = docElem.style.fontSize,
originalBodyFontSize = body && body.style.fontSize,
fakeUsed = false;
div.style.cssText = "position:absolute;font-size:1em;width:1em";
if( !body ){
body = fakeUsed = doc.createElement( "body" );
body.style.background = "none";
}
// 1em in a media query is the value of the default font size of the browser
// reset docElem and body to ensure the correct value is returned
docElem.style.fontSize = "100%";
body.style.fontSize = "100%";
body.appendChild( div );
if( fakeUsed ){
docElem.insertBefore( body, docElem.firstChild );
}
ret = div.offsetWidth;
if( fakeUsed ){
docElem.removeChild( body );
}
else {
body.removeChild( div );
}
// restore the original values
docElem.style.fontSize = originalHTMLFontSize;
if( originalBodyFontSize ) {
body.style.fontSize = originalBodyFontSize;
}

return ret;
};

StylePolyfill.prototype.onStylesLoaded = function(stylesheets) {
var parsedStylesheets = this.parseForMediaQueries(stylesheets);
this.parseForShapes(parsedStylesheets);
};

StylePolyfill.prototype.parseForMediaQueries = function(stylesheets) {

var cleanCss,
queries,
styleBlock = [],
styleBlockMQ = [],
queryMinWidth,
queryMaxWidth,
self = this;
stylesheets.forEach(function(stylesheet) {

// thanks to Scott Jehl regexp he wrote for Respond.js (https://github.com/scottjehl/Respond)
cleanCss = stylesheet.cssText.replace( /\/\*[^*]*\*+([^/][^*]*\*+)*\//gi , '' ); // comments
queries = cleanCss.match( /@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi ); // queries

if(queries) {
queries.forEach(function(query) {
cleanCss = cleanCss.split(query).join("");
queryMinWidth = query.match( /\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/ );
queryMaxWidth = query.match( /\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/ );
styleBlockMQ.push({
cssText : query.match( /@media *([^\{]+)\{([\S\s]+?)$/ )[2],
minWidth : queryMinWidth ? queryMinWidth[2] === "em" ? queryMinWidth[1]*self.eminpx : queryMinWidth[1] : null,
maxWidth : queryMaxWidth ? queryMaxWidth[2] === "em" ? queryMaxWidth[1]*self.eminpx : queryMaxWidth[1] : null
});
});
}

styleBlock.push({
cssText: cleanCss,
minWidth: null,
maxWidth : null
});

});

return styleBlock.concat(styleBlockMQ);

};

StylePolyfill.prototype.parseForShapes = function(stylesheets) {
// use : and ; as delimiters, except between ()
// this will be sufficient for most, but not all cases, eg: rectangle(calc(100%))
var selector = "\\s*([^{}]*[^\\s])\\s*{[^\\}]*";
var value = "\\s*:\\s*((?:[^;\\(]|\\([^\\)]*\\))*)\\s*;";

var re, match;
var selector = "\\s*([^{}]*[^\\s])\\s*{[^\\}]*",
value = "\\s*:\\s*((?:[^;\\(]|\\([^\\)]*\\))*)\\s*;",
re,
match,
rules = [],
properties = ["shape-outside", "shape-margin", "shape-image-threshold"],
_this = this;

var rules = [], properties = ["shape-outside", "shape-margin", "shape-image-threshold"];
properties.forEach(function(property) {
re = new RegExp(selector + "(" + property + ")" + value, "ig");
stylesheets.forEach(function(stylesheet) {
while ((match = re.exec(stylesheet.cssText)) !== null) {
rules.push({
selector: match[1],
property: match[2],
value: match[3]
value: match[3],
minWidth: stylesheet.minWidth,
maxWidth: stylesheet.maxWidth
});

}
});
});

this.callback(rules);

};

scope.ShapesPolyfill = new Polyfill(scope);
Expand Down
5 changes: 3 additions & 2 deletions shapes-polyfill.min.js

Large diffs are not rendered by default.

28 changes: 25 additions & 3 deletions src/shape-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,16 @@ function createShapeGeometry(shapeValue, whenReady) {

function ShapeInfo(element) {
this.metrics = new Metrics(element);

var queryStep = this.getQueryStep(element.getAttribute('data-shape-size-mediaqueries').split('|'));

var parserSettings = {
metrics: this.metrics,
shapeOutside: element.getAttribute('data-shape-outside'),
shapeMargin: element.getAttribute('data-shape-margin'),
shapeImageThreshold: element.getAttribute('data-shape-image-threshold')
shapeOutside: element.getAttribute('data-shape-outside').split('|')[queryStep],
shapeMargin: element.getAttribute('data-shape-margin').split('|')[queryStep],
shapeImageThreshold: element.getAttribute('data-shape-image-threshold').split('|')[queryStep]
};

this.shapeValue = new ShapeValue(parserSettings);

var self = this;
Expand All @@ -116,6 +120,24 @@ ShapeInfo.prototype.onReady = function(callback) {
this.callback = callback;
};

ShapeInfo.prototype.getQueryStep = function(steps) {
var i = 0,
step = 0,
documentWidth = document.documentElement.clientWidth,
minWidth,
maxWidth;
for(i; i < steps.length; i++) {
minWidth = steps[i].split(',')[0].split('(')[1];
minWidth = minWidth === "null" ? 0 : Number(minWidth);
maxWidth = steps[i].split(',')[1].split(')')[0];
maxWidth = maxWidth === "null" ? documentWidth : Number(maxWidth);
if(documentWidth >= minWidth && documentWidth <= maxWidth) {
step = i;
}
}
return step;
};

ShapeInfo.prototype.leftExclusionEdge = function(line) { // { top, bottom, left, right }
return this.geometry ? this.geometry.leftExclusionEdge(line.top, line.bottom) : line.left;
};
Expand Down
Loading