├── test-files ├── test.txt ├── cors.css └── cors-with-cq.css ├── .github └── FUNDING.yml ├── .gitignore ├── .tern-project ├── .editorconfig ├── docs ├── index.md ├── browserify.md ├── cors.md ├── installation.md ├── config.md ├── how-it-works.md ├── postcss.md ├── api.md └── usage.md ├── .eslintrc ├── .gitattributes ├── browserstack.json ├── postcss-plugin.js ├── LICENSE ├── mixins.scss ├── package.json ├── README.md ├── .travis.yml ├── postcss-tests.js ├── Makefile ├── tests-functional.js ├── cq-prolyfill.js └── tests.js /test-files/test.txt: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /test-files/cors.css: -------------------------------------------------------------------------------- 1 | .cors-test { 2 | color: red; 3 | width: 10%; 4 | } 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [ausi] 4 | -------------------------------------------------------------------------------- /test-files/cors-with-cq.css: -------------------------------------------------------------------------------- 1 | .cors-test:container(width >= 0px) { 2 | color: blue; 3 | width: 20%; 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /cq-prolyfill.min.js 2 | /cq-prolyfill.min.js.gz 3 | /cq-prolyfill.tmp.min.js 4 | /node_modules/ 5 | /tests/ 6 | /coverage/ 7 | -------------------------------------------------------------------------------- /.tern-project: -------------------------------------------------------------------------------- 1 | { 2 | "libs": [ 3 | "browser" 4 | ], 5 | "plugins": { 6 | "complete_strings": { 7 | "maxLength": 30 8 | }, 9 | "doc_comment": { 10 | "fullDocs": true 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = tab 7 | tab_width = 4 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | * [Installation](installation.md) 4 | * [Usage](usage.md) 5 | * [JavaScript API](api.md) 6 | * [Configuration](config.md) 7 | * [PostCSS plugin](postcss.md) 8 | * [browserify and webpack](browserify.md) 9 | * [Cross origin stylesheets](cors.md) 10 | * [How it works](how-it-works.md) 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true 4 | }, 5 | "rules": { 6 | "quotes": [2, "single"], 7 | "comma-dangle": [2, "always-multiline"], 8 | "strict": [2, "function"], 9 | "no-console": 1, 10 | "no-use-before-define": [2, "nofunc"], 11 | "no-underscore-dangle": 0, 12 | "no-loop-func": 0 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto whitespace=indent-with-non-tab,tabwidth=4 2 | 3 | *.js text 4 | *.json text 5 | *.md text 6 | *.css text 7 | *.txt text 8 | 9 | Makefile text 10 | 11 | .editorconfig text 12 | .eslintrc text 13 | .gitattributes text 14 | .gitignore text 15 | .tern-project text 16 | .travis.yml text whitespace=-indent-with-non-tab,tab-in-indent 17 | -------------------------------------------------------------------------------- /docs/browserify.md: -------------------------------------------------------------------------------- 1 | # browserify and webpack 2 | 3 | If you want to use the prolyfill with [browserify](http://browserify.org/) or [webpack](https://webpack.github.io/) you can do so by `require`ing the module as usual. [The configuration](config.md) can be passed into the required function and [the API](api.md) gets returned: 4 | 5 | ```js 6 | var cq = require('cq-prolyfill')({ /* configuration */ }); 7 | cq.reevaluate(false, function() { 8 | // Do something after all elements were updated 9 | }); 10 | ``` 11 | -------------------------------------------------------------------------------- /browserstack.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_framework" : "qunit", 3 | "test_path": ["tests/coverage.html", "tests/functional.html"], 4 | "browsers": [ 5 | "chrome_latest", 6 | "firefox_36", 7 | "firefox_latest", 8 | "safari_7_1", 9 | "safari_8", 10 | "safari_latest", 11 | "ie_9", 12 | "ie_10", 13 | "ie_11", 14 | "edge_14", 15 | "edge_latest", 16 | "opera_30", 17 | "opera_latest", 18 | { 19 | "os": "ios", 20 | "os_version": "8.3" 21 | }, 22 | { 23 | "os": "ios", 24 | "os_version": "9.3" 25 | }, 26 | { 27 | "os":"winphone", 28 | "os_version":"8.1" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /docs/cors.md: -------------------------------------------------------------------------------- 1 | # Cross origin stylesheets 2 | 3 | In order to work properly the prolyfill needs access to the stylesheets on the page. If a stylesheet is served from a different domain it needs to have a CORS header, e.g. `Access-Control-Allow-Origin: *`. You can find more information about how to enable CORS on your server at [enable-cors.org](http://enable-cors.org/server.html). 4 | 5 | For better performance it’s also recommended to use the attribute `crossorigin="anonymous"` for cross origin `` tags: 6 | 7 | ```html 8 | 9 | ``` 10 | -------------------------------------------------------------------------------- /postcss-plugin.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node */ 2 | var postcss = require('postcss'); 3 | 4 | module.exports = postcss.plugin('cq-prolyfill', function () { 5 | 'use strict'; 6 | return function (css) { 7 | css.walkRules(/:container\(/i, function (rule) { 8 | rule.selectors = rule.selectors.map(function(selector) { 9 | return selector.replace(/:container\((?:[^()]+|\([^()]*\))+\)/gi, function(match) { 10 | return '.' + match 11 | .replace(/\s+/g, '') 12 | .replace(/^:container\("((?:[^()]+|\([^()]*\))+)"\)$/i, ':container($1)') 13 | .replace(/[[\]!"#$%&'()*+,./:;<=>?@^`{|}~]/g, '\\$&') 14 | .toLowerCase(); 15 | }); 16 | }); 17 | }); 18 | }; 19 | }); 20 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | You can install the prolyfill by [downloading it from GitHub](#download-from-github) or [via npm](#install-via-npm). After installing you can use the container queries as described in [Usage](usage.md). 4 | 5 | ## Download from GitHub 6 | 7 | Download the file *cq-prolyfill.min.js* from the [latest release on GitHub](https://github.com/ausi/cq-prolyfill/releases/latest). 8 | 9 | ## Install via npm 10 | 11 | To install it via [npm](https://www.npmjs.com/package/cq-prolyfill) execute the following command in your project directory: 12 | 13 | ```bash 14 | npm install --save cq-prolyfill 15 | ``` 16 | 17 | After the installation completes you can find the prolyfill at *node_modules/cq-prolyfill/cq-prolyfill.min.js* or you load it via [browserify or webpack](browserify.md). 18 | -------------------------------------------------------------------------------- /docs/config.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | * `preprocess` should be set to `true` if the [PostCSS plugin](postcss.md) is not used and the CSS code has to be preprocessed client side. 4 | * `skipObserving` set this to `true` if the prolyfill shouldn’t listen to browser events and DOM modifications and you want to manage it yourself via the [API methods](api.md). 5 | 6 | ## Normal script 7 | 8 | If you installed the prolyfill as a normal script, the configuration can be set via `window.cqConfig`: 9 | 10 | ```html 11 | 17 | 18 | ``` 19 | 20 | ## Module loader 21 | 22 | If you’re using a module loader and [browserify or webpack](browserify.md), pass the configuration as the first parameter to the module function. 23 | 24 | ```js 25 | // Pass the configuration as a parameter 26 | var cq = require('cq-prolyfill')({ 27 | preprocess: true 28 | }); 29 | ``` 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2015 Martin Auswöger 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /docs/how-it-works.md: -------------------------------------------------------------------------------- 1 | # How it works 2 | 3 | It basically runs in three steps: 4 | 5 | ## Step 1 6 | 7 | If the `preprocess` configuration is set, it looks for stylesheets that contain container queries and escapes them to be readable by the browser. 8 | 9 | E.g. this: 10 | 11 | ```css 12 | .element:container(width >= 10px) { 13 | color: red; 14 | } 15 | ``` 16 | 17 | gets converted to this: 18 | 19 | ```css 20 | .element.\:container\(width\>\=10px\) { 21 | color: red; 22 | } 23 | ``` 24 | 25 | This step should be done by the [PostCSS plugin](postcss.md) on the server side to speed up the script. Client side processing is meant for demonstration purposes. 26 | 27 | ## Step 2 28 | 29 | Parses all (pre)processed container query rules and stores them indexed by the preceding selector to be used in step 3. 30 | 31 | ## Step 3 32 | 33 | Loops through all stored queries and adds or removes the CSS classes of the matching elements. The added CSS classes look the same as the container query itself to improve the readability in the developer tools of the browser. E.g.: 34 | 35 | ```html 36 |
37 | ``` 38 | -------------------------------------------------------------------------------- /mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin cq-prolyfill($query) { 2 | {cq-prolyfill($query)} { 3 | @content; 4 | } 5 | } 6 | 7 | @function cq-prolyfill($query) { 8 | @return unquote(".\\:container\\(" + cq-prolyfill-escape(cq-prolyfill-strip-spaces(to-lower-case($query))) + "\\)"); 9 | } 10 | 11 | @function cq-prolyfill-add-backslash($string, $search) { 12 | $index: str-index($string, $search); 13 | @while $index { 14 | $string: str-insert($string, '\\', $index); 15 | $newIndex: if( 16 | str-length($string) < $index + 2, 17 | null, 18 | str-index(str-slice($string, $index + 2), $search) 19 | ); 20 | $index: if($newIndex, $index + 1 + $newIndex, null); 21 | } 22 | @return $string; 23 | } 24 | 25 | @function cq-prolyfill-remove($string, $search) { 26 | $index: str-index($string, $search); 27 | @while $index { 28 | $string: str-slice($string, 1, $index - 1) + str-slice($string, $index + 1); 29 | $index: str-index($string, $search); 30 | } 31 | @return $string; 32 | } 33 | 34 | @function cq-prolyfill-escape($string) { 35 | @each $char in '[' ']' '!' '"' '#' '$' '%' '&' "'" '(' ')' '*' '+' ',' '.' '/' ':' ';' '<' '=' '>' '?' '@' '^' '`' '{' '|' '}' '~' { 36 | $string: cq-prolyfill-add-backslash($string, $char); 37 | } 38 | @return $string; 39 | } 40 | 41 | @function cq-prolyfill-strip-spaces($string) { 42 | // tab, line feed, carriage return and space 43 | $chars: "\9\a\d\20"; 44 | @for $i from 1 through str-length($chars) { 45 | $string: cq-prolyfill-remove( 46 | $string, 47 | str-slice($chars, $i, $i) 48 | ); 49 | } 50 | @return $string; 51 | } 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cq-prolyfill", 3 | "version": "0.4.0", 4 | "description": "Prolyfill for CSS Container Queries (aka Element Queries)", 5 | "license": "MIT", 6 | "keywords": [ 7 | "CSS", 8 | "css", 9 | "RWD", 10 | "rwd", 11 | "Container Queries", 12 | "container-queries", 13 | "Container Query", 14 | "container-query", 15 | "Element Queries", 16 | "element-queries", 17 | "Element Query", 18 | "element-query", 19 | "Prolyfill", 20 | "prolyfill", 21 | "Prollyfill", 22 | "prollyfill", 23 | "Polyfill", 24 | "polyfill", 25 | "Browser", 26 | "browser", 27 | "postcss", 28 | "postcss-plugin" 29 | ], 30 | "scripts": { 31 | "test": "make test" 32 | }, 33 | "main": "cq-prolyfill.js", 34 | "browser": "cq-prolyfill.js", 35 | "files": [ 36 | "cq-prolyfill.js", 37 | "cq-prolyfill.min.js", 38 | "cq-prolyfill.min.js.gz", 39 | "postcss-plugin.js", 40 | "docs" 41 | ], 42 | "homepage": "https://github.com/ausi/cq-prolyfill", 43 | "repository": "ausi/cq-prolyfill", 44 | "bugs": { 45 | "url": "https://github.com/ausi/cq-prolyfill/issues" 46 | }, 47 | "dependencies": { 48 | "postcss": "^5.0.2" 49 | }, 50 | "devDependencies": { 51 | "browserstack-runner": "^0.5.0", 52 | "connect": "^3.4.0", 53 | "coveralls": "^2.11.4", 54 | "eslint": "^1.10.3", 55 | "istanbul": "^0.4.1", 56 | "node-sass": "^3.4.2", 57 | "node-zopfli": "^1.4.0", 58 | "qunit-phantomjs-runner": "^2.1.0", 59 | "qunitjs": "^1.18.0", 60 | "serve-static": "^1.10.0", 61 | "slimerjs": "^0.906.2", 62 | "uglify-js": "^2.4.24" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /docs/postcss.md: -------------------------------------------------------------------------------- 1 | # PostCSS plugin 2 | 3 | To improve the performance of the prolyfill, you should use [PostCSS](https://github.com/postcss/postcss) to prepare the stylesheet on the server side: 4 | 5 | ```js 6 | var fs = require('fs'); 7 | var cqPostcss = require('cq-prolyfill/postcss-plugin'); 8 | 9 | fs.writeFileSync( 10 | 'dist.css', 11 | cqPostcss.process(fs.readFileSync('source.css', 'utf-8')).css 12 | ); 13 | ``` 14 | 15 | This converts container queries like: 16 | 17 | ```css 18 | .element:container(width >= 100px) { /* ... */ } 19 | ``` 20 | 21 | Into valid CSS selectors: 22 | 23 | ```css 24 | .element.\:container\(width\>\=100px\) { /* ... */ } 25 | ``` 26 | 27 | If you don’t use the PostCSS plugin, you can use the supplied [Sass mixin](usage.md#sass-less-and-other-preprocessors) instead or activate the `preprocess` option in the [configuration](config.md). 28 | 29 | Don’t forget to [enable CORS](cors.md) if the stylesheet is loaded from a different domain. 30 | 31 | ## Build systems 32 | 33 | If you’re using a build system like grunt or gulp you can integrate the PostCSS plugin in this process. 34 | 35 | ### Grunt 36 | 37 | ```js 38 | grunt.loadNpmTasks('grunt-postcss'); 39 | grunt.initConfig({ 40 | postcss: { 41 | options: { 42 | processors: [ 43 | require('cq-prolyfill/postcss-plugin')() 44 | ] 45 | }, 46 | dist: { 47 | src: 'css/*.css' 48 | } 49 | } 50 | }); 51 | ``` 52 | 53 | ### Gulp 54 | 55 | ```js 56 | var postcss = require('gulp-postcss'); 57 | gulp.task('css', function () { 58 | return gulp.src('./src/*.css') 59 | .pipe(postcss([ 60 | require('cq-prolyfill/postcss-plugin')() 61 | ])) 62 | .pipe(gulp.dest('./dest')); 63 | }); 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # JavaScript API 2 | 3 | The script triggers itself on load, on DOM ready, when the browser window resizes and if new elements are added to the DOM. If you want to trigger it manually you can call `reprocess` (step 1), `reparse` (step 2) or `reevaluate` (step 3) on the `window.cqApi` object. Most of the time `reevaluate` should do the job if you didn’t add, remove or change stylesheets. E.g. 4 | 5 | ```js 6 | document.querySelector('.element').addEventListener('click', function() { 7 | // Do something that changes the size of container elements 8 | // ... 9 | window.cqApi.reevaluate(false, function() { 10 | // Do something after all elements were updated 11 | }); 12 | }); 13 | ``` 14 | 15 | If you installed the prolyfill as a normal script the API is available at `window.cqApi`. If you’re using a module loader the API gets returned from the module function. 16 | 17 | ## `reprocess(fn callback)` 18 | 19 | Reprocess all stylesheets on the page. Call this method if you added a stylesheet via JavaScript. The `callback` gets called after all stylesheets are processed, parsed and evaluated. 20 | 21 | ## `reparse(fn callback)` 22 | 23 | Reparse all stylesheets on the page and look for new container queries. Call this method if you added a stylesheet via JavaScript which doesn’t contain a container query. The `callback` gets called after all stylesheets are parsed and evaluated. 24 | 25 | ## `reevaluate(bool clearCache, fn callback, array contexts)` 26 | 27 | Reevaluate all container queries. Call this method if you added new elements or changed styles that affect a container query. The boolean parameter `clearCache` specifies if the container cache should be cleared before the evaluation. The `callback` gets called after all container queries are evaluated. You can optionally pass an array of DOM elements as `contexts` if you only want to reevaluate some parts of the page. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Container Queries Prolyfill 2 | 3 | [](https://travis-ci.org/ausi/cq-prolyfill/branches) [](https://coveralls.io/github/ausi/cq-prolyfill?branch=master) [ ](https://www.npmjs.com/package/cq-prolyfill)  4 | 5 | This is a [prolyfill](https://au.si/what-is-a-prolyfill) for a special version of [container queries](https://github.com/ResponsiveImagesCG/container-queries) (aka element queries). You can read more about the idea and how they work internally in [this article](https://au.si/css-container-element-queries). 6 | 7 | ## Demo 8 | 9 | A quick demo of the container queries in action can be found here: 10 |