├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── nwb.config.js ├── package-lock.json ├── package.json └── src └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*.{css,html,js,scss}] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module" 6 | }, 7 | "env": { 8 | "browser": true 9 | }, 10 | "globals": { 11 | "module": true, 12 | }, 13 | "extends": "eslint:recommended", 14 | "rules": { 15 | "arrow-parens": 2, 16 | "brace-style": [2, "stroustrup"], 17 | "indent": [2, 2], 18 | "no-else-return": 2, 19 | "semi": [2, "always"] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /demo/dist 3 | /es 4 | /lib 5 | /node_modules 6 | /umd 7 | npm-debug.log* 8 | .DS_Store 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 6 6 | 7 | branches: 8 | only: 9 | - master 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Prerequisites 2 | 3 | [Node.js](http://nodejs.org/) >= v4 must be installed. 4 | 5 | ## Installation 6 | 7 | - Running `npm install` in the module's root directory will install everything you need for development. 8 | 9 | ## Building 10 | 11 | - `npm run build` will build the module for publishing to npm. 12 | 13 | - `npm run clean` will delete built resources. 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Matt Stow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | eqio logo 3 |

4 | 5 | Travis build status 6 | 7 | 8 | npm package 9 | 10 |
11 |

12 | 13 | # eqio 14 | 15 | **A simple, tiny alternative to element/container queries** 16 | 17 | eqio allows you to attain the holy grail of responsive web development/design systems: components that can adapt their styling based on *their* width, not the browser‘s. 18 | 19 | It uses [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)s under-the-hood (so is [well supported](https://caniuse.com/#feat=intersectionobserver) in “good” browsers and [easily polyfilled](https://github.com/w3c/IntersectionObserver) in others) to apply appropriately named classes to the component when necessary. 20 | 21 | ## Table of Contents 22 | 23 | - [Demo](#demo) 24 | - [Installation](#installation) 25 | - [npm](#npm) 26 | - [Direct ` 48 | ``` 49 | 50 | ## Usage 51 | 52 | ### The HTML 53 | 54 | 1. Add a class of `eqio` to the element. 55 | 2. Add a `data-eqio-sizes` attribute whose value is a JSON-serializable array of sizes. 56 | 3. Optionally add a `data-eqio-prefix` attribute whose value is used as the prefix for your class names. 57 | 58 | ```html 59 |
64 | … 65 |
66 | ``` 67 | 68 | The above component will: 69 | 70 | * be able to be customised when *its* width is 400 or smaller (`"<"` is a synonym for `max-width`, not “less than”). 71 | * be able to be customised when *its* width is 700 or greater (`">"` is a synonym for `min-width`, not “greater than”). 72 | * apply the following classes `media-object-eqio-<400` and `media-object-eqio->700` as appropriate. If `data-eqio-prefix` had not been specified, the applied classes would be `eqio-<400` and `eqio->700`. 73 | 74 | *Note: Multiple classes can be applied at once.* 75 | 76 | ### The CSS 77 | 78 | In your CSS, write class names that match those applied to the HTML. 79 | 80 | ```scss 81 | .media-object-eqio-\<400 { 82 | /* customizations when less than or equal to 400px */ 83 | } 84 | 85 | .media-object-eqio-\>700 { 86 | /* customizations when greater than or equal to 700px */ 87 | } 88 | ``` 89 | 90 | *Note:* 91 | * *eqio classes include the special characters `<` & `>`, so they must be escaped with a `\` in your CSS.* 92 | * *eqio elements are `position: relative` by default, but your component can override that to `absolute`/`fixed` etc as required.* 93 | * *eqio elements can't be anything but `overflow: visible`.* 94 | * *To prevent accidental creation of horizontal scrollbars, a parent element is required to `overflow-x: hidden`. It is recommended to apply this to `body`.* 95 | 96 | ### The JavaScript 97 | 98 | If using a module bundler, such as webpack, first import `Eqio`. 99 | 100 | ```js 101 | import Eqio from 'eqio'; 102 | ``` 103 | 104 | In your JS, tell eqio which elements are to be made responsive by passing a DOM reference to `Eqio`. 105 | 106 | ```js 107 | var mediaObject = new Eqio(document.querySelector('.media-object')); 108 | ``` 109 | 110 | Should you need to stop this element from being responsive, you can call `.stop()` on your instance: 111 | 112 | ```js 113 | mediaObject.stop(); 114 | ``` 115 | 116 | This will remove all observers and eqio internals, except for the class names that were applied at the time. 117 | 118 | --- 119 | 120 | Copyright (c) 2018 [Matt Stow](http://mattstow.com) 121 | Licensed under the MIT license *(see [LICENSE](https://github.com/stowball/eqio/blob/master/LICENSE) for details)* 122 | -------------------------------------------------------------------------------- /nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'web-module', 3 | npm: { 4 | esModules: true, 5 | umd: { 6 | global: 'Eqio', 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eqio", 3 | "version": "0.1.3", 4 | "description": "A simple, tiny alternative to element/container queries", 5 | "main": "lib/index.js", 6 | "module": "es/index.js", 7 | "files": [ 8 | "es", 9 | "lib", 10 | "umd" 11 | ], 12 | "scripts": { 13 | "build-web-module": "nwb build-web-module", 14 | "build": "npm-run-all lint build-web-module", 15 | "clean": "nwb clean-module", 16 | "lint": "eslint src" 17 | }, 18 | "dependencies": {}, 19 | "devDependencies": { 20 | "babel-eslint": "^7.2.3", 21 | "eslint": "^4.1.0", 22 | "npm-run-all": "^4.0.2", 23 | "nwb": "0.18.x" 24 | }, 25 | "author": "Matt Stow ", 26 | "homepage": "http://mattstow.com", 27 | "license": "MIT", 28 | "repository": "https://github.com/stowball/eqio" 29 | } 30 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | class Eqio { 2 | constructor(el) { 3 | if (!el || !('IntersectionObserver' in window)) { 4 | return; 5 | } 6 | 7 | this.init(el); 8 | } 9 | 10 | init(el) { 11 | this.el = el; 12 | 13 | this.createStyles(); 14 | this.createTriggers(); 15 | this.createObservers(); 16 | } 17 | 18 | createStyles() { 19 | if (!document.getElementById('eqio-req-css')) { 20 | const style = document.createElement('style'); 21 | const css = ` 22 | .eqio { 23 | position: relative; 24 | } 25 | 26 | .eqio__trigger { 27 | height: 1px; 28 | left: 0; 29 | pointer-events: none; 30 | position: absolute; 31 | top: 0; 32 | visibility: hidden; 33 | z-index: -1; 34 | }`; 35 | 36 | style.id = 'eqio-req-css'; 37 | style.type = 'text/css'; 38 | style.appendChild(document.createTextNode(css)); 39 | document.head.insertBefore(style, document.head.children[0]); 40 | } 41 | } 42 | 43 | createTriggers() { 44 | this.sizesArray = JSON.parse(this.el.attributes['data-eqio-sizes'].value); 45 | this.triggerEls = []; 46 | let triggerEl; 47 | const fragment = document.createDocumentFragment(); 48 | 49 | this.sizesArray.forEach((trigger) => { 50 | triggerEl = document.createElement('div'); 51 | triggerEl.className = 'eqio__trigger'; 52 | triggerEl.setAttribute('data-eqio-size', trigger); 53 | triggerEl.style.width = `${trigger.slice(1)}px`; 54 | 55 | this.triggerEls.push(triggerEl); 56 | fragment.appendChild(triggerEl); 57 | }); 58 | 59 | this.el.appendChild(fragment); 60 | } 61 | 62 | createObservers() { 63 | const prefix = this.el.attributes['data-eqio-prefix'] ? `${this.el.attributes['data-eqio-prefix'].value}-` : ''; 64 | const className = `${prefix}eqio-`; 65 | const observerOptions = { 66 | root: this.el, 67 | rootMargin: '0px', 68 | threshold: 1, 69 | }; 70 | 71 | const observerCallback = (entries) => { 72 | entries.forEach((entry) => { 73 | const size = entry.target.dataset.eqioSize; 74 | 75 | if (size.indexOf('>') === 0) { 76 | if (entry.intersectionRatio === 1) { 77 | this.el.classList.add(`${className}${size}`); 78 | } 79 | else { 80 | this.el.classList.remove(`${className}${size}`); 81 | } 82 | } 83 | else { 84 | if (entry.intersectionRatio === 1) { 85 | this.el.classList.remove(`${className}${size}`); 86 | } 87 | else { 88 | this.el.classList.add(`${className}${size}`); 89 | } 90 | } 91 | }); 92 | }; 93 | 94 | this.observer = new IntersectionObserver(observerCallback, observerOptions); 95 | this.sizesArray.forEach((size, index) => { 96 | this.observer.observe(this.triggerEls[index]); 97 | }); 98 | } 99 | 100 | stop() { 101 | this.el.removeAttribute('data-eqio-sizes'); 102 | 103 | this.sizesArray.forEach((size, index) => { 104 | this.observer.unobserve(this.triggerEls[index]); 105 | }); 106 | 107 | this.triggerEls.forEach((trigger) => { 108 | this.el.removeChild(trigger); 109 | }); 110 | 111 | delete this.el; 112 | delete this.observer; 113 | delete this.sizesArray; 114 | delete this.triggerEls; 115 | } 116 | } 117 | 118 | module.exports = Eqio; 119 | --------------------------------------------------------------------------------